diff --git a/Readme.md b/Readme.md index fbac86321..fcb995ab3 100644 --- a/Readme.md +++ b/Readme.md @@ -330,7 +330,7 @@ program .addCommand(build.makeBuildCommand()); ``` -Configuration options can be passed with the call to `.command()`. Specifying `true` for `opts.noHelp` will remove the command from the generated help output. Specifying `true` for `opts.isDefault` will run the subcommand if no other subcommand is specified ([example](./examples/defaultCommand.js)). +Configuration options can be passed with the call to `.command()` and `.addCommand()`. Specifying `true` for `opts.hidden` will remove the command from the generated help output. Specifying `true` for `opts.isDefault` will run the subcommand if no other subcommand is specified ([example](./examples/defaultCommand.js)). ### Specify the argument syntax diff --git a/index.js b/index.js index 8b6448fbb..f58565586 100644 --- a/index.js +++ b/index.js @@ -119,7 +119,7 @@ class Command extends EventEmitter { this._exitCallback = null; this._alias = null; - this._noHelp = false; + this._hidden = false; this._helpFlags = '-h, --help'; this._helpDescription = 'display help for command'; this._helpShortFlag = '-h'; @@ -174,7 +174,7 @@ class Command extends EventEmitter { } if (opts.isDefault) this._defaultCommandName = cmd._name; - cmd._noHelp = !!opts.noHelp; + cmd._hidden = !!(opts.noHelp || opts.hidden); cmd._helpFlags = this._helpFlags; cmd._helpDescription = this._helpDescription; cmd._helpShortFlag = this._helpShortFlag; @@ -216,11 +216,12 @@ class Command extends EventEmitter { * See .command() for creating an attached subcommand which inherits settings from its parent. * * @param {Command} cmd - new subcommand + * @param {Object} [opts] - configuration options * @return {Command} `this` command for chaining * @api public */ - addCommand(cmd) { + addCommand(cmd, opts) { if (!cmd._name) throw new Error('Command passed to .addCommand() must have a name'); // To keep things simple, block automatic name generation for deeply nested executables. @@ -235,6 +236,10 @@ class Command extends EventEmitter { } checkExplicitNames(cmd.commands); + opts = opts || {}; + if (opts.isDefault) this._defaultCommandName = cmd._name; + if (opts.noHelp || opts.hidden) cmd._hidden = true; // modifying passed command due to existing implementation + this.commands.push(cmd); cmd.parent = this; return this; @@ -1275,7 +1280,7 @@ class Command extends EventEmitter { prepareCommands() { const commandDetails = this.commands.filter((cmd) => { - return !cmd._noHelp; + return !cmd._hidden; }).map((cmd) => { const args = cmd._args.map((arg) => { return humanReadableArgName(arg); diff --git a/tests/command.default.test.js b/tests/command.default.test.js index 80edb0616..6f1d79210 100644 --- a/tests/command.default.test.js +++ b/tests/command.default.test.js @@ -44,13 +44,47 @@ describe('default action command', () => { expect(actionMock).toHaveBeenCalled(); }); - test('when default subcommand and unrecognised argument then call default with argument', () => { + test('when default subcommand and unrecognised argument then call default', () => { const { program, actionMock } = makeProgram(); program.parse('node test.js an-argument'.split(' ')); expect(actionMock).toHaveBeenCalled(); }); - test('when default subcommand and unrecognised option then call default with option', () => { + test('when default subcommand and unrecognised option then call default', () => { + const { program, actionMock } = makeProgram(); + program.parse('node test.js --an-option'.split(' ')); + expect(actionMock).toHaveBeenCalled(); + }); +}); + +describe('default added command', () => { + function makeProgram() { + const actionMock = jest.fn(); + const defaultCmd = new commander.Command('default') + .allowUnknownOption() + .action(actionMock); + + const program = new commander.Command(); + program + .command('other'); + program + .addCommand(defaultCmd, { isDefault: true }); + return { program, actionMock }; + } + + test('when default subcommand and no command then call default', () => { + const { program, actionMock } = makeProgram(); + program.parse('node test.js'.split(' ')); + expect(actionMock).toHaveBeenCalled(); + }); + + test('when default subcommand and unrecognised argument then call default', () => { + const { program, actionMock } = makeProgram(); + program.parse('node test.js an-argument'.split(' ')); + expect(actionMock).toHaveBeenCalled(); + }); + + test('when default subcommand and unrecognised option then call default', () => { const { program, actionMock } = makeProgram(); program.parse('node test.js --an-option'.split(' ')); expect(actionMock).toHaveBeenCalled(); diff --git a/tests/command.help.test.js b/tests/command.help.test.js index 5ed1daa42..f484254a7 100644 --- a/tests/command.help.test.js +++ b/tests/command.help.test.js @@ -88,6 +88,7 @@ test('when call outputHelp(cb) then display cb output', () => { writeSpy.mockClear(); }); +// noHelp is now named hidden, not officially deprecated yet test('when command sets noHelp then not displayed in helpInformation', () => { const program = new commander.Command(); program @@ -95,3 +96,21 @@ test('when command sets noHelp then not displayed in helpInformation', () => { const helpInformation = program.helpInformation(); expect(helpInformation).not.toMatch('secret'); }); + +test('when command sets hidden then not displayed in helpInformation', () => { + const program = new commander.Command(); + program + .command('secret', 'secret description', { hidden: true }); + const helpInformation = program.helpInformation(); + expect(helpInformation).not.toMatch('secret'); +}); + +test('when addCommand with hidden:true then not displayed in helpInformation', () => { + const secretCmd = new commander.Command('secret'); + + const program = new commander.Command(); + program + .addCommand(secretCmd, { hidden: true }); + const helpInformation = program.helpInformation(); + expect(helpInformation).not.toMatch('secret'); +}); diff --git a/tests/fixtures/pm b/tests/fixtures/pm index 41ca23bd1..40fbd396d 100755 --- a/tests/fixtures/pm +++ b/tests/fixtures/pm @@ -11,7 +11,7 @@ program .command('list', 'list packages installed').alias('lst') .command('listen', 'listen for supported signal events').alias('l') .command('publish', 'publish or update package').alias('p') - .command('default', 'default command', { noHelp: true, isDefault: true }) + .command('default', 'default command', { hidden: true, isDefault: true }) .command('specifyInstall', 'specify install subcommand', { executableFile: 'pm-install' }) .command('specifyPublish', 'specify publish subcommand', { executableFile: 'pm-publish' }) .command('silent', 'silently succeed') diff --git a/typings/commander-tests.ts b/typings/commander-tests.ts index cd9067103..5ceda8e13 100644 --- a/typings/commander-tests.ts +++ b/typings/commander-tests.ts @@ -45,9 +45,9 @@ const versionThis3: commander.Command = program.version('1.2.3', '-r,--revision' // command (and CommandOptions) const commandNew1: commander.Command = program.command('action'); -const commandNew2: commander.Command = program.command('action', { isDefault: true, noHelp: true }); +const commandNew2: commander.Command = program.command('action', { isDefault: true, hidden: true, noHelp: true }); const commandThis1: commander.Command = program.command('exec', 'exec description'); -const commandThis2: commander.Command = program.command('exec', 'exec description', { isDefault: true, noHelp: true, executableFile: 'foo' }); +const commandThis2: commander.Command = program.command('exec', 'exec description', { isDefault: true, hidden: true, noHelp: true, executableFile: 'foo' }); // addCommand const addCommandThis: commander.Command = program.addCommand(new commander.Command('abc')); diff --git a/typings/index.d.ts b/typings/index.d.ts index 69c432e07..d6b4eed41 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -83,7 +83,7 @@ declare namespace commander { * @param opts - configuration options * @returns `this` command for chaining */ - command(nameAndArgs: string, description: string, opts?: commander.CommandOptions): this; + command(nameAndArgs: string, description: string, opts?: commander.ExecutableCommandOptions): this; /** * Factory routine to create a new unattached command. @@ -100,7 +100,7 @@ declare namespace commander { * * @returns `this` command for chaining */ - addCommand(cmd: Command): this; + addCommand(cmd: Command, opts?: CommandOptions): this; /** * Define argument syntax for command. @@ -343,8 +343,11 @@ declare namespace commander { type CommandConstructor = new (name?: string) => Command; interface CommandOptions { - noHelp?: boolean; + noHelp?: boolean; // old name for hidden + hidden?: boolean; isDefault?: boolean; + } + interface ExecutableCommandOptions extends CommandOptions { executableFile?: string; }