Skip to content

Commit 2f205c1

Browse files
committed
fix: ensure demandOption is properly supported
1 parent 4ec1421 commit 2f205c1

File tree

3 files changed

+67
-12
lines changed

3 files changed

+67
-12
lines changed

src/discover.ts

+15-12
Original file line numberDiff line numberDiff line change
@@ -1029,19 +1029,22 @@ export async function discoverCommands(
10291029
: { [args[0] as string]: args[1] || /* istanbul ignore next */ {} }
10301030
) as Record<string, Options>;
10311031

1032-
Object.entries(options).forEach(([option, optionConfiguration]) => {
1033-
if ('demandOption' in optionConfiguration) {
1034-
debug_(
1035-
`discarded attempted mutation of disabled yargs option configuration key "demandOption" (for the %O option) on %O program`,
1036-
option,
1037-
descriptor
1038-
);
1039-
1040-
delete optionConfiguration.demandOption;
1041-
}
1042-
});
1032+
const optionsShallowClone = Object.fromEntries(
1033+
Object.entries(options).map(([option, optionConfiguration]) => {
1034+
if ('demandOption' in optionConfiguration) {
1035+
debug_(
1036+
`discarded attempted mutation of disabled yargs option configuration key "demandOption" (for the %O option) on %O program`,
1037+
option,
1038+
descriptor
1039+
);
1040+
}
1041+
1042+
const { demandOption: _, ...rest } = optionConfiguration;
1043+
return [option, rest];
1044+
})
1045+
);
10431046

1044-
target.options(options);
1047+
target.options(optionsShallowClone);
10451048
return proxy;
10461049
};
10471050
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// @ts-check
2+
/// <reference path="../index.d.ts"/>
3+
4+
const { dirname, basename } = require('node:path');
5+
const name = basename(dirname(__filename));
6+
7+
/**
8+
* @type {Type.RootConfig}
9+
*/
10+
module.exports = {
11+
usage: `usage text for root program ${name}`,
12+
builder: {
13+
option: { demandOption: true, string: true, choices: ['a', 'b'] }
14+
},
15+
handler: (argv) => {
16+
argv.handled_by = __filename;
17+
}
18+
};

test/unit-index.test.ts

+34
Original file line numberDiff line numberDiff line change
@@ -3590,6 +3590,40 @@ describe('<command module auto-discovery>', () => {
35903590
});
35913591
});
35923592

3593+
it('does not pass undefined through argv when using "builder"', async () => {
3594+
expect.hasAssertions();
3595+
3596+
const run = bf_util.makeRunner({
3597+
commandModulePath: getFixturePath('one-file-builder-object-literal')
3598+
});
3599+
3600+
await withMocks(async ({ errorSpy, getExitCode }) => {
3601+
await expect(run(['--option', 'a'])).resolves.toContainEntries([
3602+
['option', 'a'],
3603+
['handled_by', expect.any(String)]
3604+
]);
3605+
3606+
expect(getExitCode()).toBe(bf.FrameworkExitCode.Ok);
3607+
3608+
await expect(run(['--option', 'b'])).resolves.toContainEntries([
3609+
['option', 'b'],
3610+
['handled_by', expect.any(String)]
3611+
]);
3612+
3613+
expect(getExitCode()).toBe(bf.FrameworkExitCode.Ok);
3614+
3615+
await expect(run()).resolves.toBeUndefined();
3616+
3617+
expect(getExitCode()).toBe(bf.FrameworkExitCode.DefaultError);
3618+
3619+
expect(errorSpy.mock.calls).toStrictEqual([
3620+
[expect.any(String)],
3621+
[],
3622+
[expect.stringContaining('Missing required argument: option')]
3623+
]);
3624+
});
3625+
});
3626+
35933627
it('ensures PreExecutionContext::rootPrograms is PreExecutionContext.commands[0].programs and also referenced by the root command full name', async () => {
35943628
expect.hasAssertions();
35953629

0 commit comments

Comments
 (0)