Skip to content

Commit 6b2e42c

Browse files
authored
Allow disabling the built-in help option (#1325)
* Allow disabling the built-in help option * Suppress Options section of help if no options * Fix suppressed options * Leave [options] out of usage if no options * Expand usage tests
1 parent a71d592 commit 6b2e42c

7 files changed

+146
-18
lines changed

Readme.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -559,7 +559,7 @@ from `--help` listeners.)
559559
560560
### .helpOption(flags, description)
561561
562-
Override the default help flags and description.
562+
Override the default help flags and description. Pass false to disable the built-in help option.
563563
564564
```js
565565
program

index.js

+27-14
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ class Command extends EventEmitter {
129129
this._aliases = [];
130130

131131
this._hidden = false;
132+
this._hasHelpOption = true;
132133
this._helpFlags = '-h, --help';
133134
this._helpDescription = 'display help for command';
134135
this._helpShortFlag = '-h';
@@ -184,6 +185,7 @@ class Command extends EventEmitter {
184185
if (opts.isDefault) this._defaultCommandName = cmd._name;
185186

186187
cmd._hidden = !!(opts.noHelp || opts.hidden);
188+
cmd._hasHelpOption = this._hasHelpOption;
187189
cmd._helpFlags = this._helpFlags;
188190
cmd._helpDescription = this._helpDescription;
189191
cmd._helpShortFlag = this._helpShortFlag;
@@ -1236,7 +1238,8 @@ Read more on https://git.io/JJc0W`);
12361238
partCommands.unshift(parentCmd.name());
12371239
}
12381240
const fullCommand = partCommands.join(' ');
1239-
const message = `error: unknown command '${this.args[0]}'. See '${fullCommand} ${this._helpLongFlag}'.`;
1241+
const message = `error: unknown command '${this.args[0]}'.` +
1242+
(this._hasHelpOption ? ` See '${fullCommand} ${this._helpLongFlag}'.` : '');
12401243
console.error(message);
12411244
this._exit(1, 'commander.unknownCommand', message);
12421245
};
@@ -1345,9 +1348,11 @@ Read more on https://git.io/JJc0W`);
13451348
const args = this._args.map((arg) => {
13461349
return humanReadableArgName(arg);
13471350
});
1348-
return '[options]' +
1349-
(this.commands.length ? ' [command]' : '') +
1350-
(this._args.length ? ' ' + args.join(' ') : '');
1351+
return [].concat(
1352+
(this.options.length || this._hasHelpOption ? '[options]' : []),
1353+
(this.commands.length ? '[command]' : []),
1354+
(this._args.length ? args : [])
1355+
).join(' ');
13511356
}
13521357

13531358
this._usage = str;
@@ -1490,8 +1495,8 @@ Read more on https://git.io/JJc0W`);
14901495
});
14911496

14921497
// Implicit help
1493-
const showShortHelpFlag = this._helpShortFlag && !this._findOption(this._helpShortFlag);
1494-
const showLongHelpFlag = !this._findOption(this._helpLongFlag);
1498+
const showShortHelpFlag = this._hasHelpOption && this._helpShortFlag && !this._findOption(this._helpShortFlag);
1499+
const showLongHelpFlag = this._hasHelpOption && !this._findOption(this._helpLongFlag);
14951500
if (showShortHelpFlag || showLongHelpFlag) {
14961501
let helpFlags = this._helpFlags;
14971502
if (!showShortHelpFlag) {
@@ -1577,11 +1582,14 @@ Read more on https://git.io/JJc0W`);
15771582
const commandHelp = this.commandHelp();
15781583
if (commandHelp) cmds = [commandHelp];
15791584

1580-
const options = [
1581-
'Options:',
1582-
'' + this.optionHelp().replace(/^/gm, ' '),
1583-
''
1584-
];
1585+
let options = [];
1586+
if (this._hasHelpOption || this.options.length > 0) {
1587+
options = [
1588+
'Options:',
1589+
'' + this.optionHelp().replace(/^/gm, ' '),
1590+
''
1591+
];
1592+
}
15851593

15861594
return usage
15871595
.concat(desc)
@@ -1615,15 +1623,20 @@ Read more on https://git.io/JJc0W`);
16151623

16161624
/**
16171625
* You can pass in flags and a description to override the help
1618-
* flags and help description for your command.
1626+
* flags and help description for your command. Pass in false to
1627+
* disable the built-in help option.
16191628
*
1620-
* @param {string} [flags]
1629+
* @param {string | boolean} [flags]
16211630
* @param {string} [description]
16221631
* @return {Command} `this` command for chaining
16231632
* @api public
16241633
*/
16251634

16261635
helpOption(flags, description) {
1636+
if (typeof flags === 'boolean') {
1637+
this._hasHelpOption = flags;
1638+
return this;
1639+
}
16271640
this._helpFlags = flags || this._helpFlags;
16281641
this._helpDescription = description || this._helpDescription;
16291642

@@ -1756,7 +1769,7 @@ function optionalWrap(str, width, indent) {
17561769
*/
17571770

17581771
function outputHelpIfRequested(cmd, args) {
1759-
const helpOption = args.find(arg => arg === cmd._helpLongFlag || arg === cmd._helpShortFlag);
1772+
const helpOption = cmd._hasHelpOption && args.find(arg => arg === cmd._helpLongFlag || arg === cmd._helpShortFlag);
17601773
if (helpOption) {
17611774
cmd.outputHelp();
17621775
// (Do not have all displayed text available so only passing placeholder.)

tests/command.help.test.js

+9
Original file line numberDiff line numberDiff line change
@@ -130,3 +130,12 @@ test('when both help flags masked then not displayed in helpInformation', () =>
130130
const helpInformation = program.helpInformation();
131131
expect(helpInformation).not.toMatch('display help');
132132
});
133+
134+
test('when no options then Options not includes in helpInformation', () => {
135+
const program = new commander.Command();
136+
// No custom options, no version option, no help option
137+
program
138+
.helpOption(false);
139+
const helpInformation = program.helpInformation();
140+
expect(helpInformation).not.toMatch('Options');
141+
});

tests/command.helpOption.test.js

+52-1
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,22 @@ const commander = require('../');
22

33
describe('helpOption', () => {
44
let writeSpy;
5+
let consoleErrorSpy;
56

67
beforeAll(() => {
7-
// Optional. Suppress normal output to keep test output clean.
8+
// Optional. Suppress expected output to keep test output clean.
89
writeSpy = jest.spyOn(process.stdout, 'write').mockImplementation(() => { });
10+
consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => { });
911
});
1012

1113
afterEach(() => {
1214
writeSpy.mockClear();
15+
consoleErrorSpy.mockClear();
1316
});
1417

1518
afterAll(() => {
1619
writeSpy.mockRestore();
20+
consoleErrorSpy.mockRestore();
1721
});
1822

1923
test('when helpOption has custom flags then custom short flag invokes help', () => {
@@ -63,4 +67,51 @@ describe('helpOption', () => {
6367
const helpInformation = program.helpInformation();
6468
expect(helpInformation).toMatch(/-C,--custom-help +custom help output/);
6569
});
70+
71+
test('when helpOption(false) then helpInformation does not include --help', () => {
72+
const program = new commander.Command();
73+
program
74+
.helpOption(false);
75+
const helpInformation = program.helpInformation();
76+
expect(helpInformation).not.toMatch('--help');
77+
});
78+
79+
test('when helpOption(false) then --help is an unknown option', () => {
80+
const program = new commander.Command();
81+
program
82+
.exitOverride()
83+
.helpOption(false);
84+
let caughtErr;
85+
try {
86+
program.parse(['--help'], { from: 'user' });
87+
} catch (err) {
88+
caughtErr = err;
89+
}
90+
expect(caughtErr.code).toBe('commander.unknownOption');
91+
});
92+
93+
test('when helpOption(false) then -h is an unknown option', () => {
94+
const program = new commander.Command();
95+
program
96+
.exitOverride()
97+
.helpOption(false);
98+
let caughtErr;
99+
try {
100+
program.parse(['-h'], { from: 'user' });
101+
} catch (err) {
102+
caughtErr = err;
103+
}
104+
expect(caughtErr.code).toBe('commander.unknownOption');
105+
});
106+
107+
test('when helpOption(false) then unknown command error does not suggest --help', () => {
108+
const program = new commander.Command();
109+
program
110+
.exitOverride()
111+
.helpOption(false)
112+
.command('foo');
113+
expect(() => {
114+
program.parse(['UNKNOWN'], { from: 'user' });
115+
}).toThrow("error: unknown command 'UNKNOWN'.");
116+
});
66117
});

tests/command.usage.test.js

+53
Original file line numberDiff line numberDiff line change
@@ -44,3 +44,56 @@ test('when custom usage and check subcommand help then starts with custom usage
4444

4545
expect(helpInformation).toMatch(new RegExp(`^Usage: test info ${myUsage}`));
4646
});
47+
48+
test('when has option then [options] included in usage', () => {
49+
const program = new commander.Command();
50+
51+
program
52+
.option('--foo');
53+
54+
expect(program.usage()).toMatch('[options]');
55+
});
56+
57+
test('when no options then [options] not included in usage', () => {
58+
const program = new commander.Command();
59+
60+
program
61+
.helpOption(false);
62+
63+
expect(program.usage()).not.toMatch('[options]');
64+
});
65+
66+
test('when has command then [command] included in usage', () => {
67+
const program = new commander.Command();
68+
69+
program
70+
.command('foo');
71+
72+
expect(program.usage()).toMatch('[command]');
73+
});
74+
75+
test('when no commands then [command] not included in usage', () => {
76+
const program = new commander.Command();
77+
78+
expect(program.usage()).not.toMatch('[command]');
79+
});
80+
81+
test('when arguments then arguments included in usage', () => {
82+
const program = new commander.Command();
83+
84+
program
85+
.arguments('<file>');
86+
87+
expect(program.usage()).toMatch('<file>');
88+
});
89+
90+
test('when options and command and arguments then all three included in usage', () => {
91+
const program = new commander.Command();
92+
93+
program
94+
.arguments('<file>')
95+
.option('--alpha')
96+
.command('beta');
97+
98+
expect(program.usage()).toEqual('[options] [command] <file>');
99+
});

typings/commander-tests.ts

+1
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,7 @@ const helpInformnationValue: string = program.helpInformation();
196196
const helpOptionThis1: commander.Command = program.helpOption('-h,--help');
197197
const helpOptionThis2: commander.Command = program.helpOption('-h,--help', 'custom description');
198198
const helpOptionThis3: commander.Command = program.helpOption(undefined, 'custom description');
199+
const helpOptionThis4: commander.Command = program.helpOption(false);
199200

200201
// on
201202
const onThis: commander.Command = program.on('--help', () => {

typings/index.d.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -335,9 +335,10 @@ declare namespace commander {
335335

336336
/**
337337
* You can pass in flags and a description to override the help
338-
* flags and help description for your command.
338+
* flags and help description for your command. Pass in false
339+
* to disable the built-in help option.
339340
*/
340-
helpOption(flags?: string, description?: string): this;
341+
helpOption(flags?: string | boolean, description?: string): this;
341342

342343
/**
343344
* Output help information and exit.

0 commit comments

Comments
 (0)