From 77752b07226b2f19b1ecce288d1f26e6d3b20917 Mon Sep 17 00:00:00 2001 From: Osman Altun <17157809+OsmanAltun@users.noreply.github.com> Date: Tue, 8 Dec 2020 14:18:16 +0100 Subject: [PATCH 01/15] Standardx 7.0.0 --- package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 428ead297..f55e213b7 100644 --- a/package.json +++ b/package.json @@ -71,14 +71,14 @@ "rimraf": "^3.0.2", "rollup": "^2.23.0", "rollup-plugin-cleanup": "^3.1.1", - "standardx": "^5.0.0", + "standardx": "^7.0.0", "typescript": "^4.0.2", "which": "^2.0.0", "yargs-test-extends": "^1.0.1" }, "scripts": { "fix": "gts fix && npm run fix:js", - "fix:js": "standardx --fix '**/*.mjs' && standardx --fix '**/*.cjs' && standardx --fix './*.mjs' && standardx --fix './*.cjs'", + "fix:js": "standardx --fix **/*.mjs && standardx --fix **/*.cjs && standardx --fix ./*.mjs && standardx --fix ./*.cjs", "posttest": "npm run check", "test": "c8 mocha ./test/*.cjs --require ./test/before.cjs --timeout=12000 --check-leaks", "test:esm": "c8 mocha ./test/esm/*.mjs --check-leaks", @@ -90,7 +90,7 @@ "build:cjs": "rollup -c rollup.config.cjs", "postbuild:cjs": "rimraf ./build/index.cjs.d.ts", "check": "gts lint && npm run check:js", - "check:js": "standardx '**/*.mjs' && standardx '**/*.cjs' && standardx './*.mjs' && standardx './*.cjs'", + "check:js": "standardx **/*.mjs && standardx **/*.cjs && standardx ./*.mjs && standardx ./*.cjs", "clean": "gts clean" }, "repository": { From 380782dd1a3af7226ff5ee70a888b7cd4a0c46ec Mon Sep 17 00:00:00 2001 From: Osman Altun <17157809+OsmanAltun@users.noreply.github.com> Date: Tue, 8 Dec 2020 14:51:48 +0100 Subject: [PATCH 02/15] Fix help output with default command --- lib/command.ts | 49 +++++++++++++++++++++++++++++++------------------ test/usage.cjs | 7 ++++--- 2 files changed, 35 insertions(+), 21 deletions(-) diff --git a/lib/command.ts b/lib/command.ts index 781812268..d8182bade 100644 --- a/lib/command.ts +++ b/lib/command.ts @@ -219,6 +219,9 @@ export function command( let numFiles = currentContext.files.length; const parentCommands = currentContext.commands.slice(); + const tmpCommands = yargs.getUsageInstance().getCommands(); + const tmpUsage = yargs.getUsageInstance().getUsage(); + // what does yargs look like after the builder is run? let innerArgv: Arguments | Promise = parsed.argv; let positionalMap: Dictionary = {}; @@ -233,15 +236,20 @@ export function command( const builderOutput = builder(yargs.reset(parsed.aliases)); const innerYargs = isYargsInstance(builderOutput) ? builderOutput : yargs; if (shouldUpdateUsage(innerYargs)) { - innerYargs - .getUsageInstance() - .usage( - usageFromParentCommandsCommandHandler( - parentCommands, - commandHandler - ), - commandHandler.description - ); + if (!command) { + tmpCommands.forEach(e => innerYargs.getUsageInstance().command(...e)); + tmpUsage.forEach(e => innerYargs.getUsageInstance().usage(...e)); + } else { + innerYargs + .getUsageInstance() + .usage( + usageFromParentCommandsCommandHandler( + parentCommands, + commandHandler + ), + commandHandler.description + ); + } } innerArgv = innerYargs._parseArgs(null, null, true, commandIndex); aliases = (innerYargs.parsed as DetailedArguments).aliases; @@ -250,15 +258,20 @@ export function command( // the options that a command takes. const innerYargs = yargs.reset(parsed.aliases); if (shouldUpdateUsage(innerYargs)) { - innerYargs - .getUsageInstance() - .usage( - usageFromParentCommandsCommandHandler( - parentCommands, - commandHandler - ), - commandHandler.description - ); + if (!command) { + tmpCommands.forEach(e => innerYargs.getUsageInstance().command(...e)); + tmpUsage.forEach(e => innerYargs.getUsageInstance().usage(...e)); + } else { + innerYargs + .getUsageInstance() + .usage( + usageFromParentCommandsCommandHandler( + parentCommands, + commandHandler + ), + commandHandler.description + ); + } } Object.keys(commandHandler.builder).forEach(key => { innerYargs.option(key, builder[key]); diff --git a/test/usage.cjs b/test/usage.cjs index 5df51561e..07e8819fa 100644 --- a/test/usage.cjs +++ b/test/usage.cjs @@ -2575,13 +2575,14 @@ describe('usage tests', () => { r.logs[0] .split('\n') .should.deep.equal([ - 'usage', + 'usage [command]', '', - 'do something', + 'Commands:', + ' usage do something [default]', '', 'Options:', ' --version Show version number [boolean]', - ' -h, --help Show help [boolean]', + ' -h, --help Show help [boolean]' ]); }); }); From 80edee12e0b493251c271417660fdd7cbc0e93db Mon Sep 17 00:00:00 2001 From: Osman Altun <17157809+OsmanAltun@users.noreply.github.com> Date: Tue, 8 Dec 2020 17:44:35 +0100 Subject: [PATCH 03/15] Fix help/version option description not showing --- lib/usage.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/usage.ts b/lib/usage.ts index c5da825d8..79e6ceb56 100644 --- a/lib/usage.ts +++ b/lib/usage.ts @@ -526,10 +526,14 @@ export function usage(yargs: YargsInstance, y18n: Y18N, shim: PlatformShim) { (Object.keys(options.alias) || []).forEach(key => { options.alias[key].forEach(alias => { // copy descriptions. - if (descriptions[alias]) self.describe(key, descriptions[alias]); + if (descriptions[alias]) + self.describe(key, descriptions[key] || descriptions[alias]); // copy demanded. if (alias in demandedOptions) - yargs.demandOption(key, demandedOptions[alias]); + yargs.demandOption( + key, + demandedOptions[key] || demandedOptions[alias] + ); // type messages. if (~options.boolean.indexOf(alias)) yargs.boolean(key); if (~options.count.indexOf(alias)) yargs.count(key); From ecd0d4ad43895e37590ee21d27d0cd3c87f19e94 Mon Sep 17 00:00:00 2001 From: Osman Altun <17157809+OsmanAltun@users.noreply.github.com> Date: Tue, 8 Dec 2020 21:00:18 +0100 Subject: [PATCH 04/15] Fix showhelp calling default command handler --- lib/yargs-factory.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/yargs-factory.ts b/lib/yargs-factory.ts index 5622dd628..99ca6cdb1 100644 --- a/lib/yargs-factory.ts +++ b/lib/yargs-factory.ts @@ -1229,7 +1229,7 @@ function Yargs( self.showHelp = function (level) { argsert('[string|function]', [level], arguments.length); - if (!self.parsed) self._parseArgs(processArgs); // run parser, if it has not already been executed. + if (!self.parsed) self._parseArgs(processArgs, true); // run parser, if it has not already been executed. if (command.hasDefaultCommand()) { context.resets++; // override the restriction on top-level positoinals. command.runDefaultBuilderOn(self); From f8f767ad95866dd7f2a0c598f3bb41de9e8dcfbb Mon Sep 17 00:00:00 2001 From: Osman Altun <17157809+OsmanAltun@users.noreply.github.com> Date: Wed, 9 Dec 2020 14:51:31 +0100 Subject: [PATCH 05/15] Added commands function (similar to options function) --- lib/yargs-factory.ts | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/lib/yargs-factory.ts b/lib/yargs-factory.ts index 99ca6cdb1..c1037345e 100644 --- a/lib/yargs-factory.ts +++ b/lib/yargs-factory.ts @@ -655,7 +655,7 @@ function Yargs( return self; }; - self.command = function ( + self.command = self.commands = function ( cmd: string | CommandHandlerDefinition | DefinitionOrCommandName[], description?: CommandHandler['description'], builder?: CommandBuilderDefinition | CommandBuilder, @@ -1804,15 +1804,18 @@ export interface YargsInstance { (keys: string | string[], coerceCallback: CoerceCallback): YargsInstance; (keyCoerceCallbacks: Dictionary): YargsInstance; }; - command(handler: CommandHandlerDefinition): YargsInstance; - command( - cmd: string | string[], - description: CommandHandler['description'], - builder?: CommandBuilderDefinition | CommandBuilder, - handler?: CommandHandlerCallback, - commandMiddleware?: Middleware[], - deprecated?: boolean - ): YargsInstance; + command: { + ( + cmd: string | string[], + description: CommandHandler['description'], + builder?: CommandBuilderDefinition | CommandBuilder, + handler?: CommandHandlerCallback, + commandMiddleware?: Middleware[], + deprecated?: boolean + ): YargsInstance; + (handler: CommandHandlerDefinition): YargsInstance; + }; + commands: YargsInstance['command']; commandDir(dir: string, opts?: RequireDirectoryOptions): YargsInstance; completion: { (cmd?: string, fn?: CompletionFunction): YargsInstance; From 6f3d65bce1d0494771bbfe82443f1587513a41ba Mon Sep 17 00:00:00 2001 From: OsmanAltun Date: Wed, 24 Feb 2021 13:28:37 +0100 Subject: [PATCH 06/15] npm run fix --- test/usage.cjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/usage.cjs b/test/usage.cjs index b5842ad89..26b7e0aea 100644 --- a/test/usage.cjs +++ b/test/usage.cjs @@ -2609,7 +2609,7 @@ describe('usage tests', () => { '', 'Options:', ' --version Show version number [boolean]', - ' -h, --help Show help [boolean]' + ' -h, --help Show help [boolean]', ]); }); }); From 2c6e184edb2bd3a0f1c2d89a675a8409013f6e07 Mon Sep 17 00:00:00 2001 From: OsmanAltun Date: Tue, 2 Mar 2021 22:49:19 +0100 Subject: [PATCH 07/15] Implement feedback and add tests --- lib/command.ts | 7 ++++ lib/usage.ts | 12 +++++- lib/yargs-factory.ts | 12 +++--- test/usage.cjs | 97 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 119 insertions(+), 9 deletions(-) diff --git a/lib/command.ts b/lib/command.ts index 81a7b50e5..873340471 100644 --- a/lib/command.ts +++ b/lib/command.ts @@ -258,9 +258,16 @@ export function command( ); aliases = (innerYargs.parsed as DetailedArguments).aliases; } else if (isCommandBuilderOptionDefinitions(builder)) { + // A null command indicates we are running the default command, + // if this is the case, we should show the root usage instructions + // rather than the usage instructions for the nested default command: + if (!command) yargs.getUsageInstance().freeze(); + // as a short hand, an object can instead be provided, specifying // the options that a command takes. const innerYargs = yargs.reset(parsed.aliases); + + if (!command) innerYargs.getUsageInstance().unfreeze(); if (shouldUpdateUsage(innerYargs)) { innerYargs .getUsageInstance() diff --git a/lib/usage.ts b/lib/usage.ts index d436f209f..93f982fa0 100644 --- a/lib/usage.ts +++ b/lib/usage.ts @@ -231,7 +231,12 @@ export function usage(yargs: YargsInstance, y18n: Y18N, shim: PlatformShim) { // your application's commands, i.e., non-option // arguments populated in '_'. - if (commands.length) { + // + // If there's only a single command, and it's the default command + // (represented by commands[0][2]) don't show command stanza: + // + // TODO(@bcoe): why isnt commands[0][2] an object with a named property? + if (commands.length > 1 || (commands.length === 1 && !commands[0][2])) { ui.div(__('Commands:')); const context = yargs.getContext(); @@ -703,7 +708,10 @@ export function usage(yargs: YargsInstance, y18n: Y18N, shim: PlatformShim) { }; self.unfreeze = function unfreeze() { const frozen = frozens.pop(); - assertNotStrictEqual(frozen, undefined, shim); + // In the case of running a defaultCommand, we reset + // usage early to ensure we receive the top level instructions. + // unfreezing again should just be a noop: + if (!frozen) return; ({ failMessage, failureOutput, diff --git a/lib/yargs-factory.ts b/lib/yargs-factory.ts index 57d129505..b866b75ef 100644 --- a/lib/yargs-factory.ts +++ b/lib/yargs-factory.ts @@ -1483,7 +1483,7 @@ function Yargs( commandIndex = 0, helpOnly = false ) { - let skipValidation = !!calledFromCommand; + let skipValidation = !!calledFromCommand || helpOnly; args = args || processArgs; options.__ = y18n.__; @@ -1550,10 +1550,8 @@ function Yargs( const handlerKeys = command.getCommands(); const requestCompletions = completion!.completionKey in argv; - const skipRecommendation = argv[helpOpt!] || requestCompletions; - const skipDefaultCommand = - skipRecommendation && - (handlerKeys.length > 1 || handlerKeys[0] !== '$0'); + const skipRecommendation = + argv[helpOpt!] || requestCompletions || helpOnly; if (argv._.length) { if (handlerKeys.length) { @@ -1584,7 +1582,7 @@ function Yargs( } // run the default command, if defined - if (command.hasDefaultCommand() && !skipDefaultCommand) { + if (command.hasDefaultCommand() && !skipRecommendation) { const innerArgv = command.runCommand( null, self, @@ -1617,7 +1615,7 @@ function Yargs( self.showCompletionScript(); self.exit(0); } - } else if (command.hasDefaultCommand() && !skipDefaultCommand) { + } else if (command.hasDefaultCommand() && !skipRecommendation) { const innerArgv = command.runCommand(null, self, parsed, 0, helpOnly); return self._postProcess( innerArgv, diff --git a/test/usage.cjs b/test/usage.cjs index 355455bbb..4742f5eef 100644 --- a/test/usage.cjs +++ b/test/usage.cjs @@ -4261,4 +4261,101 @@ describe('usage tests', () => { ' --small Packet size', ]); }); + + describe('single default command', () => { + const expected = [ + 'usage', + '', + 'Default command description', + '', + 'Options:', + ' --help Show help [boolean]', + ' --version Show version number [boolean]', + ]; + + it('should contain the expected output for --help', () => { + const r = checkUsage(() => + yargs('--help').command('*', 'Default command description').parse() + ); + + r.logs[0].split('\n').should.deep.equal(expected); + }); + + it('should contain the expected output for showhelp', () => { + const r = checkUsage(() => { + yargs.command('*', 'Default command description').parse(); + yargs.showHelp('log'); + }); + r.logs[0].split('\n').should.deep.equal(expected); + }); + + it('should contain the expected output for showHelp when called from within handler', () => { + const r = checkUsage(() => + yargs + .command('*', 'Default command description', {}, () => + yargs.showHelp('log') + ) + .parse('') + ); + r.logs[0].split('\n').should.deep.equal(expected); + }); + }); + + describe('multiple commands, including a default command', () => { + const expected = [ + 'Hello, world!', + '', + 'Commands:', + ' usage Default command description [default]', + ' usage foo Foo command description', + '', + 'Options:', + ' --help Show help [boolean]', + ' --version Show version number [boolean]', + ]; + + it('should contain the expected output for --help', () => { + const r = checkUsage(() => + yargs('--help') + .usage('Hello, world!') + .commands([ + {command: '*', desc: 'Default command description'}, + {command: 'foo', desc: 'Foo command description'}, + ]) + .parse() + ); + r.logs[0].split('\n').should.deep.equal(expected); + }); + + it('should contain the expected output for showHelp', () => { + const r = checkUsage(() => { + yargs + .usage('Hello, world!') + .commands([ + {command: '*', desc: 'Default command description'}, + {command: 'foo', desc: 'Foo command description'}, + ]) + .parse(); + yargs.showHelp('log'); + }); + r.logs[0].split('\n').should.deep.equal(expected); + }); + + it('should contain the expected output for showHelp when called from within handler', () => { + const r = checkUsage(() => + yargs + .usage('Hello, world!') + .commands([ + { + command: '*', + desc: 'Default command description', + handler: _ => yargs.showHelp('log'), + }, + {command: 'foo', desc: 'Foo command description'}, + ]) + .parse('') + ); + r.logs[0].split('\n').should.deep.equal(expected); + }); + }); }); From 4960f69e0014360557e6a493ca523441fc328f1e Mon Sep 17 00:00:00 2001 From: OsmanAltun Date: Tue, 2 Mar 2021 23:08:10 +0100 Subject: [PATCH 08/15] Revert "Fix help output with default command" This reverts commit 380782dd1a3af7226ff5ee70a888b7cd4a0c46ec. --- lib/command.ts | 49 ++++++++++++++++++------------------------------- test/usage.cjs | 5 ++--- 2 files changed, 20 insertions(+), 34 deletions(-) diff --git a/lib/command.ts b/lib/command.ts index 0054108e2..873340471 100644 --- a/lib/command.ts +++ b/lib/command.ts @@ -225,9 +225,6 @@ export function command( let numFiles = currentContext.files.length; const parentCommands = currentContext.commands.slice(); - const tmpCommands = yargs.getUsageInstance().getCommands(); - const tmpUsage = yargs.getUsageInstance().getUsage(); - // what does yargs look like after the builder is run? let innerArgv: Arguments | Promise = parsed.argv; let positionalMap: Dictionary = {}; @@ -242,20 +239,15 @@ export function command( const builderOutput = builder(yargs.reset(parsed.aliases)); const innerYargs = isYargsInstance(builderOutput) ? builderOutput : yargs; if (shouldUpdateUsage(innerYargs)) { - if (!command) { - tmpCommands.forEach(e => innerYargs.getUsageInstance().command(...e)); - tmpUsage.forEach(e => innerYargs.getUsageInstance().usage(...e)); - } else { - innerYargs - .getUsageInstance() - .usage( - usageFromParentCommandsCommandHandler( - parentCommands, - commandHandler - ), - commandHandler.description - ); - } + innerYargs + .getUsageInstance() + .usage( + usageFromParentCommandsCommandHandler( + parentCommands, + commandHandler + ), + commandHandler.description + ); } innerArgv = innerYargs._parseArgs( null, @@ -277,20 +269,15 @@ export function command( if (!command) innerYargs.getUsageInstance().unfreeze(); if (shouldUpdateUsage(innerYargs)) { - if (!command) { - tmpCommands.forEach(e => innerYargs.getUsageInstance().command(...e)); - tmpUsage.forEach(e => innerYargs.getUsageInstance().usage(...e)); - } else { - innerYargs - .getUsageInstance() - .usage( - usageFromParentCommandsCommandHandler( - parentCommands, - commandHandler - ), - commandHandler.description - ); - } + innerYargs + .getUsageInstance() + .usage( + usageFromParentCommandsCommandHandler( + parentCommands, + commandHandler + ), + commandHandler.description + ); } Object.keys(commandHandler.builder).forEach(key => { innerYargs.option(key, builder[key]); diff --git a/test/usage.cjs b/test/usage.cjs index bc34956ce..4742f5eef 100644 --- a/test/usage.cjs +++ b/test/usage.cjs @@ -2602,10 +2602,9 @@ describe('usage tests', () => { r.logs[0] .split('\n') .should.deep.equal([ - 'usage [command]', + 'usage', '', - 'Commands:', - ' usage do something [default]', + 'do something', '', 'Options:', ' --version Show version number [boolean]', From e7464e001546805b1d69e5a2e6514261885bd0d1 Mon Sep 17 00:00:00 2001 From: OsmanAltun Date: Wed, 3 Mar 2021 17:15:33 +0100 Subject: [PATCH 09/15] Move freeze to root function --- lib/command.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/command.ts b/lib/command.ts index 873340471..0729e1e5b 100644 --- a/lib/command.ts +++ b/lib/command.ts @@ -231,6 +231,11 @@ export function command( if (command) { currentContext.commands.push(command); currentContext.fullCommands.push(commandHandler.original); + } else { + // A null command indicates we are running the default command, + // if this is the case, we should show the root usage instructions + // rather than the usage instructions for the nested default command: + yargs.getUsageInstance().freeze(); } const builder = commandHandler.builder; if (isCommandBuilderCallback(builder)) { @@ -238,6 +243,7 @@ export function command( // up a yargs chain and possibly returns it. const builderOutput = builder(yargs.reset(parsed.aliases)); const innerYargs = isYargsInstance(builderOutput) ? builderOutput : yargs; + if (!command) innerYargs.getUsageInstance().unfreeze(); if (shouldUpdateUsage(innerYargs)) { innerYargs .getUsageInstance() @@ -258,11 +264,6 @@ export function command( ); aliases = (innerYargs.parsed as DetailedArguments).aliases; } else if (isCommandBuilderOptionDefinitions(builder)) { - // A null command indicates we are running the default command, - // if this is the case, we should show the root usage instructions - // rather than the usage instructions for the nested default command: - if (!command) yargs.getUsageInstance().freeze(); - // as a short hand, an object can instead be provided, specifying // the options that a command takes. const innerYargs = yargs.reset(parsed.aliases); From 2dea21d6bf7d1340ccf14a32f659b659a81a5535 Mon Sep 17 00:00:00 2001 From: OsmanAltun Date: Wed, 3 Mar 2021 17:57:35 +0100 Subject: [PATCH 10/15] Remove boiler --- lib/command.ts | 73 +++++++++++++++++--------------------------------- 1 file changed, 25 insertions(+), 48 deletions(-) diff --git a/lib/command.ts b/lib/command.ts index 0729e1e5b..38eeef755 100644 --- a/lib/command.ts +++ b/lib/command.ts @@ -238,60 +238,37 @@ export function command( yargs.getUsageInstance().freeze(); } const builder = commandHandler.builder; + + let innerYargs: YargsInstance; if (isCommandBuilderCallback(builder)) { - // a function can be provided, which builds - // up a yargs chain and possibly returns it. const builderOutput = builder(yargs.reset(parsed.aliases)); - const innerYargs = isYargsInstance(builderOutput) ? builderOutput : yargs; - if (!command) innerYargs.getUsageInstance().unfreeze(); - if (shouldUpdateUsage(innerYargs)) { - innerYargs - .getUsageInstance() - .usage( - usageFromParentCommandsCommandHandler( - parentCommands, - commandHandler - ), - commandHandler.description - ); - } - innerArgv = innerYargs._parseArgs( - null, - undefined, - true, - commandIndex, - helpOnly - ); - aliases = (innerYargs.parsed as DetailedArguments).aliases; - } else if (isCommandBuilderOptionDefinitions(builder)) { - // as a short hand, an object can instead be provided, specifying - // the options that a command takes. - const innerYargs = yargs.reset(parsed.aliases); - - if (!command) innerYargs.getUsageInstance().unfreeze(); - if (shouldUpdateUsage(innerYargs)) { - innerYargs - .getUsageInstance() - .usage( - usageFromParentCommandsCommandHandler( - parentCommands, - commandHandler - ), - commandHandler.description - ); - } + innerYargs = isYargsInstance(builderOutput) ? builderOutput : yargs; + } else { + innerYargs = yargs.reset(parsed.aliases); + } + + if (!command) innerYargs.getUsageInstance().unfreeze(); + if (shouldUpdateUsage(innerYargs)) { + innerYargs + .getUsageInstance() + .usage( + usageFromParentCommandsCommandHandler(parentCommands, commandHandler), + commandHandler.description + ); + } + if (isCommandBuilderOptionDefinitions(builder)) { Object.keys(commandHandler.builder).forEach(key => { innerYargs.option(key, builder[key]); }); - innerArgv = innerYargs._parseArgs( - null, - undefined, - true, - commandIndex, - helpOnly - ); - aliases = (innerYargs.parsed as DetailedArguments).aliases; } + innerArgv = innerYargs._parseArgs( + null, + undefined, + true, + commandIndex, + helpOnly + ); + aliases = (innerYargs.parsed as DetailedArguments).aliases; if (!yargs._hasOutput()) { positionalMap = populatePositionals( From 5619c9e42bc0c596cd3c0e771e749d384b8812e0 Mon Sep 17 00:00:00 2001 From: OsmanAltun Date: Wed, 3 Mar 2021 18:05:54 +0100 Subject: [PATCH 11/15] Small change --- lib/command.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/lib/command.ts b/lib/command.ts index 38eeef755..e5ca581d2 100644 --- a/lib/command.ts +++ b/lib/command.ts @@ -245,6 +245,9 @@ export function command( innerYargs = isYargsInstance(builderOutput) ? builderOutput : yargs; } else { innerYargs = yargs.reset(parsed.aliases); + Object.keys(commandHandler.builder).forEach(key => { + innerYargs.option(key, builder[key]); + }); } if (!command) innerYargs.getUsageInstance().unfreeze(); @@ -256,11 +259,6 @@ export function command( commandHandler.description ); } - if (isCommandBuilderOptionDefinitions(builder)) { - Object.keys(commandHandler.builder).forEach(key => { - innerYargs.option(key, builder[key]); - }); - } innerArgv = innerYargs._parseArgs( null, undefined, From 225742663a26c3ef915e80aca925ed01493eba31 Mon Sep 17 00:00:00 2001 From: bcoe Date: Thu, 4 Mar 2021 13:08:17 -0800 Subject: [PATCH 12/15] refactor: call unfreeze after reset --- lib/command.ts | 81 +++++++++++++++++++++++++++++++++----------------- 1 file changed, 53 insertions(+), 28 deletions(-) diff --git a/lib/command.ts b/lib/command.ts index e5ca581d2..8cf3c2db0 100644 --- a/lib/command.ts +++ b/lib/command.ts @@ -231,43 +231,68 @@ export function command( if (command) { currentContext.commands.push(command); currentContext.fullCommands.push(commandHandler.original); - } else { - // A null command indicates we are running the default command, - // if this is the case, we should show the root usage instructions - // rather than the usage instructions for the nested default command: - yargs.getUsageInstance().freeze(); } const builder = commandHandler.builder; - - let innerYargs: YargsInstance; if (isCommandBuilderCallback(builder)) { + // a function can be provided, which builds + // up a yargs chain and possibly returns it. const builderOutput = builder(yargs.reset(parsed.aliases)); - innerYargs = isYargsInstance(builderOutput) ? builderOutput : yargs; - } else { - innerYargs = yargs.reset(parsed.aliases); + const innerYargs = isYargsInstance(builderOutput) ? builderOutput : yargs; + // A null command indicates we are running the default command, + // if this is the case, we should show the root usage instructions + // rather than the usage instructions for the nested default command: + if (!command) innerYargs.getUsageInstance().unfreeze(); + if (shouldUpdateUsage(innerYargs)) { + innerYargs + .getUsageInstance() + .usage( + usageFromParentCommandsCommandHandler( + parentCommands, + commandHandler + ), + commandHandler.description + ); + } + innerArgv = innerYargs._parseArgs( + null, + undefined, + true, + commandIndex, + helpOnly + ); + aliases = (innerYargs.parsed as DetailedArguments).aliases; + } else if (isCommandBuilderOptionDefinitions(builder)) { + // as a short hand, an object can instead be provided, specifying + // the options that a command takes. + const innerYargs = yargs.reset(parsed.aliases); + // A null command indicates we are running the default command, + // if this is the case, we should show the root usage instructions + // rather than the usage instructions for the nested default command: + if (!command) innerYargs.getUsageInstance().unfreeze(); + if (shouldUpdateUsage(innerYargs)) { + innerYargs + .getUsageInstance() + .usage( + usageFromParentCommandsCommandHandler( + parentCommands, + commandHandler + ), + commandHandler.description + ); + } Object.keys(commandHandler.builder).forEach(key => { innerYargs.option(key, builder[key]); }); + innerArgv = innerYargs._parseArgs( + null, + undefined, + true, + commandIndex, + helpOnly + ); + aliases = (innerYargs.parsed as DetailedArguments).aliases; } - if (!command) innerYargs.getUsageInstance().unfreeze(); - if (shouldUpdateUsage(innerYargs)) { - innerYargs - .getUsageInstance() - .usage( - usageFromParentCommandsCommandHandler(parentCommands, commandHandler), - commandHandler.description - ); - } - innerArgv = innerYargs._parseArgs( - null, - undefined, - true, - commandIndex, - helpOnly - ); - aliases = (innerYargs.parsed as DetailedArguments).aliases; - if (!yargs._hasOutput()) { positionalMap = populatePositionals( commandHandler, From 056d1754d9ab34ca0aa16445cc5f5f1f2b081d35 Mon Sep 17 00:00:00 2001 From: bcoe Date: Thu, 4 Mar 2021 14:03:14 -0800 Subject: [PATCH 13/15] test: add a couple test cases; avoid singleton --- test/usage.cjs | 230 +++++++++++++++++++++++++++++++++---------------- 1 file changed, 155 insertions(+), 75 deletions(-) diff --git a/test/usage.cjs b/test/usage.cjs index 4742f5eef..461d1be7f 100644 --- a/test/usage.cjs +++ b/test/usage.cjs @@ -4262,100 +4262,180 @@ describe('usage tests', () => { ]); }); - describe('single default command', () => { - const expected = [ - 'usage', - '', - 'Default command description', - '', - 'Options:', - ' --help Show help [boolean]', - ' --version Show version number [boolean]', - ]; - - it('should contain the expected output for --help', () => { - const r = checkUsage(() => - yargs('--help').command('*', 'Default command description').parse() - ); + // Refs: https://github.com/yargs/yargs/pull/1826 + describe('usage for default command', () => { + describe('default only', () => { + const expected = [ + 'usage', + '', + 'Default command description', + '', + 'Options:', + ' --help Show help [boolean]', + ' --version Show version number [boolean]', + ]; - r.logs[0].split('\n').should.deep.equal(expected); - }); + it('should contain the expected output for --help', () => { + const r = checkUsage(() => + yargs('--help') + .scriptName('usage') + .command('*', 'Default command description') + .parse() + ); - it('should contain the expected output for showhelp', () => { - const r = checkUsage(() => { - yargs.command('*', 'Default command description').parse(); - yargs.showHelp('log'); + r.logs[0].split('\n').should.deep.equal(expected); }); - r.logs[0].split('\n').should.deep.equal(expected); - }); - it('should contain the expected output for showHelp when called from within handler', () => { - const r = checkUsage(() => - yargs - .command('*', 'Default command description', {}, () => - yargs.showHelp('log') - ) - .parse('') - ); - r.logs[0].split('\n').should.deep.equal(expected); - }); - }); + it('should contain the expected output for showhelp', () => { + const r = checkUsage(() => { + const y = yargs() + .scriptName('usage') + .command('*', 'Default command description'); + y.showHelp('log'); + }); + r.logs[0].split('\n').should.deep.equal(expected); + }); - describe('multiple commands, including a default command', () => { - const expected = [ - 'Hello, world!', - '', - 'Commands:', - ' usage Default command description [default]', - ' usage foo Foo command description', - '', - 'Options:', - ' --help Show help [boolean]', - ' --version Show version number [boolean]', - ]; - - it('should contain the expected output for --help', () => { - const r = checkUsage(() => - yargs('--help') - .usage('Hello, world!') - .commands([ - {command: '*', desc: 'Default command description'}, - {command: 'foo', desc: 'Foo command description'}, - ]) - .parse() - ); - r.logs[0].split('\n').should.deep.equal(expected); + it('should contain the expected output for getHelp', async () => { + const y = yargs() + .scriptName('usage') + .command('*', 'Default command description'); + const help = await y.getHelp(); + help.split('\n').should.deep.equal(expected); + }); + + it('should contain the expected output for getHelp when called from within handler', async () => { + let help = ''; + const y = yargs() + .scriptName('usage') + .command('*', 'Default command description', {}, async () => { + help = await y.getHelp(); + }); + await y.parse(); + help.split('\n').should.deep.equal(expected); + }); + + it('should contain the expected output for showHelp when called from within handler', () => { + const r = checkUsage(() => + yargs() + .scriptName('usage') + .command('*', 'Default command description', {}, () => + yargs.showHelp('log') + ) + .parse('') + ); + r.logs[0].split('\n').should.deep.equal(expected); + }); + + it('should contain the expected output for showHelp, when exception occurs', () => { + const r = checkUsage(() => + yargs() + .scriptName('usage') + .command('*', 'Default command description', {}, () => + yargs.showHelp('log') + ) + .check(() => { + return false; + }) + .parse('') + ); + r.errors[0].split('\n').should.deep.equal(expected); + }); }); - it('should contain the expected output for showHelp', () => { - const r = checkUsage(() => { - yargs + describe('multiple', () => { + const expected = [ + 'Hello, world!', + '', + 'Commands:', + ' usage Default command description [default]', + ' usage foo Foo command description', + '', + 'Options:', + ' --help Show help [boolean]', + ' --version Show version number [boolean]', + ]; + it('should contain the expected output for --help', () => { + const r = checkUsage(() => + yargs('--help') + .scriptName('usage') + .usage('Hello, world!') + .commands([ + {command: '*', desc: 'Default command description'}, + {command: 'foo', desc: 'Foo command description'}, + ]) + .parse() + ); + r.logs[0].split('\n').should.deep.equal(expected); + }); + + it('should contain the expected output for showHelp', () => { + const r = checkUsage(() => { + yargs() + .scriptName('usage') + .usage('Hello, world!') + .commands([ + {command: '*', desc: 'Default command description'}, + {command: 'foo', desc: 'Foo command description'}, + ]) + .parse(); + yargs.showHelp('log'); + }); + r.logs[0].split('\n').should.deep.equal(expected); + }); + + it('should contain the expected output for showHelp when called from within handler', () => { + const r = checkUsage(() => + yargs() + .scriptName('usage') + .usage('Hello, world!') + .commands([ + { + command: '*', + desc: 'Default command description', + handler: _ => yargs.showHelp('log'), + }, + {command: 'foo', desc: 'Foo command description'}, + ]) + .parse('') + ); + r.logs[0].split('\n').should.deep.equal(expected); + }); + + it('should contain the expected output for getHelp', async () => { + const y = yargs() + .scriptName('usage') .usage('Hello, world!') .commands([ - {command: '*', desc: 'Default command description'}, + { + command: '*', + desc: 'Default command description', + handler: () => {}, + }, {command: 'foo', desc: 'Foo command description'}, - ]) - .parse(); - yargs.showHelp('log'); + ]); + const help = await y.getHelp(); + help.split('\n').should.deep.equal(expected); }); - r.logs[0].split('\n').should.deep.equal(expected); - }); - it('should contain the expected output for showHelp when called from within handler', () => { - const r = checkUsage(() => - yargs + it('should contain the expected output for getHelp when called from within handler', async () => { + let help = ''; + const y = yargs() + .scriptName('usage') .usage('Hello, world!') .commands([ { command: '*', desc: 'Default command description', - handler: _ => yargs.showHelp('log'), + handler: async () => { + help = await y.getHelp(); + }, }, {command: 'foo', desc: 'Foo command description'}, - ]) - .parse('') - ); - r.logs[0].split('\n').should.deep.equal(expected); + ]); + await y.parse(); + help.split('\n').should.deep.equal(expected); + }); }); }); }); From 7cd9772c8e2d2f240e8c112b318edb050a1218ba Mon Sep 17 00:00:00 2001 From: bcoe Date: Thu, 4 Mar 2021 14:07:50 -0800 Subject: [PATCH 14/15] chore: revert unrelated change --- lib/usage.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/lib/usage.ts b/lib/usage.ts index 19e3ca656..93f982fa0 100644 --- a/lib/usage.ts +++ b/lib/usage.ts @@ -540,14 +540,10 @@ export function usage(yargs: YargsInstance, y18n: Y18N, shim: PlatformShim) { (Object.keys(options.alias) || []).forEach(key => { options.alias[key].forEach(alias => { // copy descriptions. - if (descriptions[alias]) - self.describe(key, descriptions[key] || descriptions[alias]); + if (descriptions[alias]) self.describe(key, descriptions[alias]); // copy demanded. if (alias in demandedOptions) - yargs.demandOption( - key, - demandedOptions[key] || demandedOptions[alias] - ); + yargs.demandOption(key, demandedOptions[alias]); // type messages. if (~options.boolean.indexOf(alias)) yargs.boolean(key); if (~options.count.indexOf(alias)) yargs.count(key); From 6e2da353707f7a7869cebe0bbe879cc3f9520e13 Mon Sep 17 00:00:00 2001 From: bcoe Date: Fri, 5 Mar 2021 14:26:46 -0800 Subject: [PATCH 15/15] fix: .argv should use same code path --- lib/yargs-factory.ts | 11 ++++++----- test/usage.cjs | 28 ++++++++++++++-------------- 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/lib/yargs-factory.ts b/lib/yargs-factory.ts index b866b75ef..f41c9c8b3 100644 --- a/lib/yargs-factory.ts +++ b/lib/yargs-factory.ts @@ -934,12 +934,11 @@ function Yargs( [args, shortCircuit, _parseFn], arguments.length ); - freeze(); + freeze(); // Push current state of parser onto stack. if (typeof args === 'undefined') { const argv = self._parseArgs(processArgs); const tmpParsed = self.parsed; - unfreeze(); - // TODO: remove this compatibility hack when we release yargs@15.x: + unfreeze(); // Pop the stack. self.parsed = tmpParsed; return argv; } @@ -967,7 +966,7 @@ function Yargs( const parsed = self._parseArgs(args, !!shortCircuit); completion!.setParsed(self.parsed as DetailedArguments); if (parseFn) parseFn(exitError, parsed, output); - unfreeze(); + unfreeze(); // Pop the stack. return parsed; }; @@ -1472,7 +1471,9 @@ function Yargs( }; Object.defineProperty(self, 'argv', { - get: () => self._parseArgs(processArgs), + get: () => { + return self.parse(); + }, enumerable: true, }); diff --git a/test/usage.cjs b/test/usage.cjs index 461d1be7f..f09b88c41 100644 --- a/test/usage.cjs +++ b/test/usage.cjs @@ -4385,19 +4385,19 @@ describe('usage tests', () => { }); it('should contain the expected output for showHelp when called from within handler', () => { - const r = checkUsage(() => - yargs() - .scriptName('usage') - .usage('Hello, world!') - .commands([ - { - command: '*', - desc: 'Default command description', - handler: _ => yargs.showHelp('log'), - }, - {command: 'foo', desc: 'Foo command description'}, - ]) - .parse('') + const r = checkUsage( + () => + yargs() + .scriptName('usage') + .usage('Hello, world!') + .commands([ + { + command: '*', + desc: 'Default command description', + handler: _ => yargs.showHelp('log'), + }, + {command: 'foo', desc: 'Foo command description'}, + ]).argv ); r.logs[0].split('\n').should.deep.equal(expected); }); @@ -4433,7 +4433,7 @@ describe('usage tests', () => { }, {command: 'foo', desc: 'Foo command description'}, ]); - await y.parse(); + await y.argv; help.split('\n').should.deep.equal(expected); }); });