Skip to content

Commit

Permalink
Add opts to addCommand, and rename noHelp to hidden (#1232)
Browse files Browse the repository at this point in the history
  • Loading branch information
shadowspawn committed Mar 30, 2020
1 parent 8ec3e7f commit b5d95ee
Show file tree
Hide file tree
Showing 7 changed files with 74 additions and 13 deletions.
2 changes: 1 addition & 1 deletion Readme.md
Expand Up @@ -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
Expand Down
13 changes: 9 additions & 4 deletions index.js
Expand Up @@ -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';
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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.
Expand All @@ -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;
Expand Down Expand Up @@ -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);
Expand Down
38 changes: 36 additions & 2 deletions tests/command.default.test.js
Expand Up @@ -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();
Expand Down
19 changes: 19 additions & 0 deletions tests/command.help.test.js
Expand Up @@ -88,10 +88,29 @@ 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
.command('secret', 'secret description', { noHelp: true });
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');
});
2 changes: 1 addition & 1 deletion tests/fixtures/pm
Expand Up @@ -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')
Expand Down
4 changes: 2 additions & 2 deletions typings/commander-tests.ts
Expand Up @@ -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'));
Expand Down
9 changes: 6 additions & 3 deletions typings/index.d.ts
Expand Up @@ -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.
Expand All @@ -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.
Expand Down Expand Up @@ -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;
}

Expand Down

0 comments on commit b5d95ee

Please sign in to comment.