Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add configuration options to addCommand #1232

Merged
merged 2 commits into from Mar 30, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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} parent 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 top level command for chaining more command definitions
*/
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 parent command for chaining
*/
addCommand(cmd: Command): this;
addCommand(cmd: Command, opts?: CommandOptions): this;

/**
* Define argument syntax for the top-level 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