Skip to content

Commit 33249a7

Browse files
committed
fix: address review comments
- Rename `helpShown` to `earlyExit` for clarity (issue #4) The flag is now accurately named since it's set for help, version, and completion output, not just help display. - Extract shared test helper `withCapturedStderr()` (issues #2, #3) Reduces code duplication in tests that capture stderr and exitCode. - Handle HelpError in nested command delegation (issue #1) `__parseWithParentGlobals()` now catches HelpError so nested builders can render their own help instead of bubbling up to the parent.
1 parent 33a4e59 commit 33249a7

File tree

4 files changed

+249
-172
lines changed

4 files changed

+249
-172
lines changed

src/bargs.ts

Lines changed: 56 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -557,17 +557,46 @@ const createCliBuilder = <V, P extends readonly unknown[]>(
557557
},
558558

559559
// Internal method for nested command support - not part of public API
560+
// Handles HelpError here so nested builders can render their own help
560561
__parseWithParentGlobals(
561562
args: string[],
562563
parentGlobals: ParseResult<unknown, readonly unknown[]>,
563564
allowAsync: boolean,
564565
):
565-
| (ParseResult<V, P> & { command?: string })
566-
| Promise<ParseResult<V, P> & { command?: string }> {
566+
| (ParseResult<V, P> & { command?: string; earlyExit?: boolean })
567+
| Promise<ParseResult<V, P> & { command?: string; earlyExit?: boolean }> {
567568
const stateWithGlobals = { ...state, parentGlobals };
568-
return parseCore(stateWithGlobals, args, allowAsync) as
569-
| (ParseResult<V, P> & { command?: string })
570-
| Promise<ParseResult<V, P> & { command?: string }>;
569+
try {
570+
const result = parseCore(stateWithGlobals, args, allowAsync);
571+
if (isThenable(result)) {
572+
return result.catch((error: unknown) => {
573+
if (error instanceof HelpError) {
574+
return handleHelpError(error, stateWithGlobals) as ParseResult<
575+
V,
576+
P
577+
> & {
578+
command?: string;
579+
earlyExit: true;
580+
};
581+
}
582+
throw error;
583+
}) as Promise<
584+
ParseResult<V, P> & { command?: string; earlyExit?: boolean }
585+
>;
586+
}
587+
return result as ParseResult<V, P> & { command?: string };
588+
} catch (error) {
589+
if (error instanceof HelpError) {
590+
return handleHelpError(error, stateWithGlobals) as ParseResult<
591+
V,
592+
P
593+
> & {
594+
command?: string;
595+
earlyExit: true;
596+
};
597+
}
598+
throw error;
599+
}
571600
},
572601

573602
// Overloaded command(): accepts (name, factory, options?),
@@ -760,7 +789,7 @@ const createCliBuilder = <V, P extends readonly unknown[]>(
760789

761790
parse(
762791
args: string[] = process.argv.slice(2),
763-
): ParseResult<V, P> & { command?: string; helpShown?: boolean } {
792+
): ParseResult<V, P> & { command?: string; earlyExit?: boolean } {
764793
try {
765794
const result = parseCore(state, args, false);
766795
if (isThenable(result)) {
@@ -773,7 +802,7 @@ const createCliBuilder = <V, P extends readonly unknown[]>(
773802
if (error instanceof HelpError) {
774803
return handleHelpError(error, state) as ParseResult<V, P> & {
775804
command?: string;
776-
helpShown: true;
805+
earlyExit: true;
777806
};
778807
}
779808
throw error;
@@ -782,7 +811,7 @@ const createCliBuilder = <V, P extends readonly unknown[]>(
782811

783812
async parseAsync(
784813
args: string[] = process.argv.slice(2),
785-
): Promise<ParseResult<V, P> & { command?: string; helpShown?: boolean }> {
814+
): Promise<ParseResult<V, P> & { command?: string; earlyExit?: boolean }> {
786815
try {
787816
return (await parseCore(state, args, true)) as ParseResult<V, P> & {
788817
command?: string;
@@ -791,7 +820,7 @@ const createCliBuilder = <V, P extends readonly unknown[]>(
791820
if (error instanceof HelpError) {
792821
return handleHelpError(error, state) as ParseResult<V, P> & {
793822
command?: string;
794-
helpShown: true;
823+
earlyExit: true;
795824
};
796825
}
797826
throw error;
@@ -821,18 +850,18 @@ const parseCore = (
821850

822851
/**
823852
* Helper to create an early-exit result (for help, version, completions).
824-
* Sets process.exitCode and returns a result with helpShown: true.
853+
* Sets process.exitCode and returns a result with earlyExit: true.
825854
*
826855
* @function
827856
*/
828-
const earlyExit = (
857+
const createEarlyExitResult = (
829858
exitCode: number,
830859
): ParseResult<unknown, readonly unknown[]> & {
831860
command?: string;
832-
helpShown: true;
861+
earlyExit: true;
833862
} => {
834863
process.exitCode = exitCode;
835-
return { command: undefined, helpShown: true, positionals: [], values: {} };
864+
return { command: undefined, earlyExit: true, positionals: [], values: {} };
836865
};
837866

838867
// Handle --help
@@ -880,12 +909,12 @@ const parseCore = (
880909

881910
// Regular command help
882911
console.log(generateCommandHelpNew(state, commandName, theme));
883-
return earlyExit(0);
912+
return createEarlyExitResult(0);
884913
}
885914
}
886915

887916
console.log(generateHelpNew(state, theme));
888-
return earlyExit(0);
917+
return createEarlyExitResult(0);
889918
}
890919

891920
// Handle --version
@@ -896,7 +925,7 @@ const parseCore = (
896925
} else {
897926
console.log('Version information not available');
898927
}
899-
return earlyExit(0);
928+
return createEarlyExitResult(0);
900929
}
901930

902931
// Handle shell completion (when enabled)
@@ -909,15 +938,15 @@ const parseCore = (
909938
console.error(
910939
'Error: --completion-script requires a shell argument (bash, zsh, or fish)',
911940
);
912-
return earlyExit(1);
941+
return createEarlyExitResult(1);
913942
}
914943
try {
915944
const shell = validateShell(shellArg);
916945
console.log(generateCompletionScript(state.name, shell));
917-
return earlyExit(0);
946+
return createEarlyExitResult(0);
918947
} catch (err) {
919948
console.error(`Error: ${(err as Error).message}`);
920-
return earlyExit(1);
949+
return createEarlyExitResult(1);
921950
}
922951
}
923952

@@ -927,7 +956,7 @@ const parseCore = (
927956
const shellArg = args[getCompletionsIndex + 1];
928957
if (!shellArg) {
929958
// No shell specified, output nothing
930-
return earlyExit(0);
959+
return createEarlyExitResult(0);
931960
}
932961
try {
933962
const shell = validateShell(shellArg);
@@ -937,10 +966,10 @@ const parseCore = (
937966
if (candidates.length > 0) {
938967
console.log(candidates.join('\n'));
939968
}
940-
return earlyExit(0);
969+
return createEarlyExitResult(0);
941970
} catch {
942971
// Invalid shell, output nothing
943-
return earlyExit(0);
972+
return createEarlyExitResult(0);
944973
}
945974
}
946975
}
@@ -965,19 +994,19 @@ const showNestedCommandHelp = (
965994
):
966995
| (ParseResult<unknown, readonly unknown[]> & {
967996
command?: string;
968-
helpShown?: boolean;
997+
earlyExit?: boolean;
969998
})
970999
| Promise<
9711000
ParseResult<unknown, readonly unknown[]> & {
9721001
command?: string;
973-
helpShown?: boolean;
1002+
earlyExit?: boolean;
9741003
}
9751004
> => {
9761005
const commandEntry = state.commands.get(commandName);
9771006
if (!commandEntry || commandEntry.type !== 'nested') {
9781007
console.error(`Unknown command group: ${commandName}`);
9791008
process.exitCode = 1;
980-
return { command: undefined, helpShown: true, positionals: [], values: {} };
1009+
return { command: undefined, earlyExit: true, positionals: [], values: {} };
9811010
}
9821011

9831012
// Delegate to nested builder with --help
@@ -1107,7 +1136,7 @@ const handleHelpError = (
11071136
state: InternalCliState,
11081137
): ParseResult<unknown, readonly unknown[]> & {
11091138
command?: string;
1110-
helpShown: true;
1139+
earlyExit: true;
11111140
} => {
11121141
const { theme } = state;
11131142

@@ -1125,7 +1154,7 @@ const handleHelpError = (
11251154
// Return a result indicating help was shown
11261155
return {
11271156
command: error.command,
1128-
helpShown: true,
1157+
earlyExit: true,
11291158
positionals: [],
11301159
values: {},
11311160
};

src/types.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -163,28 +163,28 @@ export interface CliBuilder<
163163
*
164164
* Throws if any transform or handler returns a Promise.
165165
*
166-
* When a HelpError occurs (e.g., unknown command, no command specified), help
167-
* text is displayed to stderr, process.exitCode is set to 1, and a result
168-
* with `helpShown: true` is returned instead of throwing.
166+
* When an early exit occurs (--help, --version, --completion-script, or
167+
* HelpError), output is displayed, process.exitCode is set appropriately, and
168+
* a result with `earlyExit: true` is returned instead of throwing.
169169
*/
170170
parse(args?: string[]): ParseResult<TGlobalValues, TGlobalPositionals> & {
171171
command?: string;
172-
helpShown?: boolean;
172+
earlyExit?: boolean;
173173
};
174174

175175
/**
176176
* Parse arguments asynchronously and run handlers.
177177
*
178178
* Supports async transforms and handlers.
179179
*
180-
* When a HelpError occurs (e.g., unknown command, no command specified), help
181-
* text is displayed to stderr, process.exitCode is set to 1, and a result
182-
* with `helpShown: true` is returned instead of rejecting.
180+
* When an early exit occurs (--help, --version, --completion-script, or
181+
* HelpError), output is displayed, process.exitCode is set appropriately, and
182+
* a result with `earlyExit: true` is returned instead of rejecting.
183183
*/
184184
parseAsync(args?: string[]): Promise<
185185
ParseResult<TGlobalValues, TGlobalPositionals> & {
186186
command?: string;
187-
helpShown?: boolean;
187+
earlyExit?: boolean;
188188
}
189189
>;
190190
}

0 commit comments

Comments
 (0)