@@ -819,7 +819,22 @@ const parseCore = (
819819 > => {
820820 const { aliasMap, commands, options, theme } = state ;
821821
822- /* c8 ignore start -- help/version output calls process.exit() */
822+ /**
823+ * Helper to create an early-exit result (for help, version, completions).
824+ * Sets process.exitCode and returns a result with helpShown: true.
825+ *
826+ * @function
827+ */
828+ const earlyExit = (
829+ exitCode : number ,
830+ ) : ParseResult < unknown , readonly unknown [ ] > & {
831+ command ?: string ;
832+ helpShown : true ;
833+ } => {
834+ process . exitCode = exitCode ;
835+ return { command : undefined , helpShown : true , positionals : [ ] , values : { } } ;
836+ } ;
837+
823838 // Handle --help
824839 if ( args . includes ( '--help' ) || args . includes ( '-h' ) ) {
825840 // Check for command-specific help
@@ -852,27 +867,25 @@ const parseCore = (
852867 values : { } ,
853868 } ;
854869 // This will trigger the nested builder's help handling
855- // and call process.exit(0) if --help is handled
856- void internalNestedBuilder . __parseWithParentGlobals (
870+ return internalNestedBuilder . __parseWithParentGlobals (
857871 nestedArgs ,
858872 emptyGlobals ,
859873 true ,
860874 ) ;
861875 }
862876
863877 // If no more args, show help for this nested command group
864- showNestedCommandHelp ( state , commandName ) ;
865- // showNestedCommandHelp calls process.exit(0)
878+ return showNestedCommandHelp ( state , commandName ) ;
866879 }
867880
868881 // Regular command help
869882 console . log ( generateCommandHelpNew ( state , commandName , theme ) ) ;
870- process . exit ( 0 ) ;
883+ return earlyExit ( 0 ) ;
871884 }
872885 }
873886
874887 console . log ( generateHelpNew ( state , theme ) ) ;
875- process . exit ( 0 ) ;
888+ return earlyExit ( 0 ) ;
876889 }
877890
878891 // Handle --version
@@ -883,7 +896,7 @@ const parseCore = (
883896 } else {
884897 console . log ( 'Version information not available' ) ;
885898 }
886- process . exit ( 0 ) ;
899+ return earlyExit ( 0 ) ;
887900 }
888901
889902 // Handle shell completion (when enabled)
@@ -896,15 +909,15 @@ const parseCore = (
896909 console . error (
897910 'Error: --completion-script requires a shell argument (bash, zsh, or fish)' ,
898911 ) ;
899- process . exit ( 1 ) ;
912+ return earlyExit ( 1 ) ;
900913 }
901914 try {
902915 const shell = validateShell ( shellArg ) ;
903916 console . log ( generateCompletionScript ( state . name , shell ) ) ;
904- process . exit ( 0 ) ;
917+ return earlyExit ( 0 ) ;
905918 } catch ( err ) {
906919 console . error ( `Error: ${ ( err as Error ) . message } ` ) ;
907- process . exit ( 1 ) ;
920+ return earlyExit ( 1 ) ;
908921 }
909922 }
910923
@@ -914,7 +927,7 @@ const parseCore = (
914927 const shellArg = args [ getCompletionsIndex + 1 ] ;
915928 if ( ! shellArg ) {
916929 // No shell specified, output nothing
917- process . exit ( 0 ) ;
930+ return earlyExit ( 0 ) ;
918931 }
919932 try {
920933 const shell = validateShell ( shellArg ) ;
@@ -924,14 +937,13 @@ const parseCore = (
924937 if ( candidates . length > 0 ) {
925938 console . log ( candidates . join ( '\n' ) ) ;
926939 }
927- process . exit ( 0 ) ;
940+ return earlyExit ( 0 ) ;
928941 } catch {
929942 // Invalid shell, output nothing
930- process . exit ( 0 ) ;
943+ return earlyExit ( 0 ) ;
931944 }
932945 }
933946 }
934- /* c8 ignore stop */
935947
936948 // If we have commands, dispatch to the appropriate one
937949 if ( commands . size > 0 ) {
@@ -947,15 +959,25 @@ const parseCore = (
947959 *
948960 * @function
949961 */
950- /* c8 ignore start -- only called from help paths that call process.exit() */
951962const showNestedCommandHelp = (
952963 state : InternalCliState ,
953964 commandName : string ,
954- ) : void => {
965+ ) :
966+ | ( ParseResult < unknown , readonly unknown [ ] > & {
967+ command ?: string ;
968+ helpShown ?: boolean ;
969+ } )
970+ | Promise <
971+ ParseResult < unknown , readonly unknown [ ] > & {
972+ command ?: string ;
973+ helpShown ?: boolean ;
974+ }
975+ > => {
955976 const commandEntry = state . commands . get ( commandName ) ;
956977 if ( ! commandEntry || commandEntry . type !== 'nested' ) {
957- console . log ( `Unknown command group: ${ commandName } ` ) ;
958- process . exit ( 1 ) ;
978+ console . error ( `Unknown command group: ${ commandName } ` ) ;
979+ process . exitCode = 1 ;
980+ return { command : undefined , helpShown : true , positionals : [ ] , values : { } } ;
959981 }
960982
961983 // Delegate to nested builder with --help
@@ -968,21 +990,20 @@ const showNestedCommandHelp = (
968990 values : { } ,
969991 } ;
970992
971- // This will show the nested builder's help and call process.exit(0)
972- void internalNestedBuilder . __parseWithParentGlobals (
993+ // This will show the nested builder's help
994+ return internalNestedBuilder . __parseWithParentGlobals (
973995 [ '--help' ] ,
974996 emptyGlobals ,
975997 true ,
976998 ) ;
977999} ;
978- /* c8 ignore stop */
9791000
9801001/**
9811002 * Generate command-specific help.
9821003 *
9831004 * @function
9841005 */
985- /* c8 ignore start -- only called from help paths that call process.exit() */
1006+ /* c8 ignore start -- only called from help paths */
9861007const generateCommandHelpNew = (
9871008 state : InternalCliState ,
9881009 commandName : string ,
@@ -993,11 +1014,10 @@ const generateCommandHelpNew = (
9931014 return `Unknown command: ${ commandName } ` ;
9941015 }
9951016
996- // Handle nested commands - this shouldn't be reached as nested commands
997- // delegate to showNestedCommandHelp in parseCore, but handle it gracefully
1017+ // Nested commands are handled by showNestedCommandHelp in parseCore,
1018+ // so this function should never be called for nested commands
9981019 if ( commandEntry . type === 'nested' ) {
999- showNestedCommandHelp ( state , commandName ) ;
1000- return '' ; // Never reached, showNestedCommandHelp calls process.exit
1020+ return `${ commandName } is a command group. Use --help after a subcommand.` ;
10011021 }
10021022
10031023 // Regular command help
@@ -1024,7 +1044,7 @@ const generateCommandHelpNew = (
10241044 *
10251045 * @function
10261046 */
1027- /* c8 ignore start -- only called from help paths that call process.exit() */
1047+ /* c8 ignore start -- only called from help paths */
10281048const generateHelpNew = ( state : InternalCliState , theme : Theme ) : string => {
10291049 // Build options schema, adding built-in options
10301050 let options = state . globalParser ?. __optionsSchema ;
0 commit comments