From 84d47d5da2a4f9feb8f7929bf80bdb8561f04a9c Mon Sep 17 00:00:00 2001 From: John Gee Date: Wed, 17 Jun 2020 23:19:39 +1200 Subject: [PATCH 01/36] Add new help events --- index.js | 39 ++++++++++++++++++++++++++++++--------- 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/index.js b/index.js index 7134a3865..1884a47c3 100644 --- a/index.js +++ b/index.js @@ -1525,17 +1525,38 @@ class Command extends EventEmitter { */ outputHelp(cb) { - if (!cb) { - cb = (passthru) => { - return passthru; - }; + const groupListeners = []; + let command = this; + while (command) { + groupListeners.push(command); // ordered from current command to root + command = command.parent; } - const cbOutput = cb(this.helpInformation()); - if (typeof cbOutput !== 'string' && !Buffer.isBuffer(cbOutput)) { - throw new Error('outputHelp callback must return a string or a Buffer'); + + groupListeners.slice().reverse().forEach(command => command.emit('preGroupHelp', this)); + this.emit('preHelp', this); + + // 'help' listener is an override + if (this.listenerCount('help') > 0) { + this.emit('help', this); + } else { + const nearestGroupListener = groupListeners.find(command => command.listenerCount('help') > 0); + if (nearestGroupListener) { + nearestGroupListener.emit('help', this); + } else { + let helpInformation = this.helpInformation(); + if (cb) { + helpInformation = cb(helpInformation); + if (typeof helpInformation !== 'string' && !Buffer.isBuffer(helpInformation)) { + throw new Error('outputHelp callback must return a string or a Buffer'); + } + } + process.stdout.write(helpInformation); + } } - process.stdout.write(cbOutput); - this.emit(this._helpLongFlag); + + this.emit(this._helpLongFlag); // Leave in for backwards compatibility + this.emit('postHelp', this); + groupListeners.forEach(command => command.emit('postGroupHelp', this)); }; /** From 901b25ea3ed3e75467e5151a2e426463d99f960d Mon Sep 17 00:00:00 2001 From: John Gee Date: Fri, 19 Jun 2020 21:34:20 +1200 Subject: [PATCH 02/36] Add context for new help events --- index.js | 47 +++++++++++++++++++++++++++++++++++++---------- 1 file changed, 37 insertions(+), 10 deletions(-) diff --git a/index.js b/index.js index 1884a47c3..609d9b517 100644 --- a/index.js +++ b/index.js @@ -1515,6 +1515,24 @@ class Command extends EventEmitter { .join('\n'); }; + _getOutputContext(options) { + const contextOptions = options || {}; + const context = { error: !!contextOptions.error }; + let write; + let log; + if (context.error) { + log = (...args) => console.error(...args); + write = (...args) => process.stderr.write(...args); + } else { + log = (...args) => console.log(...args); + write = (...args) => process.stdout.write(...args); + } + context.log = contextOptions.log || log; + context.write = contextOptions.write || write; + context.command = this; + return context; + } + /** * Output help information for this command. * @@ -1524,7 +1542,16 @@ class Command extends EventEmitter { * @api public */ - outputHelp(cb) { + outputHelp(custom) { + let options; + let legacyCallback; + if (typeof custom === 'function') { + legacyCallback = custom; + } else { + options = custom; + } + const context = this._getOutputContext(options); + const groupListeners = []; let command = this; while (command) { @@ -1532,20 +1559,20 @@ class Command extends EventEmitter { command = command.parent; } - groupListeners.slice().reverse().forEach(command => command.emit('preGroupHelp', this)); - this.emit('preHelp', this); + groupListeners.slice().reverse().forEach(command => command.emit('preGroupHelp', context)); + this.emit('preHelp', context); // 'help' listener is an override if (this.listenerCount('help') > 0) { - this.emit('help', this); + this.emit('help', context); } else { const nearestGroupListener = groupListeners.find(command => command.listenerCount('help') > 0); if (nearestGroupListener) { - nearestGroupListener.emit('help', this); + nearestGroupListener.emit('help', context); } else { let helpInformation = this.helpInformation(); - if (cb) { - helpInformation = cb(helpInformation); + if (legacyCallback) { + helpInformation = legacyCallback(helpInformation); if (typeof helpInformation !== 'string' && !Buffer.isBuffer(helpInformation)) { throw new Error('outputHelp callback must return a string or a Buffer'); } @@ -1555,8 +1582,8 @@ class Command extends EventEmitter { } this.emit(this._helpLongFlag); // Leave in for backwards compatibility - this.emit('postHelp', this); - groupListeners.forEach(command => command.emit('postGroupHelp', this)); + this.emit('postHelp', context); + groupListeners.forEach(command => command.emit('postGroupHelp', context)); }; /** @@ -1604,7 +1631,7 @@ class Command extends EventEmitter { */ _helpAndError() { - this.outputHelp(); + this.outputHelp({ error: true }); // message: do not have all displayed text available so only passing placeholder. this._exit(1, 'commander.help', '(outputHelp)'); }; From 243eae2c48c819a3e954590bcdafa3a652902c50 Mon Sep 17 00:00:00 2001 From: John Gee Date: Fri, 19 Jun 2020 22:04:32 +1200 Subject: [PATCH 03/36] Rename and use help context --- index.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/index.js b/index.js index 609d9b517..183ac12d1 100644 --- a/index.js +++ b/index.js @@ -1515,7 +1515,7 @@ class Command extends EventEmitter { .join('\n'); }; - _getOutputContext(options) { + _getHelpContext(options) { const contextOptions = options || {}; const context = { error: !!contextOptions.error }; let write; @@ -1550,7 +1550,7 @@ class Command extends EventEmitter { } else { options = custom; } - const context = this._getOutputContext(options); + const context = this._getHelpContext(options); const groupListeners = []; let command = this; @@ -1577,7 +1577,7 @@ class Command extends EventEmitter { throw new Error('outputHelp callback must return a string or a Buffer'); } } - process.stdout.write(helpInformation); + context.write(helpInformation); } } From ab863a28ca85554b0a6fed30a53f66c5e9df6e3c Mon Sep 17 00:00:00 2001 From: John Gee Date: Fri, 19 Jun 2020 22:04:41 +1200 Subject: [PATCH 04/36] Suppress help output --- tests/command.exitOverride.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/command.exitOverride.test.js b/tests/command.exitOverride.test.js index 52917ca38..10d5f068d 100644 --- a/tests/command.exitOverride.test.js +++ b/tests/command.exitOverride.test.js @@ -144,6 +144,7 @@ describe('.exitOverride and error details', () => { const program = new commander.Command(); program .exitOverride() + .on('help', () => {}) .command('compress', 'compress description'); let caughtErr; @@ -153,7 +154,6 @@ describe('.exitOverride and error details', () => { caughtErr = err; } - expect(writeSpy).toHaveBeenCalled(); expectCommanderError(caughtErr, 1, 'commander.help', '(outputHelp)'); }); From c8440f7e82988668dd09e85f1bb7564a51d7c487 Mon Sep 17 00:00:00 2001 From: John Gee Date: Sun, 21 Jun 2020 18:48:45 +1200 Subject: [PATCH 05/36] Slight tidy of legacy callback handling --- index.js | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/index.js b/index.js index 183ac12d1..8f88b8bb1 100644 --- a/index.js +++ b/index.js @@ -1515,6 +1515,10 @@ class Command extends EventEmitter { .join('\n'); }; + /** + * @api private + */ + _getHelpContext(options) { const contextOptions = options || {}; const context = { error: !!contextOptions.error }; @@ -1536,21 +1540,16 @@ class Command extends EventEmitter { /** * Output help information for this command. * - * When listener(s) are available for the helpLongFlag - * those callbacks are invoked. - * * @api public */ - outputHelp(custom) { - let options; + outputHelp(contextOptions) { let legacyCallback; - if (typeof custom === 'function') { - legacyCallback = custom; - } else { - options = custom; + if (typeof contextOptions === 'function') { + legacyCallback = contextOptions; + contextOptions = {}; } - const context = this._getHelpContext(options); + const context = this._getHelpContext(contextOptions); const groupListeners = []; let command = this; From aee58dc5e4e630ca7511f1733e8bd2b06cec1a1d Mon Sep 17 00:00:00 2001 From: John Gee Date: Sun, 21 Jun 2020 19:22:38 +1200 Subject: [PATCH 06/36] Shift help contextOptions up to public API --- index.js | 34 ++++++++++++---------------------- 1 file changed, 12 insertions(+), 22 deletions(-) diff --git a/index.js b/index.js index 8f88b8bb1..39b8cbb38 100644 --- a/index.js +++ b/index.js @@ -857,7 +857,7 @@ class Command extends EventEmitter { */ _dispatchSubcommand(commandName, operands, unknown) { const subCommand = this._findCommand(commandName); - if (!subCommand) this._helpAndError(); + if (!subCommand) this.help({ error: true }); if (subCommand._executableHandler) { this._executeSubCommand(subCommand, operands.concat(unknown)); @@ -892,7 +892,7 @@ class Command extends EventEmitter { } else { if (this.commands.length && this.args.length === 0 && !this._actionHandler && !this._defaultCommandName) { // probably missing subcommand and no handler, user needs help - this._helpAndError(); + this.help({ error: true }); } outputHelpIfRequested(this, parsed.unknown); @@ -923,7 +923,7 @@ class Command extends EventEmitter { } } else if (this.commands.length) { // This command has subcommands and nothing hooked up at this level, so display help. - this._helpAndError(); + this.help({ error: true }); } else { // fall through for caller to handle after calling .parse() } @@ -1519,8 +1519,8 @@ class Command extends EventEmitter { * @api private */ - _getHelpContext(options) { - const contextOptions = options || {}; + _getHelpContext(contextOptions) { + contextOptions = contextOptions || {}; const context = { error: !!contextOptions.error }; let write; let log; @@ -1612,27 +1612,17 @@ class Command extends EventEmitter { /** * Output help information and exit. * - * @param {Function} [cb] * @api public */ - help(cb) { - this.outputHelp(cb); - // exitCode: preserving original behaviour which was calling process.exit() - // message: do not have all displayed text available so only passing placeholder. - this._exit(process.exitCode || 0, 'commander.help', '(outputHelp)'); - }; - - /** - * Output help information and exit. Display for error situations. - * - * @api private - */ - - _helpAndError() { - this.outputHelp({ error: true }); + help(contextOptions) { + this.outputHelp(contextOptions); + let exitCode = process.exitCode || 0; + if (exitCode === 0 && contextOptions && typeof contextOptions !== 'function' && contextOptions.error) { + exitCode = 1; + } // message: do not have all displayed text available so only passing placeholder. - this._exit(1, 'commander.help', '(outputHelp)'); + this._exit(exitCode, 'commander.help', '(outputHelp)'); }; }; From 040bc4f28bfe8cbb88a8fc95bdf74cac737891c1 Mon Sep 17 00:00:00 2001 From: John Gee Date: Sun, 21 Jun 2020 19:32:09 +1200 Subject: [PATCH 07/36] Fix some spelling errors --- examples/storeOptionsAsProperties-action.js | 2 +- index.js | 8 ++++---- tests/command.addCommand.test.js | 2 +- tests/command.executableSubcommand.signals.test.js | 2 +- tests/command.exitOverride.test.js | 2 +- tests/command.help.test.js | 2 +- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/examples/storeOptionsAsProperties-action.js b/examples/storeOptionsAsProperties-action.js index af3db8a69..8e9670ae2 100755 --- a/examples/storeOptionsAsProperties-action.js +++ b/examples/storeOptionsAsProperties-action.js @@ -3,7 +3,7 @@ // To avoid possible name clashes, you can change the default behaviour // of storing the options values as properties on the command object. // In addition, you can pass just the options to the action handler -// instead of a commmand object. This allows simpler code, and is more consistent +// instead of a command object. This allows simpler code, and is more consistent // with the previous behaviour so less code changes from old code. // // Example output: diff --git a/index.js b/index.js index 39b8cbb38..7e18f1866 100644 --- a/index.js +++ b/index.js @@ -260,7 +260,7 @@ class Command extends EventEmitter { * * addHelpCommand() // force on * addHelpCommand(false); // force off - * addHelpCommand('help [cmd]', 'display help for [cmd]'); // force on with custom detais + * addHelpCommand('help [cmd]', 'display help for [cmd]'); // force on with custom details * * @return {Command} `this` command for chaining * @api public @@ -428,7 +428,7 @@ class Command extends EventEmitter { * @param {Object} config * @param {string} flags * @param {string} description - * @param {Function|*} [fn] - custom option processing function or default vaue + * @param {Function|*} [fn] - custom option processing function or default value * @param {*} [defaultValue] * @return {Command} `this` command for chaining * @api private @@ -546,7 +546,7 @@ class Command extends EventEmitter { * * @param {string} flags * @param {string} description - * @param {Function|*} [fn] - custom option processing function or default vaue + * @param {Function|*} [fn] - custom option processing function or default value * @param {*} [defaultValue] * @return {Command} `this` command for chaining * @api public @@ -564,7 +564,7 @@ class Command extends EventEmitter { * * @param {string} flags * @param {string} description - * @param {Function|*} [fn] - custom option processing function or default vaue + * @param {Function|*} [fn] - custom option processing function or default value * @param {*} [defaultValue] * @return {Command} `this` command for chaining * @api public diff --git a/tests/command.addCommand.test.js b/tests/command.addCommand.test.js index d9773b17e..714128d6d 100644 --- a/tests/command.addCommand.test.js +++ b/tests/command.addCommand.test.js @@ -35,7 +35,7 @@ test('when commands added using .addCommand and .command then internals similar' case 'boolean': case 'number': case 'undefined': - // Compare vaues in a way that will be readable in test failure message. + // Compare values in a way that will be readable in test failure message. expect(`${key}:${cmd1[key]}`).toEqual(`${key}:${cmd2[key]}`); break; } diff --git a/tests/command.executableSubcommand.signals.test.js b/tests/command.executableSubcommand.signals.test.js index 7cdabf01d..5789d86f3 100644 --- a/tests/command.executableSubcommand.signals.test.js +++ b/tests/command.executableSubcommand.signals.test.js @@ -8,7 +8,7 @@ const path = require('path'); // https://nodejs.org/api/process.html#process_signal_events const describeOrSkipOnWindows = (process.platform === 'win32') ? describe.skip : describe; -// Note: the previous (sinon) test had custom code for SIGUSR1, revist if required: +// Note: the previous (sinon) test had custom code for SIGUSR1, revisit if required: // As described at https://nodejs.org/api/process.html#process_signal_events // this signal will start a debugger and thus the process might output an // additional error message: diff --git a/tests/command.exitOverride.test.js b/tests/command.exitOverride.test.js index 10d5f068d..4629eb91e 100644 --- a/tests/command.exitOverride.test.js +++ b/tests/command.exitOverride.test.js @@ -3,7 +3,7 @@ const path = require('path'); // Test details of the exitOverride errors. // The important checks are the exitCode and code which are intended to be stable for -// semver minor versions. For now, also testing the error.message and that output occured +// semver minor versions. For now, also testing the error.message and that output occurred // to detect accidental changes in behaviour. /* eslint jest/expect-expect: ["error", { "assertFunctionNames": ["expect", "expectCommanderError"] }] */ diff --git a/tests/command.help.test.js b/tests/command.help.test.js index 58b146f07..d6e0e899a 100644 --- a/tests/command.help.test.js +++ b/tests/command.help.test.js @@ -26,7 +26,7 @@ Commands: expect(helpInformation).toBe(expectedHelpInformation); }); -test('when use .description for command then help incudes description', () => { +test('when use .description for command then help includes description', () => { const program = new commander.Command(); program .command('simple-command') From dc8d01da60f03ea370caff804bf73d29cf9027af Mon Sep 17 00:00:00 2001 From: John Gee Date: Sun, 21 Jun 2020 20:27:33 +1200 Subject: [PATCH 08/36] Start adding tests. Fix groupHelp. --- index.js | 4 +- tests/command.help.test.js | 24 ++++++++++++ tests/help.events.test.js | 78 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 104 insertions(+), 2 deletions(-) create mode 100644 tests/help.events.test.js diff --git a/index.js b/index.js index 7e18f1866..b987fab03 100644 --- a/index.js +++ b/index.js @@ -1565,9 +1565,9 @@ class Command extends EventEmitter { if (this.listenerCount('help') > 0) { this.emit('help', context); } else { - const nearestGroupListener = groupListeners.find(command => command.listenerCount('help') > 0); + const nearestGroupListener = groupListeners.find(command => command.listenerCount('groupHelp') > 0); if (nearestGroupListener) { - nearestGroupListener.emit('help', context); + nearestGroupListener.emit('groupHelp', context); } else { let helpInformation = this.helpInformation(); if (legacyCallback) { diff --git a/tests/command.help.test.js b/tests/command.help.test.js index d6e0e899a..d3d99da3d 100644 --- a/tests/command.help.test.js +++ b/tests/command.help.test.js @@ -130,3 +130,27 @@ test('when both help flags masked then not displayed in helpInformation', () => const helpInformation = program.helpInformation(); expect(helpInformation).not.toMatch('display help'); }); + +test('when call .help then output on stdout', () => { + const writeSpy = jest.spyOn(process.stdout, 'write').mockImplementation(() => { }); + const program = new commander.Command(); + program + .exitOverride(); + expect(() => { + program.help(); + }).toThrow('(outputHelp)'); + expect(writeSpy).toHaveBeenCalledWith(program.helpInformation()); + writeSpy.mockClear(); +}); + +test('when call .help with { error: true } then output on stderr', () => { + const writeSpy = jest.spyOn(process.stderr, 'write').mockImplementation(() => { }); + const program = new commander.Command(); + program + .exitOverride(); + expect(() => { + program.help({ error: true }); + }).toThrow('(outputHelp)'); + expect(writeSpy).toHaveBeenCalledWith(program.helpInformation()); + writeSpy.mockClear(); +}); diff --git a/tests/help.events.test.js b/tests/help.events.test.js new file mode 100644 index 000000000..05ddfbd4e --- /dev/null +++ b/tests/help.events.test.js @@ -0,0 +1,78 @@ +const commander = require('../'); + +// Using .outputHelp more than .help, since don't need to avoid process.exit if tests go wrong. +// Assuming .help calls .outputHelp so not testing separately. + +describe('listeners for "help" and "groupHelp"', () => { + let writeSpy; + + beforeAll(() => { + writeSpy = jest.spyOn(process.stdout, 'write').mockImplementation(() => { }); + }); + + afterEach(() => { + writeSpy.mockClear(); + }); + + afterAll(() => { + writeSpy.mockRestore(); + }); + + test('when no listener then default help implementation', () => { + const program = new commander.Command(); + program.outputHelp(); + expect(writeSpy).toHaveBeenCalledWith(program.helpInformation()); + writeSpy.mockClear(); + }); + + test('when on("help") then just "help"', () => { + const program = new commander.Command(); + const customHelp = jest.fn(); + program + .on('help', customHelp); + program.outputHelp(); + expect(writeSpy).toHaveBeenCalledTimes(0); + expect(customHelp).toHaveBeenCalledTimes(1); + writeSpy.mockClear(); + }); + + test('when on("groupHelp") then just "groupHelp"', () => { + const program = new commander.Command(); + const customHelp = jest.fn(); + program + .on('groupHelp', customHelp); + program.outputHelp(); + expect(writeSpy).toHaveBeenCalledTimes(0); + expect(customHelp).toHaveBeenCalledTimes(1); + writeSpy.mockClear(); + }); + + test('when on("groupHelp") and subcommand help then just "groupHelp"', () => { + const program = new commander.Command(); + const customHelp = jest.fn(); + program + .exitOverride() + .on('groupHelp', customHelp) + .command('sub'); + expect(() => { + program.parse(['sub', '--help'], { from: 'user' }); + }).toThrow('(outputHelp)'); + expect(writeSpy).toHaveBeenCalledTimes(0); + expect(customHelp).toHaveBeenCalledTimes(1); + writeSpy.mockClear(); + }); + + test('when on("help") and on("groupHelp") then just "help"', () => { + const program = new commander.Command(); + const customHelp = jest.fn(); + const customGroupHelp = jest.fn(); + program + .on('help', customHelp) + .on('groupHelp', customGroupHelp); + program.outputHelp(); + expect(writeSpy).toHaveBeenCalledTimes(0); + expect(customGroupHelp).toHaveBeenCalledTimes(0); + expect(customHelp).toHaveBeenCalledTimes(1); + writeSpy.mockClear(); + }); +}); From d118efb9e6a9f1aa6de45c29c16026003cce91ad Mon Sep 17 00:00:00 2001 From: John Gee Date: Sun, 21 Jun 2020 21:37:17 +1200 Subject: [PATCH 09/36] Test help event order --- tests/help.events.test.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tests/help.events.test.js b/tests/help.events.test.js index 05ddfbd4e..9a2c4a27d 100644 --- a/tests/help.events.test.js +++ b/tests/help.events.test.js @@ -76,3 +76,17 @@ describe('listeners for "help" and "groupHelp"', () => { writeSpy.mockClear(); }); }); + +test('when listen to lots then emitted in order"', () => { + const program = new commander.Command(); + const eventsOrder = []; + // Mix up the order added + program + .on('postGroupHelp', () => eventsOrder.push('postGroupHelp')) + .on('preGroupHelp', () => eventsOrder.push('preGroupHelp')) + .on('preHelp', () => eventsOrder.push('preHelp')) + .on('postHelp', () => eventsOrder.push('postHelp')) + .on('help', () => eventsOrder.push('help')); + program.outputHelp(); + expect(eventsOrder).toEqual(['preGroupHelp', 'preHelp', 'help', 'postHelp', 'postGroupHelp']); +}); From 6583da928a7de4cb78b8d16075ee867555bb569f Mon Sep 17 00:00:00 2001 From: John Gee Date: Sun, 21 Jun 2020 22:08:45 +1200 Subject: [PATCH 10/36] Add tests on context --- tests/help.events.test.js | 64 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/tests/help.events.test.js b/tests/help.events.test.js index 9a2c4a27d..126ac3abd 100644 --- a/tests/help.events.test.js +++ b/tests/help.events.test.js @@ -90,3 +90,67 @@ test('when listen to lots then emitted in order"', () => { program.outputHelp(); expect(eventsOrder).toEqual(['preGroupHelp', 'preHelp', 'help', 'postHelp', 'postGroupHelp']); }); + +describe('event context', () => { + test('when error:undefined then write is stdout.write', () => { + const writeSpy = jest.spyOn(process.stdout, 'write').mockImplementation(() => { }); + const program = new commander.Command(); + program + .on('help', (context) => context.write('test')) + .outputHelp(); + expect(writeSpy).toHaveBeenCalledWith('test'); + writeSpy.mockClear(); + }); + + test('when error:true then write is stderr.write', () => { + const writeSpy = jest.spyOn(process.stderr, 'write').mockImplementation(() => { }); + const program = new commander.Command(); + program + .on('help', (context) => context.write('test')) + .outputHelp({ error: true }); + expect(writeSpy).toHaveBeenCalledWith('test'); + writeSpy.mockClear(); + }); + + test('when error:undefined then log is console.log', () => { + const logSpy = jest.spyOn(console, 'log').mockImplementation(() => { }); + const program = new commander.Command(); + program + .on('help', (context) => context.log('test')) + .outputHelp(); + expect(logSpy).toHaveBeenCalledWith('test'); + logSpy.mockClear(); + }); + + test('when error:true then log is console.error', () => { + const logSpy = jest.spyOn(console, 'error').mockImplementation(() => { }); + const program = new commander.Command(); + program + .on('help', (context) => context.log('test')) + .outputHelp({ error: true }); + expect(logSpy).toHaveBeenCalledWith('test'); + logSpy.mockClear(); + }); + + test('when help called on program then context.command for groupHelp is program', () => { + let contextCommand; + const program = new commander.Command(); + program + .on('groupHelp', (context) => { contextCommand = context.command; }) + .outputHelp(); + expect(contextCommand).toBe(program); + }); + + test('when help called on subcommand then context.command for groupHelp is subcommand', () => { + let contextCommand; + const program = new commander.Command(); + program + .exitOverride() + .on('groupHelp', (context) => { contextCommand = context.command; }) + const sub = program.command('sub'); + expect(() => { + program.parse(['sub', '--help'], { from: 'user' }); + }).toThrow('(outputHelp)'); + expect(contextCommand).toBe(sub); + }); +}); From 9f39b7612d89aaea27361e505026acb22cdd0cf0 Mon Sep 17 00:00:00 2001 From: John Gee Date: Mon, 22 Jun 2020 21:53:57 +1200 Subject: [PATCH 11/36] Add missing semicolon --- tests/help.events.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/help.events.test.js b/tests/help.events.test.js index 126ac3abd..967b43432 100644 --- a/tests/help.events.test.js +++ b/tests/help.events.test.js @@ -146,7 +146,7 @@ describe('event context', () => { const program = new commander.Command(); program .exitOverride() - .on('groupHelp', (context) => { contextCommand = context.command; }) + .on('groupHelp', (context) => { contextCommand = context.command; }); const sub = program.command('sub'); expect(() => { program.parse(['sub', '--help'], { from: 'user' }); From 2fefb40668d2c5bcb72725708e68d21f3340ef38 Mon Sep 17 00:00:00 2001 From: John Gee Date: Wed, 24 Jun 2020 21:47:21 +1200 Subject: [PATCH 12/36] Add context.error tests --- tests/help.events.test.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/tests/help.events.test.js b/tests/help.events.test.js index 967b43432..dc554efcd 100644 --- a/tests/help.events.test.js +++ b/tests/help.events.test.js @@ -153,4 +153,22 @@ describe('event context', () => { }).toThrow('(outputHelp)'); expect(contextCommand).toBe(sub); }); + + test('when error:undefined then context error is false', () => { + let contextError; + const program = new commander.Command(); + program + .on('help', (context) => { contextError = context.error; }) + .outputHelp(); + expect(contextError).toBe(false); + }); + + test('when error:true then context error is true', () => { + let contextError; + const program = new commander.Command(); + program + .on('help', (context) => { contextError = context.error; }) + .outputHelp({ error: true }); + expect(contextError).toBe(true); + }); }); From e7126e759ab9ddb998832fb4f6d199efb16ba4c9 Mon Sep 17 00:00:00 2001 From: John Gee Date: Sun, 2 Aug 2020 16:41:12 +1200 Subject: [PATCH 13/36] First cut at README adding events and removing callbacks --- Readme.md | 39 ++++++++++++++++++++++++++++++--------- index.js | 4 +++- 2 files changed, 33 insertions(+), 10 deletions(-) diff --git a/Readme.md b/Readme.md index 7f9e110e0..6f4e0d5a5 100644 --- a/Readme.md +++ b/Readme.md @@ -27,8 +27,8 @@ Read this in other languages: English | [简体中文](./Readme_zh-CN.md) - [Automated help](#automated-help) - [Custom help](#custom-help) - [.usage and .name](#usage-and-name) - - [.help(cb)](#helpcb) - - [.outputHelp(cb)](#outputhelpcb) + - [.help()](#help) + - [.outputHelp()](#outputhelp) - [.helpInformation()](#helpinformation) - [.helpOption(flags, description)](#helpoptionflags-description) - [.addHelpCommand()](#addhelpcommand) @@ -497,7 +497,14 @@ shell spawn --help ### Custom help -You can display extra information by listening for "--help". +You can display extra information or even replace the built-in help by listening for special help events. There are three events just for the single command, and three "group" events which occur for the command as well as any of its subcommands. +In the order the events are emitted: + +- `preGroupHelp`: use for adding a global banner or header +- `preHelp`: display extra information before built-in help +- `help` | `groupHelp`: if there is a listener, the event is emitted _instead_ of displaying the built-in help +- `postHelp`: display extra information after built-in help +- `postGroupHelp`: use for adding a global footer (sometimes called an epilog) Example file: [custom-help](./examples/custom-help) @@ -505,8 +512,8 @@ Example file: [custom-help](./examples/custom-help) program .option('-f, --foo', 'enable some foo'); -// must be before .parse() -program.on('--help', () => { +// must be added before calling .parse() +program.on('postHelp', () => { console.log(''); console.log('Example call:'); console.log(' $ custom-help --help'); @@ -526,6 +533,21 @@ Example call: $ custom-help --help ``` +The help listeners are passed a context object for your convenience. The properties are: + +- error: a boolean whether the help is being displayed due to a usage error +- command: the Command which is displaying the help +- write: either `process.stdout.write()` or `.process.stderr.write()`, depending on `error` +- log: either `console.log()` or `console.error()`, depending on `error` + +As an example, the built-in help can be replaced like this: + +```js +program.on('groupHelp', (context) => { + context.write(context.command.helpInformation()); +}); +``` + ### .usage and .name These allow you to customise the usage description in the first line of the help. The name is otherwise @@ -543,14 +565,13 @@ The help will start with: Usage: my-command [global options] command ``` -### .help(cb) +### .help() -Output help information and exit immediately. Optional callback cb allows post-processing of help text before it is displayed. +Output help information and exit immediately. -### .outputHelp(cb) +### .outputHelp() Output help information without exiting. -Optional callback cb allows post-processing of help text before it is displayed. ### .helpInformation() diff --git a/index.js b/index.js index d706aba64..cf72458ac 100644 --- a/index.js +++ b/index.js @@ -1616,6 +1616,7 @@ Read more on https://git.io/JJc0W`); * Output help information for this command. * * @api public + * @param {Object} [contextOptions] - Can optionally pass in `{ error: true }` (and other context properties for help events) */ outputHelp(contextOptions) { @@ -1651,7 +1652,7 @@ Read more on https://git.io/JJc0W`); throw new Error('outputHelp callback must return a string or a Buffer'); } } - context.write(helpInformation); + context.write(helpInformation); // switch to this in next major version } } @@ -1684,6 +1685,7 @@ Read more on https://git.io/JJc0W`); /** * Output help information and exit. * + * @param {Object} [contextOptions] - Can optionally pass in `{ error: true }` (and other context properties for help events) * @api public */ From 772bf752fb089bbec8b03f7fc73549197c40fb7f Mon Sep 17 00:00:00 2001 From: John Gee Date: Sun, 2 Aug 2020 17:56:01 +1200 Subject: [PATCH 14/36] Add typings and parameter descriptions --- index.js | 6 +++++- typings/commander-tests.ts | 6 +++++- typings/index.d.ts | 24 ++++++++++++++++++++---- 3 files changed, 30 insertions(+), 6 deletions(-) diff --git a/index.js b/index.js index cf72458ac..61fa3e5cd 100644 --- a/index.js +++ b/index.js @@ -1615,6 +1615,8 @@ Read more on https://git.io/JJc0W`); /** * Output help information for this command. * + * Outputs built-in help, and customised by adding help event listeners. + * * @api public * @param {Object} [contextOptions] - Can optionally pass in `{ error: true }` (and other context properties for help events) */ @@ -1685,7 +1687,9 @@ Read more on https://git.io/JJc0W`); /** * Output help information and exit. * - * @param {Object} [contextOptions] - Can optionally pass in `{ error: true }` (and other context properties for help events) + * Outputs built-in help, and customised by adding help event listeners. + * + * @param {Object} [contextOptions] - optionally pass in `{ error: true }` (and other context properties for help events) * @api public */ diff --git a/typings/commander-tests.ts b/typings/commander-tests.ts index 57392c667..7ff7a1c77 100644 --- a/typings/commander-tests.ts +++ b/typings/commander-tests.ts @@ -184,10 +184,14 @@ const nameValue: string = program.name(); // outputHelp program.outputHelp(); program.outputHelp((str: string) => { return str; }); +program.help({ error: true }); +program.help({ error: true, write: process.stderr.write, log: console.error }); // help program.help(); -program.help((str: string) => { return str; }); +program.help((str: string) => { return str; }); // Deprecated +program.help({ error: true }); +program.help({ error: false, write: process.stdout.write, log: console.log }); // helpInformation const helpInformnationValue: string = program.helpInformation(); diff --git a/typings/index.d.ts b/typings/index.d.ts index c8061ffb3..97e929c76 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -26,6 +26,17 @@ declare namespace commander { interface ParseOptions { from: 'node' | 'electron' | 'user'; } + interface HelpContext { // optional parameter for .help() and .outputHelp() + error: boolean; + write?: (message: string) => boolean; // e.g. process.stdout.write + log?: (message?: any, ...optionalParams: any[]) => void; // e.g. console.log + } + interface HelpEventContext { // passed to help event listeners + error: boolean; + write: (message: string) => boolean; // e.g. process.stdout.write + log: (message?: any, ...optionalParams: any[]) => void; // e.g. console.log + command: Command; + } interface Command { [key: string]: any; // options as properties @@ -323,10 +334,11 @@ declare namespace commander { /** * Output help information for this command. * - * When listener(s) are available for the helpLongFlag - * those callbacks are invoked. + * Outputs built-in help, and customised by adding help event listeners. + * */ - outputHelp(cb?: (str: string) => string): void; + outputHelp(context?: HelpContext): void; + outputHelp(cb?: (str: string) => string): void; // callback deprecated /** * Return command help documentation. @@ -341,8 +353,12 @@ declare namespace commander { /** * Output help information and exit. + * + * Outputs built-in help, and customised by adding help event listeners. + * */ - help(cb?: (str: string) => string): never; + help(context?: HelpContext): never; + help(cb?: (str: string) => string): never; // callback deprecated /** * Add a listener (callback) for when events occur. (Implemented using EventEmitter.) From 38e32bb8b5780adc1b387deb5a884f19c2453b47 Mon Sep 17 00:00:00 2001 From: John Gee Date: Sun, 2 Aug 2020 18:06:49 +1200 Subject: [PATCH 15/36] Change from --help to postHelp event --- Readme.md | 2 +- examples/deploy | 2 +- typings/commander-tests.ts | 2 +- typings/index.d.ts | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Readme.md b/Readme.md index 6f4e0d5a5..9272869f9 100644 --- a/Readme.md +++ b/Readme.md @@ -770,7 +770,7 @@ program .option("-e, --exec_mode ", "Which exec mode to use") .action(function(cmd, options){ console.log('exec "%s" using %s mode', cmd, options.exec_mode); - }).on('--help', function() { + }).on('postHelp', function() { console.log(''); console.log('Examples:'); console.log(''); diff --git a/examples/deploy b/examples/deploy index 5db5ce298..6c632bf26 100755 --- a/examples/deploy +++ b/examples/deploy @@ -27,7 +27,7 @@ program .option('-e, --exec_mode ', 'Which exec mode to use') .action(function(cmd, options) { console.log('exec "%s" using %s mode', cmd, options.exec_mode); - }).on('--help', function() { + }).on('postHelp', function() { console.log(' Examples:'); console.log(); console.log(' $ deploy exec sequential'); diff --git a/typings/commander-tests.ts b/typings/commander-tests.ts index 7ff7a1c77..707d3b843 100644 --- a/typings/commander-tests.ts +++ b/typings/commander-tests.ts @@ -202,7 +202,7 @@ const helpOptionThis2: commander.Command = program.helpOption('-h,--help', 'cust const helpOptionThis3: commander.Command = program.helpOption(undefined, 'custom description'); // on -const onThis: commander.Command = program.on('--help', () => { +const onThis: commander.Command = program.on('postHelp', () => { // do nothing. }); diff --git a/typings/index.d.ts b/typings/index.d.ts index 97e929c76..7cf32e9c9 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -365,7 +365,7 @@ declare namespace commander { * * @example * program - * .on('--help', () -> { + * .on('postHelp', () -> { * console.log('See web site for more information.'); * }); */ From cda067cca719f40249a00d95a23283292ef70d0d Mon Sep 17 00:00:00 2001 From: John Gee Date: Tue, 4 Aug 2020 21:39:19 +1200 Subject: [PATCH 16/36] Update and add custom help examples --- examples/custom-help | 8 +++++-- examples/custom-help-listeners.js | 37 +++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 2 deletions(-) create mode 100644 examples/custom-help-listeners.js diff --git a/examples/custom-help b/examples/custom-help index e47a0c376..12d4519d8 100755 --- a/examples/custom-help +++ b/examples/custom-help @@ -1,6 +1,7 @@ #!/usr/bin/env node -// This example displays some custom help text after the built-in help. +// This example shows the various help event listeners. +// This is used as an example in the README. // const { Command } = require('commander'); // (normal include) const { Command } = require('../'); // include commander in git clone of commander repo @@ -10,10 +11,13 @@ program .option('-f, --foo', 'enable some foo'); // must be before .parse() -program.on('--help', () => { +program.on('postHelp', () => { console.log(''); console.log('Example call:'); console.log(' $ custom-help --help'); }); program.parse(process.argv); + +// Try the following: +// node custom-help --help diff --git a/examples/custom-help-listeners.js b/examples/custom-help-listeners.js new file mode 100644 index 000000000..1c1cb337c --- /dev/null +++ b/examples/custom-help-listeners.js @@ -0,0 +1,37 @@ +#!/usr/bin/env node + +// This example shows most of the help event listeners. + +// const { Command } = require('commander'); // (normal include) +const { Command } = require('../'); // include commander in git clone of commander repo +const program = new Command(); + +program + .on('preGroupHelp', (context) => { + context.log('Custom preGroupHelp: global banner'); + }) + .on('postGroupHelp', (context) => { + context.log('Custom postGroupHelp: global epilog'); + }); + +program + .command('extra') + .on('preHelp', (context) => { + context.log('Custom preHelp: header'); + }) + .on('postHelp', (context) => { + context.log('Custom postHelp: trailer'); + }); + +program + .command('replacement') + .on('help', (context) => { + context.log(`Completely custom help for ${context.command.name()}`); + }); + +program.parse(); + +// Try the following: +// node custom-help-listeners.js --help +// node custom-help-listeners.js extra --help +// node custom-help-listeners.js replacement --help From 48a3067211030bb4460e2e45c881bbcae02b31ad Mon Sep 17 00:00:00 2001 From: John Gee Date: Sat, 8 Aug 2020 18:59:08 +1200 Subject: [PATCH 17/36] Make help listener example more realistic --- examples/custom-help-listeners.js | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/examples/custom-help-listeners.js b/examples/custom-help-listeners.js index 1c1cb337c..3a065e90c 100644 --- a/examples/custom-help-listeners.js +++ b/examples/custom-help-listeners.js @@ -6,21 +6,29 @@ const { Command } = require('../'); // include commander in git clone of commander repo const program = new Command(); +program.name('awesome'); + program .on('preGroupHelp', (context) => { - context.log('Custom preGroupHelp: global banner'); + context.log('A W E S O M E'); + context.log(); }) .on('postGroupHelp', (context) => { - context.log('Custom postGroupHelp: global epilog'); + context.log(); + context.log('See web site for further help'); }); program .command('extra') .on('preHelp', (context) => { - context.log('Custom preHelp: header'); + context.log('Note: the extra command does not do anything'); + context.log(); }) .on('postHelp', (context) => { - context.log('Custom postHelp: trailer'); + context.log(); + context.log('Examples:'); + context.log(' awesome extra --help'); + context.log(' awesome help extra'); }); program From 4efb4325bd9511a726475bd4ee66fc880bdcf175 Mon Sep 17 00:00:00 2001 From: John Gee Date: Sat, 8 Aug 2020 19:02:58 +1200 Subject: [PATCH 18/36] Update example --- examples/custom-help | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/custom-help b/examples/custom-help index 12d4519d8..73cefd4ef 100755 --- a/examples/custom-help +++ b/examples/custom-help @@ -1,6 +1,6 @@ #!/usr/bin/env node -// This example shows the various help event listeners. +// This example shows the postHelp event listener. // This is used as an example in the README. // const { Command } = require('commander'); // (normal include) @@ -10,7 +10,7 @@ const program = new Command(); program .option('-f, --foo', 'enable some foo'); -// must be before .parse() +// must be added before calling .parse() program.on('postHelp', () => { console.log(''); console.log('Example call:'); From fa574df0260cbbd4874fcb69e47b0a91f10dba56 Mon Sep 17 00:00:00 2001 From: John Gee Date: Mon, 24 Aug 2020 19:47:16 +1200 Subject: [PATCH 19/36] Call the old callback, deprecated --- index.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/index.js b/index.js index ef7e88bc8..a17d46ade 100644 --- a/index.js +++ b/index.js @@ -1649,9 +1649,9 @@ Read more on https://git.io/JJc0W`); */ outputHelp(contextOptions) { - let legacyCallback; + let deprecatedCallback; if (typeof contextOptions === 'function') { - legacyCallback = contextOptions; + deprecatedCallback = contextOptions; contextOptions = {}; } const context = this._getHelpContext(contextOptions); @@ -1675,8 +1675,8 @@ Read more on https://git.io/JJc0W`); nearestGroupListener.emit('groupHelp', context); } else { let helpInformation = this.helpInformation(); - if (legacyCallback) { - helpInformation = legacyCallback(helpInformation); + if (deprecatedCallback) { + helpInformation = deprecatedCallback(helpInformation); if (typeof helpInformation !== 'string' && !Buffer.isBuffer(helpInformation)) { throw new Error('outputHelp callback must return a string or a Buffer'); } From 88a59c4ede0dbde299e1c6cf00bc0f8cb14370bd Mon Sep 17 00:00:00 2001 From: John Gee Date: Mon, 31 Aug 2020 22:25:40 +1200 Subject: [PATCH 20/36] First cut at .addHelp() --- index.js | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/index.js b/index.js index a17d46ade..c582bec1b 100644 --- a/index.js +++ b/index.js @@ -1734,6 +1734,37 @@ Read more on https://git.io/JJc0W`); // message: do not have all displayed text available so only passing placeholder. this._exit(exitCode, 'commander.help', '(outputHelp)'); }; + + /** + * Add additional text to be displayed with the built-in help, + * or add an override to replace the built-in help. + * + * Position is 'before' or 'after' or 'override' to affect just this command, + * and 'beforeAll' or 'afterAll' or 'overrideAll' to affect this command and all its subcommands. + * + * @param {string} position - before or after or override built-in help + * @param {string | Function} text - string to add, or a function returning a string + * @return {Command} `this` command for chaining + */ + addHelp(position, text) { + const whereValues = ['before', 'beforeAll', 'after', 'afterAll', 'override', 'overrideAll']; + if (!whereValues.includes(position)) { + throw new Error(`Unexpected value for position to addHelp. +Expecting one of '${whereValues.join("', '")}'`); + } + // temporary while decide on where strings + const helpEvents = ['preHelp', 'preGroupHelp', 'postHelp', 'postGroupHelp', 'help', 'groupHelp']; + const helpEvent = helpEvents[whereValues.indexOf(position)]; + + this.on(helpEvent, (context) => { + if (typeof text === 'function') { + context.log(text({ error: context.error, command: context.command })); + } else { + context.log(text); + } + }); + return this; + } }; /** From 001c7e12fff8c2dea98367a2f1c923feb2d6d26d Mon Sep 17 00:00:00 2001 From: John Gee Date: Tue, 1 Sep 2020 21:09:08 +1200 Subject: [PATCH 21/36] Change name to addHelpText --- index.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/index.js b/index.js index c582bec1b..517175b88 100644 --- a/index.js +++ b/index.js @@ -1746,10 +1746,10 @@ Read more on https://git.io/JJc0W`); * @param {string | Function} text - string to add, or a function returning a string * @return {Command} `this` command for chaining */ - addHelp(position, text) { + addHelpText(position, text) { const whereValues = ['before', 'beforeAll', 'after', 'afterAll', 'override', 'overrideAll']; if (!whereValues.includes(position)) { - throw new Error(`Unexpected value for position to addHelp. + throw new Error(`Unexpected value for position to addHelpText. Expecting one of '${whereValues.join("', '")}'`); } // temporary while decide on where strings @@ -1757,11 +1757,13 @@ Expecting one of '${whereValues.join("', '")}'`); const helpEvent = helpEvents[whereValues.indexOf(position)]; this.on(helpEvent, (context) => { + let helpStr; if (typeof text === 'function') { - context.log(text({ error: context.error, command: context.command })); + helpStr = text({ error: context.error, command: context.command }); } else { - context.log(text); + helpStr = text; } + context.write(`${helpStr}\n`); }); return this; } From fea1ef5fea6ee85840bd6cb5cd29de29a38a7138 Mon Sep 17 00:00:00 2001 From: John Gee Date: Tue, 1 Sep 2020 21:09:31 +1200 Subject: [PATCH 22/36] First round of tests for addHelpText --- tests/command.addHelp.test.js | 184 ++++++++++++++++++++++++++++++++++ 1 file changed, 184 insertions(+) create mode 100644 tests/command.addHelp.test.js diff --git a/tests/command.addHelp.test.js b/tests/command.addHelp.test.js new file mode 100644 index 000000000..f79f23dfd --- /dev/null +++ b/tests/command.addHelp.test.js @@ -0,0 +1,184 @@ +const commander = require('../'); + +// Using outputHelp to simplify testing. + +describe('program calls to addHelpText', () => { + let writeSpy; + + beforeAll(() => { + writeSpy = jest.spyOn(process.stdout, 'write').mockImplementation(() => { }); + }); + + afterEach(() => { + writeSpy.mockClear(); + }); + + afterAll(() => { + writeSpy.mockRestore(); + }); + + test('when "before" string then string before built-in help', () => { + const program = new commander.Command(); + program.addHelpText('before', 'text'); + program.outputHelp(); + expect(writeSpy).toHaveBeenNthCalledWith(1, 'text\n'); + expect(writeSpy).toHaveBeenNthCalledWith(2, program.helpInformation()); + }); + + test('when "before" function then function result before built-in help', () => { + const program = new commander.Command(); + program.addHelpText('before', () => 'text'); + program.outputHelp(); + expect(writeSpy).toHaveBeenNthCalledWith(1, 'text\n'); + expect(writeSpy).toHaveBeenNthCalledWith(2, program.helpInformation()); + }); + + test('when "beforeAll" string then string before built-in help', () => { + const program = new commander.Command(); + program.addHelpText('beforeAll', 'text'); + program.outputHelp(); + expect(writeSpy).toHaveBeenNthCalledWith(1, 'text\n'); + expect(writeSpy).toHaveBeenNthCalledWith(2, program.helpInformation()); + }); + + test('when "after" string then string after built-in help', () => { + const program = new commander.Command(); + program.addHelpText('after', 'text'); + program.outputHelp(); + expect(writeSpy).toHaveBeenNthCalledWith(1, program.helpInformation()); + expect(writeSpy).toHaveBeenNthCalledWith(2, 'text\n'); + }); + + test('when "afterAll" string then string after built-in help', () => { + const program = new commander.Command(); + program.addHelpText('afterAll', 'text'); + program.outputHelp(); + expect(writeSpy).toHaveBeenNthCalledWith(1, program.helpInformation()); + expect(writeSpy).toHaveBeenNthCalledWith(2, 'text\n'); + }); + + test('when all the simple positions then strings in order', () => { + const program = new commander.Command(); + program.addHelpText('before', 'before'); + program.addHelpText('after', 'after'); + program.addHelpText('beforeAll', 'beforeAll'); + program.addHelpText('afterAll', 'afterAll'); + program.outputHelp(); + expect(writeSpy).toHaveBeenNthCalledWith(1, 'beforeAll\n'); + expect(writeSpy).toHaveBeenNthCalledWith(2, 'before\n'); + expect(writeSpy).toHaveBeenNthCalledWith(3, program.helpInformation()); + expect(writeSpy).toHaveBeenNthCalledWith(4, 'after\n'); + expect(writeSpy).toHaveBeenNthCalledWith(5, 'afterAll\n'); + }); + + test('when "override" string then replaces built-in help', () => { + const program = new commander.Command(); + program.addHelpText('override', 'text'); + program.outputHelp(); + expect(writeSpy).toHaveBeenCalledTimes(1); + expect(writeSpy).toHaveBeenCalledWith('text\n'); + }); + + test('when "overrideAll" string then replaces built-in help', () => { + const program = new commander.Command(); + program.addHelpText('overrideAll', 'text'); + program.outputHelp(); + expect(writeSpy).toHaveBeenCalledTimes(1); + expect(writeSpy).toHaveBeenCalledWith('text\n'); + }); + + test('when "override" and "overrideAll" string then "override" replaces built-in help', () => { + const program = new commander.Command(); + program.addHelpText('override', 'override'); + program.addHelpText('overrideAll', 'overrideAll'); + program.outputHelp(); + expect(writeSpy).toHaveBeenCalledTimes(1); + expect(writeSpy).toHaveBeenCalledWith('override\n'); + }); +}); + +describe('program and subcommand calls to addHelpText', () => { + let writeSpy; + + beforeAll(() => { + writeSpy = jest.spyOn(process.stdout, 'write').mockImplementation(() => { }); + }); + + afterEach(() => { + writeSpy.mockClear(); + }); + + afterAll(() => { + writeSpy.mockRestore(); + }); + + test('when "before" on program then not called on subcommand', () => { + const program = new commander.Command(); + const sub = program.command('sub'); + const testMock = jest.fn(); + program.addHelpText('before', testMock); + sub.outputHelp(); + expect(testMock).not.toHaveBeenCalled(); + }); + + test('when "beforeAll" on program then is called on subcommand', () => { + const program = new commander.Command(); + const sub = program.command('sub'); + const testMock = jest.fn(); + program.addHelpText('beforeAll', testMock); + sub.outputHelp(); + expect(testMock).toHaveBeenCalled(); + }); + + test('when "after" on program then not called on subcommand', () => { + const program = new commander.Command(); + const sub = program.command('sub'); + const testMock = jest.fn(); + program.addHelpText('after', testMock); + sub.outputHelp(); + expect(testMock).not.toHaveBeenCalled(); + }); + + test('when "afterAll" on program then is called on subcommand', () => { + const program = new commander.Command(); + const sub = program.command('sub'); + const testMock = jest.fn(); + program.addHelpText('afterAll', testMock); + sub.outputHelp(); + expect(testMock).toHaveBeenCalled(); + }); + + test('when "override" on program then not called on subcommand', () => { + const program = new commander.Command(); + const sub = program.command('sub'); + const testMock = jest.fn(); + program.addHelpText('override', testMock); + sub.outputHelp(); + expect(testMock).not.toHaveBeenCalled(); + }); + + test('when "overrideAll" on program then is called on subcommand', () => { + const program = new commander.Command(); + const sub = program.command('sub'); + const testMock = jest.fn(); + program.addHelpText('overrideAll', testMock); + sub.outputHelp(); + expect(testMock).toHaveBeenCalled(); + }); + + test('when "overrideAll" on program and "override" on subcommand then only override called', () => { + const program = new commander.Command(); + const programMock = jest.fn(); + program.addHelpText('overrideAll', programMock); + const sub = program.command('sub'); + const subMock = jest.fn(); + sub.addHelpText('override', subMock); + sub.outputHelp(); + expect(programMock).not.toHaveBeenCalled(); + expect(subMock).toHaveBeenCalled(); + }); +}); + +test.todo('addHelpText with error goes to stderr'); +test.todo('addHelpText function passed error correctly'); +test.todo('addHelpText function passed command correctly'); From e440a47e420d5d6cee08edf9a43f1721086081c0 Mon Sep 17 00:00:00 2001 From: John Gee Date: Tue, 1 Sep 2020 21:30:16 +1200 Subject: [PATCH 23/36] Simplify help event context, remove log --- Readme.md | 1 - index.js | 4 ---- tests/help.events.test.js | 20 -------------------- typings/commander-tests.ts | 4 ++-- typings/index.d.ts | 6 ++---- 5 files changed, 4 insertions(+), 31 deletions(-) diff --git a/Readme.md b/Readme.md index 3e5dcf19b..6542d9451 100644 --- a/Readme.md +++ b/Readme.md @@ -538,7 +538,6 @@ The help listeners are passed a context object for your convenience. The propert - error: a boolean whether the help is being displayed due to a usage error - command: the Command which is displaying the help - write: either `process.stdout.write()` or `.process.stderr.write()`, depending on `error` -- log: either `console.log()` or `console.error()`, depending on `error` As an example, the built-in help can be replaced like this: diff --git a/index.js b/index.js index 517175b88..06605121b 100644 --- a/index.js +++ b/index.js @@ -1625,15 +1625,11 @@ Read more on https://git.io/JJc0W`); contextOptions = contextOptions || {}; const context = { error: !!contextOptions.error }; let write; - let log; if (context.error) { - log = (...args) => console.error(...args); write = (...args) => process.stderr.write(...args); } else { - log = (...args) => console.log(...args); write = (...args) => process.stdout.write(...args); } - context.log = contextOptions.log || log; context.write = contextOptions.write || write; context.command = this; return context; diff --git a/tests/help.events.test.js b/tests/help.events.test.js index dc554efcd..7efb345a0 100644 --- a/tests/help.events.test.js +++ b/tests/help.events.test.js @@ -112,26 +112,6 @@ describe('event context', () => { writeSpy.mockClear(); }); - test('when error:undefined then log is console.log', () => { - const logSpy = jest.spyOn(console, 'log').mockImplementation(() => { }); - const program = new commander.Command(); - program - .on('help', (context) => context.log('test')) - .outputHelp(); - expect(logSpy).toHaveBeenCalledWith('test'); - logSpy.mockClear(); - }); - - test('when error:true then log is console.error', () => { - const logSpy = jest.spyOn(console, 'error').mockImplementation(() => { }); - const program = new commander.Command(); - program - .on('help', (context) => context.log('test')) - .outputHelp({ error: true }); - expect(logSpy).toHaveBeenCalledWith('test'); - logSpy.mockClear(); - }); - test('when help called on program then context.command for groupHelp is program', () => { let contextCommand; const program = new commander.Command(); diff --git a/typings/commander-tests.ts b/typings/commander-tests.ts index 34cec1a20..e655392a0 100644 --- a/typings/commander-tests.ts +++ b/typings/commander-tests.ts @@ -189,13 +189,13 @@ const nameValue: string = program.name(); program.outputHelp(); program.outputHelp((str: string) => { return str; }); program.help({ error: true }); -program.help({ error: true, write: process.stderr.write, log: console.error }); +program.help({ error: true, write: process.stderr.write }); // help program.help(); program.help((str: string) => { return str; }); // Deprecated program.help({ error: true }); -program.help({ error: false, write: process.stdout.write, log: console.log }); +program.help({ error: false, write: process.stdout.write }); // helpInformation const helpInformnationValue: string = program.helpInformation(); diff --git a/typings/index.d.ts b/typings/index.d.ts index 4afc5c4c4..60a510f31 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -28,13 +28,11 @@ declare namespace commander { } interface HelpContext { // optional parameter for .help() and .outputHelp() error: boolean; - write?: (message: string) => boolean; // e.g. process.stdout.write - log?: (message?: any, ...optionalParams: any[]) => void; // e.g. console.log + write?: (message: string) => void; } interface HelpEventContext { // passed to help event listeners error: boolean; - write: (message: string) => boolean; // e.g. process.stdout.write - log: (message?: any, ...optionalParams: any[]) => void; // e.g. console.log + write: (message: string) => void; command: Command; } From ed553e4dd43a771069d05f4ac73e616acebad112 Mon Sep 17 00:00:00 2001 From: John Gee Date: Tue, 1 Sep 2020 22:29:33 +1200 Subject: [PATCH 24/36] Assign write directly --- index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/index.js b/index.js index 06605121b..d7443acad 100644 --- a/index.js +++ b/index.js @@ -1626,9 +1626,9 @@ Read more on https://git.io/JJc0W`); const context = { error: !!contextOptions.error }; let write; if (context.error) { - write = (...args) => process.stderr.write(...args); + write = process.stderr.write; } else { - write = (...args) => process.stdout.write(...args); + write = process.stdout.write; } context.write = contextOptions.write || write; context.command = this; From f49d2e36b5ff3ff534791f0c94b293ada82107fb Mon Sep 17 00:00:00 2001 From: John Gee Date: Tue, 1 Sep 2020 22:30:06 +1200 Subject: [PATCH 25/36] Add end-to-end and context checks for addHelpText --- tests/command.addHelp.test.js | 108 +++++++++++++++++++++++++++++++++- 1 file changed, 105 insertions(+), 3 deletions(-) diff --git a/tests/command.addHelp.test.js b/tests/command.addHelp.test.js index f79f23dfd..57b7eb280 100644 --- a/tests/command.addHelp.test.js +++ b/tests/command.addHelp.test.js @@ -179,6 +179,108 @@ describe('program and subcommand calls to addHelpText', () => { }); }); -test.todo('addHelpText with error goes to stderr'); -test.todo('addHelpText function passed error correctly'); -test.todo('addHelpText function passed command correctly'); +describe('context checks with full parse', () => { + let stdoutSpy; + let stderrSpy; + + beforeAll(() => { + stdoutSpy = jest.spyOn(process.stdout, 'write').mockImplementation(() => { }); + stderrSpy = jest.spyOn(process.stderr, 'write').mockImplementation(() => { }); + }); + + afterEach(() => { + stdoutSpy.mockClear(); + stderrSpy.mockClear(); + }); + + afterAll(() => { + stdoutSpy.mockRestore(); + stderrSpy.mockRestore(); + }); + + test('when help requested then text is on stdout', () => { + const program = new commander.Command(); + program + .exitOverride() + .addHelpText('before', 'text'); + expect(() => { + program.parse(['--help'], { from: 'user' }); + }).toThrow(); + expect(stdoutSpy).toHaveBeenCalledWith('text\n'); + }); + + test('when help for error then text is on stderr', () => { + const program = new commander.Command(); + program + .exitOverride() + .addHelpText('before', 'text') + .command('sub'); + expect(() => { + program.parse([], { from: 'user' }); + }).toThrow(); + expect(stderrSpy).toHaveBeenCalledWith('text\n'); + }); + + test('when help requested then context.error is false', () => { + let context = {}; + const program = new commander.Command(); + program + .exitOverride() + .addHelpText('override', (contextParam) => { context = contextParam; }); + expect(() => { + program.parse(['--help'], { from: 'user' }); + }).toThrow(); + expect(context.error).toBe(false); + }); + + test('when help for error then context.error is true', () => { + let context = {}; + const program = new commander.Command(); + program + .exitOverride() + .addHelpText('override', (contextParam) => { context = contextParam; }) + .command('sub'); + expect(() => { + program.parse([], { from: 'user' }); + }).toThrow(); + expect(context.error).toBe(true); + }); + + test('when help on program then context.command is program', () => { + let context = {}; + const program = new commander.Command(); + program + .exitOverride() + .addHelpText('override', (contextParam) => { context = contextParam; }); + expect(() => { + program.parse(['--help'], { from: 'user' }); + }).toThrow(); + expect(context.command).toBe(program); + }); + + test('when help on subcommand and override on subcommand then context.command is subcommand', () => { + let context = {}; + const program = new commander.Command(); + program + .exitOverride(); + const sub = program.command('sub') + .addHelpText('override', (contextParam) => { context = contextParam; }); + expect(() => { + program.parse(['sub', '--help'], { from: 'user' }); + }).toThrow(); + expect(context.command).toBe(sub); + }); + + test('when help on subcommand and overrideAll on program then context.command is subcommand', () => { + let context = {}; + const program = new commander.Command(); + program + .exitOverride() + .addHelpText('overrideAll', (contextParam) => { context = contextParam; }); + const sub = program.command('sub'); + expect(() => { + program.parse(['sub', '--help'], { from: 'user' }); + }).toThrow(); + expect(context.command).toBe(sub); + }); +}); From 0606acbd92ed911b8cc260878e91bee03206a58f Mon Sep 17 00:00:00 2001 From: John Gee Date: Tue, 1 Sep 2020 22:50:05 +1200 Subject: [PATCH 26/36] Put back write wrapper to fix unit test --- index.js | 4 ++-- tests/command.executableSubcommand.test.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/index.js b/index.js index d7443acad..06605121b 100644 --- a/index.js +++ b/index.js @@ -1626,9 +1626,9 @@ Read more on https://git.io/JJc0W`); const context = { error: !!contextOptions.error }; let write; if (context.error) { - write = process.stderr.write; + write = (...args) => process.stderr.write(...args); } else { - write = process.stdout.write; + write = (...args) => process.stdout.write(...args); } context.write = contextOptions.write || write; context.command = this; diff --git a/tests/command.executableSubcommand.test.js b/tests/command.executableSubcommand.test.js index 8c6076cb0..04d47c4ec 100644 --- a/tests/command.executableSubcommand.test.js +++ b/tests/command.executableSubcommand.test.js @@ -3,7 +3,7 @@ const commander = require('../'); // Executable subcommand tests that didn't fit in elsewhere. // This is the default behaviour when no default command and no action handlers -test('when no command missing then display help', () => { +test('when no command specified and executable then display help', () => { // Optional. Suppress normal output to keep test output clean. const writeSpy = jest.spyOn(process.stdout, 'write').mockImplementation(() => { }); const program = new commander.Command(); From 4bc95d04e17deda179f3a25af3b1f0ffd8873817 Mon Sep 17 00:00:00 2001 From: John Gee Date: Tue, 1 Sep 2020 23:03:27 +1200 Subject: [PATCH 27/36] Fix write mocks for help-as-error output --- tests/command.asterisk.test.js | 13 ++----------- tests/command.executableSubcommand.test.js | 2 +- 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/tests/command.asterisk.test.js b/tests/command.asterisk.test.js index aff9495e0..14711b2d4 100644 --- a/tests/command.asterisk.test.js +++ b/tests/command.asterisk.test.js @@ -11,18 +11,8 @@ const commander = require('../'); // Historical: the event 'command:*' used to also be shared by the action handler on the program. describe(".command('*')", () => { - // Use internal knowledge to suppress output to keep test output clean. - let writeMock; - - beforeAll(() => { - writeMock = jest.spyOn(process.stdout, 'write').mockImplementation(() => { }); - }); - - afterAll(() => { - writeMock.mockRestore(); - }); - test('when no arguments then asterisk action not called', () => { + const writeMock = jest.spyOn(process.stderr, 'write').mockImplementation(() => { }); const mockAction = jest.fn(); const program = new commander.Command(); program @@ -35,6 +25,7 @@ describe(".command('*')", () => { ; } expect(mockAction).not.toHaveBeenCalled(); + writeMock.mockRestore(); }); test('when unrecognised argument then asterisk action called', () => { diff --git a/tests/command.executableSubcommand.test.js b/tests/command.executableSubcommand.test.js index 04d47c4ec..538a35f34 100644 --- a/tests/command.executableSubcommand.test.js +++ b/tests/command.executableSubcommand.test.js @@ -5,7 +5,7 @@ const commander = require('../'); // This is the default behaviour when no default command and no action handlers test('when no command specified and executable then display help', () => { // Optional. Suppress normal output to keep test output clean. - const writeSpy = jest.spyOn(process.stdout, 'write').mockImplementation(() => { }); + const writeSpy = jest.spyOn(process.stderr, 'write').mockImplementation(() => { }); const program = new commander.Command(); program .exitOverride((err) => { throw err; }) From 9ea0de1d3ac940e18d83143552a5f8d5510f61da Mon Sep 17 00:00:00 2001 From: John Gee Date: Tue, 1 Sep 2020 23:27:31 +1200 Subject: [PATCH 28/36] Ignore falsy values for AddHelpText --- index.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/index.js b/index.js index 06605121b..aa47b59a4 100644 --- a/index.js +++ b/index.js @@ -1759,7 +1759,10 @@ Expecting one of '${whereValues.join("', '")}'`); } else { helpStr = text; } - context.write(`${helpStr}\n`); + // Ignore falsy value when nothing to output. + if (helpStr) { + context.write(`${helpStr}\n`); + } }); return this; } From 47a0c1059e0c3a04f4e08fb6651c69126a433f9d Mon Sep 17 00:00:00 2001 From: John Gee Date: Wed, 2 Sep 2020 20:42:03 +1200 Subject: [PATCH 29/36] Remove the help override from addHelpText as not good fit --- index.js | 47 ++++----- tests/command.addHelp.test.js | 69 ++----------- tests/command.exitOverride.test.js | 13 ++- tests/help.events.test.js | 154 ----------------------------- typings/commander-tests.ts | 19 +++- typings/index.d.ts | 23 +++-- 6 files changed, 57 insertions(+), 268 deletions(-) delete mode 100644 tests/help.events.test.js diff --git a/index.js b/index.js index aa47b59a4..3fee023db 100644 --- a/index.js +++ b/index.js @@ -1641,7 +1641,7 @@ Read more on https://git.io/JJc0W`); * Outputs built-in help, and customised by adding help event listeners. * * @api public - * @param {Object} [contextOptions] - Can optionally pass in `{ error: true }` (and other context properties for help events) + * @param {Object} [contextOptions] - Can optionally pass in `{ error: true }` to write to stderr */ outputHelp(contextOptions) { @@ -1659,31 +1659,21 @@ Read more on https://git.io/JJc0W`); command = command.parent; } - groupListeners.slice().reverse().forEach(command => command.emit('preGroupHelp', context)); - this.emit('preHelp', context); + groupListeners.slice().reverse().forEach(command => command.emit('beforeAllHelp', context)); + this.emit('beforeHelp', context); - // 'help' listener is an override - if (this.listenerCount('help') > 0) { - this.emit('help', context); - } else { - const nearestGroupListener = groupListeners.find(command => command.listenerCount('groupHelp') > 0); - if (nearestGroupListener) { - nearestGroupListener.emit('groupHelp', context); - } else { - let helpInformation = this.helpInformation(); - if (deprecatedCallback) { - helpInformation = deprecatedCallback(helpInformation); - if (typeof helpInformation !== 'string' && !Buffer.isBuffer(helpInformation)) { - throw new Error('outputHelp callback must return a string or a Buffer'); - } - } - context.write(helpInformation); // switch to this in next major version + let helpInformation = this.helpInformation(); + if (deprecatedCallback) { + helpInformation = deprecatedCallback(helpInformation); + if (typeof helpInformation !== 'string' && !Buffer.isBuffer(helpInformation)) { + throw new Error('outputHelp callback must return a string or a Buffer'); } } + context.write(helpInformation); this.emit(this._helpLongFlag); // Leave in for backwards compatibility - this.emit('postHelp', context); - groupListeners.forEach(command => command.emit('postGroupHelp', context)); + this.emit('afterHelp', context); + groupListeners.forEach(command => command.emit('afterAllHelp', context)); }; /** @@ -1717,7 +1707,7 @@ Read more on https://git.io/JJc0W`); * * Outputs built-in help, and customised by adding help event listeners. * - * @param {Object} [contextOptions] - optionally pass in `{ error: true }` (and other context properties for help events) + * @param {Object} [contextOptions] - optionally pass in `{ error: true }` to write to stderr * @api public */ @@ -1736,22 +1726,19 @@ Read more on https://git.io/JJc0W`); * or add an override to replace the built-in help. * * Position is 'before' or 'after' or 'override' to affect just this command, - * and 'beforeAll' or 'afterAll' or 'overrideAll' to affect this command and all its subcommands. + * and 'beforeAll' or 'afterAll' to affect this command and all its subcommands. * * @param {string} position - before or after or override built-in help * @param {string | Function} text - string to add, or a function returning a string * @return {Command} `this` command for chaining */ addHelpText(position, text) { - const whereValues = ['before', 'beforeAll', 'after', 'afterAll', 'override', 'overrideAll']; - if (!whereValues.includes(position)) { + const allowedValues = ['beforeAll', 'before', 'after', 'afterAll']; + if (!allowedValues.includes(position)) { throw new Error(`Unexpected value for position to addHelpText. -Expecting one of '${whereValues.join("', '")}'`); +Expecting one of '${allowedValues.join("', '")}'`); } - // temporary while decide on where strings - const helpEvents = ['preHelp', 'preGroupHelp', 'postHelp', 'postGroupHelp', 'help', 'groupHelp']; - const helpEvent = helpEvents[whereValues.indexOf(position)]; - + const helpEvent = `${position}Help`; this.on(helpEvent, (context) => { let helpStr; if (typeof text === 'function') { diff --git a/tests/command.addHelp.test.js b/tests/command.addHelp.test.js index 57b7eb280..1fc7ca42e 100644 --- a/tests/command.addHelp.test.js +++ b/tests/command.addHelp.test.js @@ -70,31 +70,6 @@ describe('program calls to addHelpText', () => { expect(writeSpy).toHaveBeenNthCalledWith(4, 'after\n'); expect(writeSpy).toHaveBeenNthCalledWith(5, 'afterAll\n'); }); - - test('when "override" string then replaces built-in help', () => { - const program = new commander.Command(); - program.addHelpText('override', 'text'); - program.outputHelp(); - expect(writeSpy).toHaveBeenCalledTimes(1); - expect(writeSpy).toHaveBeenCalledWith('text\n'); - }); - - test('when "overrideAll" string then replaces built-in help', () => { - const program = new commander.Command(); - program.addHelpText('overrideAll', 'text'); - program.outputHelp(); - expect(writeSpy).toHaveBeenCalledTimes(1); - expect(writeSpy).toHaveBeenCalledWith('text\n'); - }); - - test('when "override" and "overrideAll" string then "override" replaces built-in help', () => { - const program = new commander.Command(); - program.addHelpText('override', 'override'); - program.addHelpText('overrideAll', 'overrideAll'); - program.outputHelp(); - expect(writeSpy).toHaveBeenCalledTimes(1); - expect(writeSpy).toHaveBeenCalledWith('override\n'); - }); }); describe('program and subcommand calls to addHelpText', () => { @@ -147,36 +122,6 @@ describe('program and subcommand calls to addHelpText', () => { sub.outputHelp(); expect(testMock).toHaveBeenCalled(); }); - - test('when "override" on program then not called on subcommand', () => { - const program = new commander.Command(); - const sub = program.command('sub'); - const testMock = jest.fn(); - program.addHelpText('override', testMock); - sub.outputHelp(); - expect(testMock).not.toHaveBeenCalled(); - }); - - test('when "overrideAll" on program then is called on subcommand', () => { - const program = new commander.Command(); - const sub = program.command('sub'); - const testMock = jest.fn(); - program.addHelpText('overrideAll', testMock); - sub.outputHelp(); - expect(testMock).toHaveBeenCalled(); - }); - - test('when "overrideAll" on program and "override" on subcommand then only override called', () => { - const program = new commander.Command(); - const programMock = jest.fn(); - program.addHelpText('overrideAll', programMock); - const sub = program.command('sub'); - const subMock = jest.fn(); - sub.addHelpText('override', subMock); - sub.outputHelp(); - expect(programMock).not.toHaveBeenCalled(); - expect(subMock).toHaveBeenCalled(); - }); }); describe('context checks with full parse', () => { @@ -226,7 +171,7 @@ describe('context checks with full parse', () => { const program = new commander.Command(); program .exitOverride() - .addHelpText('override', (contextParam) => { context = contextParam; }); + .addHelpText('before', (contextParam) => { context = contextParam; }); expect(() => { program.parse(['--help'], { from: 'user' }); }).toThrow(); @@ -238,7 +183,7 @@ describe('context checks with full parse', () => { const program = new commander.Command(); program .exitOverride() - .addHelpText('override', (contextParam) => { context = contextParam; }) + .addHelpText('before', (contextParam) => { context = contextParam; }) .command('sub'); expect(() => { program.parse([], { from: 'user' }); @@ -251,32 +196,32 @@ describe('context checks with full parse', () => { const program = new commander.Command(); program .exitOverride() - .addHelpText('override', (contextParam) => { context = contextParam; }); + .addHelpText('before', (contextParam) => { context = contextParam; }); expect(() => { program.parse(['--help'], { from: 'user' }); }).toThrow(); expect(context.command).toBe(program); }); - test('when help on subcommand and override on subcommand then context.command is subcommand', () => { + test('when help on subcommand and "before" subcommand then context.command is subcommand', () => { let context = {}; const program = new commander.Command(); program .exitOverride(); const sub = program.command('sub') - .addHelpText('override', (contextParam) => { context = contextParam; }); + .addHelpText('before', (contextParam) => { context = contextParam; }); expect(() => { program.parse(['sub', '--help'], { from: 'user' }); }).toThrow(); expect(context.command).toBe(sub); }); - test('when help on subcommand and overrideAll on program then context.command is subcommand', () => { + test('when help on subcommand and "beforeAll" on program then context.command is subcommand', () => { let context = {}; const program = new commander.Command(); program .exitOverride() - .addHelpText('overrideAll', (contextParam) => { context = contextParam; }); + .addHelpText('beforeAll', (contextParam) => { context = contextParam; }); const sub = program.command('sub'); expect(() => { program.parse(['sub', '--help'], { from: 'user' }); diff --git a/tests/command.exitOverride.test.js b/tests/command.exitOverride.test.js index 115eddcf2..62bfdf6e4 100644 --- a/tests/command.exitOverride.test.js +++ b/tests/command.exitOverride.test.js @@ -18,21 +18,17 @@ function expectCommanderError(err, exitCode, code, message) { describe('.exitOverride and error details', () => { // Use internal knowledge to suppress output to keep test output clean. let consoleErrorSpy; - let writeSpy; // used for help [sic] and version beforeAll(() => { consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => { }); - writeSpy = jest.spyOn(process.stdout, 'write').mockImplementation(() => { }); }); afterEach(() => { consoleErrorSpy.mockClear(); - writeSpy.mockClear(); }); afterAll(() => { consoleErrorSpy.mockRestore(); - writeSpy.mockRestore(); }); test('when specify unknown program option then throw CommanderError', () => { @@ -125,6 +121,7 @@ describe('.exitOverride and error details', () => { }); test('when specify --help then throw CommanderError', () => { + const writeSpy = jest.spyOn(process.stdout, 'write').mockImplementation(() => { }); const program = new commander.Command(); program .exitOverride(); @@ -136,15 +133,15 @@ describe('.exitOverride and error details', () => { caughtErr = err; } - expect(writeSpy).toHaveBeenCalled(); expectCommanderError(caughtErr, 0, 'commander.helpDisplayed', '(outputHelp)'); + writeSpy.mockRestore(); }); test('when executable subcommand and no command specified then throw CommanderError', () => { + const writeSpy = jest.spyOn(process.stderr, 'write').mockImplementation(() => { }); const program = new commander.Command(); program .exitOverride() - .on('help', () => {}) .command('compress', 'compress description'); let caughtErr; @@ -155,9 +152,11 @@ describe('.exitOverride and error details', () => { } expectCommanderError(caughtErr, 1, 'commander.help', '(outputHelp)'); + writeSpy.mockRestore(); }); test('when specify --version then throw CommanderError', () => { + const writeSpy = jest.spyOn(process.stdout, 'write').mockImplementation(() => { }); const myVersion = '1.2.3'; const program = new commander.Command(); program @@ -171,8 +170,8 @@ describe('.exitOverride and error details', () => { caughtErr = err; } - expect(writeSpy).toHaveBeenCalled(); expectCommanderError(caughtErr, 0, 'commander.version', myVersion); + writeSpy.mockRestore(); }); test('when executableSubcommand succeeds then call exitOverride', async() => { diff --git a/tests/help.events.test.js b/tests/help.events.test.js deleted file mode 100644 index 7efb345a0..000000000 --- a/tests/help.events.test.js +++ /dev/null @@ -1,154 +0,0 @@ -const commander = require('../'); - -// Using .outputHelp more than .help, since don't need to avoid process.exit if tests go wrong. -// Assuming .help calls .outputHelp so not testing separately. - -describe('listeners for "help" and "groupHelp"', () => { - let writeSpy; - - beforeAll(() => { - writeSpy = jest.spyOn(process.stdout, 'write').mockImplementation(() => { }); - }); - - afterEach(() => { - writeSpy.mockClear(); - }); - - afterAll(() => { - writeSpy.mockRestore(); - }); - - test('when no listener then default help implementation', () => { - const program = new commander.Command(); - program.outputHelp(); - expect(writeSpy).toHaveBeenCalledWith(program.helpInformation()); - writeSpy.mockClear(); - }); - - test('when on("help") then just "help"', () => { - const program = new commander.Command(); - const customHelp = jest.fn(); - program - .on('help', customHelp); - program.outputHelp(); - expect(writeSpy).toHaveBeenCalledTimes(0); - expect(customHelp).toHaveBeenCalledTimes(1); - writeSpy.mockClear(); - }); - - test('when on("groupHelp") then just "groupHelp"', () => { - const program = new commander.Command(); - const customHelp = jest.fn(); - program - .on('groupHelp', customHelp); - program.outputHelp(); - expect(writeSpy).toHaveBeenCalledTimes(0); - expect(customHelp).toHaveBeenCalledTimes(1); - writeSpy.mockClear(); - }); - - test('when on("groupHelp") and subcommand help then just "groupHelp"', () => { - const program = new commander.Command(); - const customHelp = jest.fn(); - program - .exitOverride() - .on('groupHelp', customHelp) - .command('sub'); - expect(() => { - program.parse(['sub', '--help'], { from: 'user' }); - }).toThrow('(outputHelp)'); - expect(writeSpy).toHaveBeenCalledTimes(0); - expect(customHelp).toHaveBeenCalledTimes(1); - writeSpy.mockClear(); - }); - - test('when on("help") and on("groupHelp") then just "help"', () => { - const program = new commander.Command(); - const customHelp = jest.fn(); - const customGroupHelp = jest.fn(); - program - .on('help', customHelp) - .on('groupHelp', customGroupHelp); - program.outputHelp(); - expect(writeSpy).toHaveBeenCalledTimes(0); - expect(customGroupHelp).toHaveBeenCalledTimes(0); - expect(customHelp).toHaveBeenCalledTimes(1); - writeSpy.mockClear(); - }); -}); - -test('when listen to lots then emitted in order"', () => { - const program = new commander.Command(); - const eventsOrder = []; - // Mix up the order added - program - .on('postGroupHelp', () => eventsOrder.push('postGroupHelp')) - .on('preGroupHelp', () => eventsOrder.push('preGroupHelp')) - .on('preHelp', () => eventsOrder.push('preHelp')) - .on('postHelp', () => eventsOrder.push('postHelp')) - .on('help', () => eventsOrder.push('help')); - program.outputHelp(); - expect(eventsOrder).toEqual(['preGroupHelp', 'preHelp', 'help', 'postHelp', 'postGroupHelp']); -}); - -describe('event context', () => { - test('when error:undefined then write is stdout.write', () => { - const writeSpy = jest.spyOn(process.stdout, 'write').mockImplementation(() => { }); - const program = new commander.Command(); - program - .on('help', (context) => context.write('test')) - .outputHelp(); - expect(writeSpy).toHaveBeenCalledWith('test'); - writeSpy.mockClear(); - }); - - test('when error:true then write is stderr.write', () => { - const writeSpy = jest.spyOn(process.stderr, 'write').mockImplementation(() => { }); - const program = new commander.Command(); - program - .on('help', (context) => context.write('test')) - .outputHelp({ error: true }); - expect(writeSpy).toHaveBeenCalledWith('test'); - writeSpy.mockClear(); - }); - - test('when help called on program then context.command for groupHelp is program', () => { - let contextCommand; - const program = new commander.Command(); - program - .on('groupHelp', (context) => { contextCommand = context.command; }) - .outputHelp(); - expect(contextCommand).toBe(program); - }); - - test('when help called on subcommand then context.command for groupHelp is subcommand', () => { - let contextCommand; - const program = new commander.Command(); - program - .exitOverride() - .on('groupHelp', (context) => { contextCommand = context.command; }); - const sub = program.command('sub'); - expect(() => { - program.parse(['sub', '--help'], { from: 'user' }); - }).toThrow('(outputHelp)'); - expect(contextCommand).toBe(sub); - }); - - test('when error:undefined then context error is false', () => { - let contextError; - const program = new commander.Command(); - program - .on('help', (context) => { contextError = context.error; }) - .outputHelp(); - expect(contextError).toBe(false); - }); - - test('when error:true then context error is true', () => { - let contextError; - const program = new commander.Command(); - program - .on('help', (context) => { contextError = context.error; }) - .outputHelp({ error: true }); - expect(contextError).toBe(true); - }); -}); diff --git a/typings/commander-tests.ts b/typings/commander-tests.ts index e655392a0..16abd3dfa 100644 --- a/typings/commander-tests.ts +++ b/typings/commander-tests.ts @@ -187,15 +187,13 @@ const nameValue: string = program.name(); // outputHelp program.outputHelp(); -program.outputHelp((str: string) => { return str; }); -program.help({ error: true }); -program.help({ error: true, write: process.stderr.write }); +program.outputHelp((str: string) => { return str; }); // deprecated +program.outputHelp({ error: true }); // help program.help(); program.help((str: string) => { return str; }); // Deprecated program.help({ error: true }); -program.help({ error: false, write: process.stdout.write }); // helpInformation const helpInformnationValue: string = program.helpInformation(); @@ -206,8 +204,19 @@ const helpOptionThis2: commander.Command = program.helpOption('-h,--help', 'cust const helpOptionThis3: commander.Command = program.helpOption(undefined, 'custom description'); const helpOptionThis4: commander.Command = program.helpOption(false); +// addHelpText +const addHelpTextThis1: commander.Command = program.addHelpText('after', 'text'); +const addHelpTextThis2: commander.Command = program.addHelpText('afterAll', 'text'); +const addHelpTextThis3: commander.Command = program.addHelpText('before', () => 'before'); +const addHelpTextThis4: commander.Command = program.addHelpText('beforeAll', (context: commander.AddHelpTextContext) => { + if (context.error) { + return; // Can return nothing to skip display + } + return context.command.name(); +}); + // on -const onThis: commander.Command = program.on('postHelp', () => { +const onThis: commander.Command = program.on('command:foo', () => { // do nothing. }); diff --git a/typings/index.d.ts b/typings/index.d.ts index 60a510f31..6b8ed11c2 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -28,14 +28,14 @@ declare namespace commander { } interface HelpContext { // optional parameter for .help() and .outputHelp() error: boolean; - write?: (message: string) => void; } - interface HelpEventContext { // passed to help event listeners + interface AddHelpTextContext { // passed to text function used with .addHelpText() error: boolean; - write: (message: string) => void; command: Command; } + type AddHelpTextPosition = 'beforeAll' | 'before' | 'after' | 'afterAll'; + interface Command { [key: string]: any; // options as properties @@ -366,19 +366,22 @@ declare namespace commander { * Output help information and exit. * * Outputs built-in help, and customised by adding help event listeners. - * */ help(context?: HelpContext): never; help(cb?: (str: string) => string): never; // callback deprecated /** - * Add a listener (callback) for when events occur. (Implemented using EventEmitter.) + * Add additional text to be displayed with the built-in help, + * or add an override to replace the built-in help. * - * @example - * program - * .on('postHelp', () -> { - * console.log('See web site for more information.'); - * }); + * Position is 'before' or 'after' to affect just this command, + * and 'beforeAll' or 'afterAll'to affect this command and all its subcommands. + */ + addHelpText(position: AddHelpTextPosition, text: string): this; + addHelpText(position: AddHelpTextPosition, text: (context: AddHelpTextContext) => string | void): this; + + /** + * Add a listener (callback) for when events occur. (Implemented using EventEmitter.) */ on(event: string | symbol, listener: (...args: any[]) => void): this; } From 034a6877076c7abbc342b9b31322328daa6452d5 Mon Sep 17 00:00:00 2001 From: John Gee Date: Wed, 2 Sep 2020 20:58:43 +1200 Subject: [PATCH 30/36] Convert example to addHelpText --- examples/custom-help-listeners.js | 45 ------------------------------- examples/custom-help-text.js | 33 +++++++++++++++++++++++ 2 files changed, 33 insertions(+), 45 deletions(-) delete mode 100644 examples/custom-help-listeners.js create mode 100644 examples/custom-help-text.js diff --git a/examples/custom-help-listeners.js b/examples/custom-help-listeners.js deleted file mode 100644 index 3a065e90c..000000000 --- a/examples/custom-help-listeners.js +++ /dev/null @@ -1,45 +0,0 @@ -#!/usr/bin/env node - -// This example shows most of the help event listeners. - -// const { Command } = require('commander'); // (normal include) -const { Command } = require('../'); // include commander in git clone of commander repo -const program = new Command(); - -program.name('awesome'); - -program - .on('preGroupHelp', (context) => { - context.log('A W E S O M E'); - context.log(); - }) - .on('postGroupHelp', (context) => { - context.log(); - context.log('See web site for further help'); - }); - -program - .command('extra') - .on('preHelp', (context) => { - context.log('Note: the extra command does not do anything'); - context.log(); - }) - .on('postHelp', (context) => { - context.log(); - context.log('Examples:'); - context.log(' awesome extra --help'); - context.log(' awesome help extra'); - }); - -program - .command('replacement') - .on('help', (context) => { - context.log(`Completely custom help for ${context.command.name()}`); - }); - -program.parse(); - -// Try the following: -// node custom-help-listeners.js --help -// node custom-help-listeners.js extra --help -// node custom-help-listeners.js replacement --help diff --git a/examples/custom-help-text.js b/examples/custom-help-text.js new file mode 100644 index 000000000..c83414eda --- /dev/null +++ b/examples/custom-help-text.js @@ -0,0 +1,33 @@ +#!/usr/bin/env node + +// This example shows using addHelpText. + +// const { Command } = require('commander'); // (normal include) +const { Command } = require('../'); // include commander in git clone of commander repo +const program = new Command(); + +program.name('awesome'); + +program + .addHelpText('beforeAll', 'A W E S O M E\n') + .addHelpText('afterAll', (context) => { + if (context.error) { + return '\nHelp being displayed for an error'; + } + return '\nSee web site for further help'; + }); + +program + .command('extra') + .addHelpText('before', 'Note: the extra command does not do anything') + .addHelpText('after', ` +Examples: + awesome extra --help + awesome help extra`); + +program.parse(); + +// Try the following: +// node custom-help-text.js --help +// node custom-help-text.js extra --help +// node custom-help-text.js From f020a123a5bd3a47cb58c91568d26133de13144c Mon Sep 17 00:00:00 2001 From: John Gee Date: Wed, 2 Sep 2020 21:46:08 +1200 Subject: [PATCH 31/36] Update README --- Readme.md | 52 ++++++++++++++++++-------------------------- examples/custom-help | 10 ++++----- typings/index.d.ts | 2 +- 3 files changed, 26 insertions(+), 38 deletions(-) diff --git a/Readme.md b/Readme.md index 6542d9451..56d2ad9d6 100644 --- a/Readme.md +++ b/Readme.md @@ -497,14 +497,7 @@ shell spawn --help ### Custom help -You can display extra information or even replace the built-in help by listening for special help events. There are three events just for the single command, and three "group" events which occur for the command as well as any of its subcommands. -In the order the events are emitted: - -- `preGroupHelp`: use for adding a global banner or header -- `preHelp`: display extra information before built-in help -- `help` | `groupHelp`: if there is a listener, the event is emitted _instead_ of displaying the built-in help -- `postHelp`: display extra information after built-in help -- `postGroupHelp`: use for adding a global footer (sometimes called an epilog) +You can add extra text to be displayed along with the built-in help. Example file: [custom-help](./examples/custom-help) @@ -512,12 +505,10 @@ Example file: [custom-help](./examples/custom-help) program .option('-f, --foo', 'enable some foo'); -// must be added before calling .parse() -program.on('postHelp', () => { - console.log(''); - console.log('Example call:'); - console.log(' $ custom-help --help'); -}); +program.addHelpText('after', ` + +Example call: + $ custom-help --help`); ``` Yields the following help output: @@ -533,19 +524,19 @@ Example call: $ custom-help --help ``` -The help listeners are passed a context object for your convenience. The properties are: +The positions in order displayed are: -- error: a boolean whether the help is being displayed due to a usage error -- command: the Command which is displaying the help -- write: either `process.stdout.write()` or `.process.stderr.write()`, depending on `error` +- `beforeAll`: add to the program for a global banner or header +- `before`: display extra information before built-in help +- `after`: display extra information after built-in help +- `afterAll`: add to the program for a global footer ( epilog) -As an example, the built-in help can be replaced like this: +The positions "beforeAll" and "afterAll" apply to the command and all its subcommands. -```js -program.on('groupHelp', (context) => { - context.write(context.command.helpInformation()); -}); -``` +The second parameter can be a string, or a function returning a string. The function is passed a context object for your convenience. The properties are: + +- error: a boolean for whether the help is being displayed due to a usage error +- command: the Command which is displaying the help ### .usage and .name @@ -769,13 +760,12 @@ program .option("-e, --exec_mode ", "Which exec mode to use") .action(function(cmd, options){ console.log('exec "%s" using %s mode', cmd, options.exec_mode); - }).on('postHelp', function() { - console.log(''); - console.log('Examples:'); - console.log(''); - console.log(' $ deploy exec sequential'); - console.log(' $ deploy exec async'); - }); + }).addHelpText('after', ` +Examples: + + $ deploy exec sequential + $ deploy exec async + ); program.parse(process.argv); ``` diff --git a/examples/custom-help b/examples/custom-help index 73cefd4ef..53d43ced2 100755 --- a/examples/custom-help +++ b/examples/custom-help @@ -10,12 +10,10 @@ const program = new Command(); program .option('-f, --foo', 'enable some foo'); -// must be added before calling .parse() -program.on('postHelp', () => { - console.log(''); - console.log('Example call:'); - console.log(' $ custom-help --help'); -}); +program.addHelpText('after', ` + +Example call: + $ custom-help --help`); program.parse(process.argv); diff --git a/typings/index.d.ts b/typings/index.d.ts index 6b8ed11c2..f31373265 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -375,7 +375,7 @@ declare namespace commander { * or add an override to replace the built-in help. * * Position is 'before' or 'after' to affect just this command, - * and 'beforeAll' or 'afterAll'to affect this command and all its subcommands. + * and 'beforeAll' or 'afterAll' to affect this command and all its subcommands. */ addHelpText(position: AddHelpTextPosition, text: string): this; addHelpText(position: AddHelpTextPosition, text: (context: AddHelpTextContext) => string | void): this; From d027d52e032c4aacaa4dc4be762cdee392f437e4 Mon Sep 17 00:00:00 2001 From: John Gee Date: Wed, 2 Sep 2020 21:56:48 +1200 Subject: [PATCH 32/36] Add info about new .help param --- Readme.md | 7 +++---- index.js | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/Readme.md b/Readme.md index 56d2ad9d6..cb7fba1e1 100644 --- a/Readme.md +++ b/Readme.md @@ -557,16 +557,15 @@ Usage: my-command [global options] command ### .help() -Output help information and exit immediately. +Output help information and exit immediately. You can optionally pass `{ error: true }` to display on stderr and exit with an error status. ### .outputHelp() -Output help information without exiting. +Output help information without exiting. You can optionally pass `{ error: true }` to display on stderr. ### .helpInformation() -Get the command help information as a string for processing or displaying yourself. (The text does not include the custom help -from `--help` listeners.) +Get the built-in command help information as a string for processing or displaying yourself. ### .helpOption(flags, description) diff --git a/index.js b/index.js index 3fee023db..df98ef6ee 100644 --- a/index.js +++ b/index.js @@ -1671,7 +1671,7 @@ Read more on https://git.io/JJc0W`); } context.write(helpInformation); - this.emit(this._helpLongFlag); // Leave in for backwards compatibility + this.emit(this._helpLongFlag); // deprecated this.emit('afterHelp', context); groupListeners.forEach(command => command.emit('afterAllHelp', context)); }; From 06c3b6209de2d26238b62f78d380d273d4e2db3d Mon Sep 17 00:00:00 2001 From: John Gee Date: Wed, 2 Sep 2020 22:26:43 +1200 Subject: [PATCH 33/36] Remove excess space --- Readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Readme.md b/Readme.md index cb7fba1e1..0413af1c3 100644 --- a/Readme.md +++ b/Readme.md @@ -529,7 +529,7 @@ The positions in order displayed are: - `beforeAll`: add to the program for a global banner or header - `before`: display extra information before built-in help - `after`: display extra information after built-in help -- `afterAll`: add to the program for a global footer ( epilog) +- `afterAll`: add to the program for a global footer (epilog) The positions "beforeAll" and "afterAll" apply to the command and all its subcommands. From e9f4f6f98b955377d1c5b80a7256e4b98c83dd4f Mon Sep 17 00:00:00 2001 From: John Gee Date: Wed, 2 Sep 2020 22:34:02 +1200 Subject: [PATCH 34/36] Update more examples --- Readme.md | 3 +-- examples/custom-help | 2 +- examples/deploy | 14 ++++++-------- 3 files changed, 8 insertions(+), 11 deletions(-) diff --git a/Readme.md b/Readme.md index 0413af1c3..82fc4d43c 100644 --- a/Readme.md +++ b/Readme.md @@ -761,9 +761,8 @@ program console.log('exec "%s" using %s mode', cmd, options.exec_mode); }).addHelpText('after', ` Examples: - $ deploy exec sequential - $ deploy exec async + $ deploy exec async` ); program.parse(process.argv); diff --git a/examples/custom-help b/examples/custom-help index 53d43ced2..66ebdb2dc 100755 --- a/examples/custom-help +++ b/examples/custom-help @@ -1,6 +1,6 @@ #!/usr/bin/env node -// This example shows the postHelp event listener. +// This example shows a simple use of addHelpText. // This is used as an example in the README. // const { Command } = require('commander'); // (normal include) diff --git a/examples/deploy b/examples/deploy index 6c632bf26..3e9c3909f 100755 --- a/examples/deploy +++ b/examples/deploy @@ -27,12 +27,10 @@ program .option('-e, --exec_mode ', 'Which exec mode to use') .action(function(cmd, options) { console.log('exec "%s" using %s mode', cmd, options.exec_mode); - }).on('postHelp', function() { - console.log(' Examples:'); - console.log(); - console.log(' $ deploy exec sequential'); - console.log(' $ deploy exec async'); - console.log(); - }); - + }).addHelpText('after', ` +Examples: + $ deploy exec sequential + $ deploy exec async` + ); + program.parse(process.argv); From 6b31ef6940bb3c744b210d484727c166210e899d Mon Sep 17 00:00:00 2001 From: John Gee Date: Sat, 5 Sep 2020 10:05:08 +1200 Subject: [PATCH 35/36] Remove references to override --- index.js | 7 +++---- typings/index.d.ts | 3 +-- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/index.js b/index.js index df98ef6ee..4a2250c86 100644 --- a/index.js +++ b/index.js @@ -1722,13 +1722,12 @@ Read more on https://git.io/JJc0W`); }; /** - * Add additional text to be displayed with the built-in help, - * or add an override to replace the built-in help. + * Add additional text to be displayed with the built-in help. * - * Position is 'before' or 'after' or 'override' to affect just this command, + * Position is 'before' or 'after' to affect just this command, * and 'beforeAll' or 'afterAll' to affect this command and all its subcommands. * - * @param {string} position - before or after or override built-in help + * @param {string} position - before or after built-in help * @param {string | Function} text - string to add, or a function returning a string * @return {Command} `this` command for chaining */ diff --git a/typings/index.d.ts b/typings/index.d.ts index f31373265..c0d0c1500 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -371,8 +371,7 @@ declare namespace commander { help(cb?: (str: string) => string): never; // callback deprecated /** - * Add additional text to be displayed with the built-in help, - * or add an override to replace the built-in help. + * Add additional text to be displayed with the built-in help. * * Position is 'before' or 'after' to affect just this command, * and 'beforeAll' or 'afterAll' to affect this command and all its subcommands. From a78aff462ff7c2c557742855fa47ea055ed90242 Mon Sep 17 00:00:00 2001 From: John Gee Date: Sat, 5 Sep 2020 10:21:54 +1200 Subject: [PATCH 36/36] Update docs with .addHelpText --- index.js | 4 ++-- typings/index.d.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/index.js b/index.js index 4a2250c86..b367dadaf 100644 --- a/index.js +++ b/index.js @@ -1638,7 +1638,7 @@ Read more on https://git.io/JJc0W`); /** * Output help information for this command. * - * Outputs built-in help, and customised by adding help event listeners. + * Outputs built-in help, and custom text added using `.addHelpText()`. * * @api public * @param {Object} [contextOptions] - Can optionally pass in `{ error: true }` to write to stderr @@ -1705,7 +1705,7 @@ Read more on https://git.io/JJc0W`); /** * Output help information and exit. * - * Outputs built-in help, and customised by adding help event listeners. + * Outputs built-in help, and custom text added using `.addHelpText()`. * * @param {Object} [contextOptions] - optionally pass in `{ error: true }` to write to stderr * @api public diff --git a/typings/index.d.ts b/typings/index.d.ts index c0d0c1500..700c51cdf 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -344,7 +344,7 @@ declare namespace commander { /** * Output help information for this command. * - * Outputs built-in help, and customised by adding help event listeners. + * Outputs built-in help, and custom text added using `.addHelpText()`. * */ outputHelp(context?: HelpContext): void; @@ -365,7 +365,7 @@ declare namespace commander { /** * Output help information and exit. * - * Outputs built-in help, and customised by adding help event listeners. + * Outputs built-in help, and custom text added using `.addHelpText()`. */ help(context?: HelpContext): never; help(cb?: (str: string) => string): never; // callback deprecated