From 64537e890b15a4e388d2cbc80459f214336db960 Mon Sep 17 00:00:00 2001 From: John Gee Date: Thu, 3 Dec 2020 21:14:11 +1300 Subject: [PATCH 01/24] First cut at storing options safely by default --- Readme.md | 5 +- examples/storeOptionsAsProperties-action.js | 3 +- index.js | 75 +-------------------- tests/args.literal.test.js | 5 +- tests/args.variadic.test.js | 8 +-- tests/command.action.test.js | 26 ++----- tests/command.chain.test.js | 6 -- tests/commander.configureCommand.test.js | 41 +---------- tests/deprecated.test.js | 6 +- tests/options.bool.combo.test.js | 28 ++++---- tests/options.bool.small.combined.test.js | 4 +- tests/options.bool.test.js | 40 +++++------ tests/options.camelcase.test.js | 12 ++-- tests/options.custom-processing.test.js | 18 ++--- tests/options.detectClash.test.js | 52 -------------- tests/options.flags.test.js | 20 +++--- tests/options.mandatory.test.js | 24 +++---- tests/options.optional.test.js | 14 ++-- tests/options.opts.test.js | 3 +- tests/options.required.test.js | 8 +-- tests/options.values.test.js | 18 ++--- typings/commander-tests.ts | 4 -- typings/index.d.ts | 8 --- 23 files changed, 120 insertions(+), 308 deletions(-) delete mode 100644 tests/options.detectClash.test.js diff --git a/Readme.md b/Readme.md index 6f5b15afc..2370206c8 100644 --- a/Readme.md +++ b/Readme.md @@ -699,15 +699,12 @@ existing properties of Command. There are two new routines to change the behaviour, and the default behaviour may change in the future: - `storeOptionsAsProperties`: whether to store option values as properties on command object, or store separately (specify false) and access using `.opts()` -- `passCommandToAction`: whether to pass command to action handler, -or just the options (specify false) Example file: [storeOptionsAsProperties-action.js](./examples/storeOptionsAsProperties-action.js) ```js program - .storeOptionsAsProperties(false) - .passCommandToAction(false); + .storeOptionsAsProperties(false); program .name('my-program-name') diff --git a/examples/storeOptionsAsProperties-action.js b/examples/storeOptionsAsProperties-action.js index 8e9670ae2..7005da739 100755 --- a/examples/storeOptionsAsProperties-action.js +++ b/examples/storeOptionsAsProperties-action.js @@ -21,8 +21,7 @@ const commander = require('../'); // include commander in git clone of commander const program = new commander.Command(); program - .storeOptionsAsProperties(false) // <--- change behaviour - .passCommandToAction(false); // <--- change behaviour + .storeOptionsAsProperties(false); // <--- change behaviour program .name('my-program-name') diff --git a/index.js b/index.js index ed23283d6..5d3c8d54a 100644 --- a/index.js +++ b/index.js @@ -540,9 +540,7 @@ class Command extends EventEmitter { this._scriptPath = null; this._name = name || ''; this._optionValues = {}; - this._storeOptionsAsProperties = true; // backwards compatible by default - this._storeOptionsAsPropertiesCalled = false; - this._passCommandToAction = true; // backwards compatible by default + this._storeOptionsAsProperties = false; this._actionResults = []; this._actionHandler = null; this._executableHandler = false; @@ -631,7 +629,6 @@ class Command extends EventEmitter { cmd._helpConfiguration = this._helpConfiguration; cmd._exitCallback = this._exitCallback; cmd._storeOptionsAsProperties = this._storeOptionsAsProperties; - cmd._passCommandToAction = this._passCommandToAction; cmd._combineFlagAndOptionalValue = this._combineFlagAndOptionalValue; cmd._executableFile = opts.executableFile || null; // Custom name for executable file, set missing to null to match constructor @@ -893,15 +890,8 @@ class Command extends EventEmitter { // The .action callback takes an extra parameter which is the command or options. const expectedArgsCount = this._args.length; const actionArgs = args.slice(0, expectedArgsCount); - if (this._passCommandToAction) { - actionArgs[expectedArgsCount] = this; - } else { - actionArgs[expectedArgsCount] = this.opts(); - } - // Add the extra arguments so available too. - if (args.length > expectedArgsCount) { - actionArgs.push(args.slice(expectedArgsCount)); - } + actionArgs[expectedArgsCount] = this.opts(); + actionArgs.push(this); const actionResult = fn.apply(this, actionArgs); // Remember result in case it is async. Assume parseAsync getting called on root. @@ -915,49 +905,6 @@ class Command extends EventEmitter { return this; }; - /** - * Internal routine to check whether there is a clash storing option value with a Command property. - * - * @param {Option} option - * @api private - */ - - _checkForOptionNameClash(option) { - if (!this._storeOptionsAsProperties || this._storeOptionsAsPropertiesCalled) { - // Storing options safely, or user has been explicit and up to them. - return; - } - // User may override help, and hard to tell if worth warning. - if (option.name() === 'help') { - return; - } - - const commandProperty = this._getOptionValue(option.attributeName()); - if (commandProperty === undefined) { - // no clash - return; - } - - let foundClash = true; - if (option.negate) { - // It is ok if define foo before --no-foo. - const positiveLongFlag = option.long.replace(/^--no-/, '--'); - foundClash = !this._findOption(positiveLongFlag); - } else if (option.long) { - const negativeLongFlag = option.long.replace(/^--/, '--no-'); - foundClash = !this._findOption(negativeLongFlag); - } - - if (foundClash) { - throw new Error(`option '${option.name()}' clashes with existing property '${option.attributeName()}' on Command -- call storeOptionsAsProperties(false) to store option values safely, -- or call storeOptionsAsProperties(true) to suppress this check, -- or change option name - -Read more on https://git.io/JJc0W`); - } - }; - /** * Factory routine to create a new unattached option. * @@ -983,8 +930,6 @@ Read more on https://git.io/JJc0W`); const oname = option.name(); const name = option.attributeName(); - this._checkForOptionNameClash(option); - let defaultValue = option.defaultValue; // preassign default value for --no-*, [optional], , or plain flag if boolean value @@ -1179,7 +1124,6 @@ Read more on https://git.io/JJc0W`); */ storeOptionsAsProperties(storeAsProperties) { - this._storeOptionsAsPropertiesCalled = true; this._storeOptionsAsProperties = (storeAsProperties === undefined) || !!storeAsProperties; if (this.options.length) { throw new Error('call .storeOptionsAsProperties() before adding options'); @@ -1187,19 +1131,6 @@ Read more on https://git.io/JJc0W`); return this; }; - /** - * Whether to pass command to action handler, - * or just the options (specify false). - * - * @param {boolean} passCommand - * @return {Command} `this` command for chaining - */ - - passCommandToAction(passCommand) { - this._passCommandToAction = (passCommand === undefined) || !!passCommand; - return this; - }; - /** * Store option value * diff --git a/tests/args.literal.test.js b/tests/args.literal.test.js index 66cc9db42..3c3fad59e 100644 --- a/tests/args.literal.test.js +++ b/tests/args.literal.test.js @@ -12,8 +12,9 @@ test('when arguments includes -- then stop processing options', () => { .option('-b, --bar', 'add some bar'); program.parse(['node', 'test', '--foo', '--', '--bar', 'baz']); // More than one assert, ported from legacy test - expect(program.foo).toBe(true); - expect(program.bar).toBeUndefined(); + const opts = program.opts(); + expect(opts.foo).toBe(true); + expect(opts.bar).toBeUndefined(); expect(program.args).toEqual(['--bar', 'baz']); }); diff --git a/tests/args.variadic.test.js b/tests/args.variadic.test.js index 750880175..d04259da8 100644 --- a/tests/args.variadic.test.js +++ b/tests/args.variadic.test.js @@ -12,7 +12,7 @@ describe('variadic argument', () => { program.parse(['node', 'test', 'id']); - expect(actionMock).toHaveBeenCalledWith('id', [], program); + expect(actionMock).toHaveBeenCalledWith('id', [], program.opts(), program); }); test('when extra arguments specified for program then variadic arg is array of values', () => { @@ -25,7 +25,7 @@ describe('variadic argument', () => { program.parse(['node', 'test', 'id', ...extraArguments]); - expect(actionMock).toHaveBeenCalledWith('id', extraArguments, program); + expect(actionMock).toHaveBeenCalledWith('id', extraArguments, program.opts(), program); }); test('when no extra arguments specified for command then variadic arg is empty array', () => { @@ -37,7 +37,7 @@ describe('variadic argument', () => { program.parse(['node', 'test', 'sub']); - expect(actionMock).toHaveBeenCalledWith([], cmd); + expect(actionMock).toHaveBeenCalledWith([], cmd.opts(), cmd); }); test('when extra arguments specified for command then variadic arg is array of values', () => { @@ -50,7 +50,7 @@ describe('variadic argument', () => { program.parse(['node', 'test', 'sub', ...extraArguments]); - expect(actionMock).toHaveBeenCalledWith(extraArguments, cmd); + expect(actionMock).toHaveBeenCalledWith(extraArguments, cmd.opts(), cmd); }); test('when program variadic argument not last then error', () => { diff --git a/tests/command.action.test.js b/tests/command.action.test.js index cef18bee8..b1f936f36 100644 --- a/tests/command.action.test.js +++ b/tests/command.action.test.js @@ -9,7 +9,7 @@ test('when .action called then command passed to action', () => { .command('info') .action(actionMock); program.parse(['node', 'test', 'info']); - expect(actionMock).toHaveBeenCalledWith(cmd); + expect(actionMock).toHaveBeenCalledWith(cmd.opts(), cmd); }); test('when .action called then program.args only contains args', () => { @@ -23,18 +23,6 @@ test('when .action called then program.args only contains args', () => { expect(program.args).toEqual(['info', 'my-file']); }); -test('when .action called with extra arguments then extras also passed to action', () => { - // This is a new and undocumented behaviour for now. - // Might make this an error by default in future. - const actionMock = jest.fn(); - const program = new commander.Command(); - const cmd = program - .command('info ') - .action(actionMock); - program.parse(['node', 'test', 'info', 'my-file', 'a']); - expect(actionMock).toHaveBeenCalledWith('my-file', cmd, ['a']); -}); - test('when .action on program with required argument and argument supplied then action called', () => { const actionMock = jest.fn(); const program = new commander.Command(); @@ -42,7 +30,7 @@ test('when .action on program with required argument and argument supplied then .arguments('') .action(actionMock); program.parse(['node', 'test', 'my-file']); - expect(actionMock).toHaveBeenCalledWith('my-file', program); + expect(actionMock).toHaveBeenCalledWith('my-file', program.opts(), program); }); test('when .action on program with required argument and argument not supplied then action not called', () => { @@ -66,7 +54,7 @@ test('when .action on program and no arguments then action called', () => { program .action(actionMock); program.parse(['node', 'test']); - expect(actionMock).toHaveBeenCalledWith(program); + expect(actionMock).toHaveBeenCalledWith(program.opts(), program); }); test('when .action on program with optional argument supplied then action called', () => { @@ -76,7 +64,7 @@ test('when .action on program with optional argument supplied then action called .arguments('[file]') .action(actionMock); program.parse(['node', 'test', 'my-file']); - expect(actionMock).toHaveBeenCalledWith('my-file', program); + expect(actionMock).toHaveBeenCalledWith('my-file', program.opts(), program); }); test('when .action on program without optional argument supplied then action called', () => { @@ -86,7 +74,7 @@ test('when .action on program without optional argument supplied then action cal .arguments('[file]') .action(actionMock); program.parse(['node', 'test']); - expect(actionMock).toHaveBeenCalledWith(undefined, program); + expect(actionMock).toHaveBeenCalledWith(undefined, program.opts(), program); }); test('when .action on program with optional argument and subcommand and program argument then program action called', () => { @@ -100,7 +88,7 @@ test('when .action on program with optional argument and subcommand and program program.parse(['node', 'test', 'a']); - expect(actionMock).toHaveBeenCalledWith('a', program); + expect(actionMock).toHaveBeenCalledWith('a', program.opts(), program); }); // Changes made in #1062 to allow this case @@ -115,7 +103,7 @@ test('when .action on program with optional argument and subcommand and no progr program.parse(['node', 'test']); - expect(actionMock).toHaveBeenCalledWith(undefined, program); + expect(actionMock).toHaveBeenCalledWith(undefined, program.opts(), program); }); test('when action is async then can await parseAsync', async() => { diff --git a/tests/command.chain.test.js b/tests/command.chain.test.js index 9918dae0c..ced153a7f 100644 --- a/tests/command.chain.test.js +++ b/tests/command.chain.test.js @@ -76,12 +76,6 @@ describe('Command methods that should return this for chaining', () => { expect(result).toBe(program); }); - test('when call .passCommandToAction() then returns this', () => { - const program = new Command(); - const result = program.passCommandToAction(); - expect(result).toBe(program); - }); - test('when call .version() then returns this', () => { const program = new Command(); const result = program.version('1.2.3'); diff --git a/tests/commander.configureCommand.test.js b/tests/commander.configureCommand.test.js index 69ad7bcb4..82f3d59ca 100644 --- a/tests/commander.configureCommand.test.js +++ b/tests/commander.configureCommand.test.js @@ -9,17 +9,17 @@ test('when default then options stored on command', () => { program .option('--foo ', 'description'); program.parse(['node', 'test', '--foo', 'bar']); - expect(program.foo).toBe('bar'); + expect(program.opts().foo).toBe('bar'); }); -test('when default then command passed to action', () => { +test('when default then options+command passed to action', () => { const program = new commander.Command(); const callback = jest.fn(); program .arguments('') .action(callback); program.parse(['node', 'test', 'value']); - expect(callback).toHaveBeenCalledWith('value', program); + expect(callback).toHaveBeenCalledWith('value', program.opts(), program); }); // storeOptionsAsProperties @@ -50,38 +50,3 @@ test('when storeOptionsAsProperties(false) then options not stored on command', program.parse(['node', 'test', '--foo', 'bar']); expect(program.foo).toBeUndefined(); }); - -// passCommandToAction - -test('when passCommandToAction() then command passed to action', () => { - const program = new commander.Command(); - const callback = jest.fn(); - program - .passCommandToAction() - .arguments('') - .action(callback); - program.parse(['node', 'test', 'value']); - expect(callback).toHaveBeenCalledWith('value', program); -}); - -test('when passCommandToAction(true) then command passed to action', () => { - const program = new commander.Command(); - const callback = jest.fn(); - program - .passCommandToAction(true) - .arguments('') - .action(callback); - program.parse(['node', 'test', 'value']); - expect(callback).toHaveBeenCalledWith('value', program); -}); - -test('when passCommandToAction(false) then options passed to action', () => { - const program = new commander.Command(); - const callback = jest.fn(); - program - .passCommandToAction(false) - .arguments('') - .action(callback); - program.parse(['node', 'test', 'value']); - expect(callback).toHaveBeenCalledWith('value', program.opts()); -}); diff --git a/tests/deprecated.test.js b/tests/deprecated.test.js index ec08fd9e2..c23f48ce5 100644 --- a/tests/deprecated.test.js +++ b/tests/deprecated.test.js @@ -10,7 +10,7 @@ describe('option with regular expression instead of custom processing function', program .option('--cheese ', 'cheese type', /mild|tasty/, 'mild'); program.parse([], { from: 'user' }); - expect(program.cheese).toEqual('mild'); + expect(program.opts().cheese).toEqual('mild'); }); test('when argument matches regexp then value is as specified', () => { @@ -18,7 +18,7 @@ describe('option with regular expression instead of custom processing function', program .option('--cheese ', 'cheese type', /mild|tasty/, 'mild'); program.parse(['--cheese', 'tasty'], { from: 'user' }); - expect(program.cheese).toEqual('tasty'); + expect(program.opts().cheese).toEqual('tasty'); }); test('when argument does mot matches regexp then value is default', () => { @@ -26,6 +26,6 @@ describe('option with regular expression instead of custom processing function', program .option('--cheese ', 'cheese type', /mild|tasty/, 'mild'); program.parse(['--cheese', 'other'], { from: 'user' }); - expect(program.cheese).toEqual('mild'); + expect(program.opts().cheese).toEqual('mild'); }); }); diff --git a/tests/options.bool.combo.test.js b/tests/options.bool.combo.test.js index 0843cd5b7..5aed5724d 100644 --- a/tests/options.bool.combo.test.js +++ b/tests/options.bool.combo.test.js @@ -16,31 +16,31 @@ describe('boolean option combo with no default', () => { test('when boolean combo not specified then value is undefined', () => { const program = createPepperProgram(); program.parse(['node', 'test']); - expect(program.pepper).toBeUndefined(); + expect(program.opts().pepper).toBeUndefined(); }); test('when boolean combo positive then value is true', () => { const program = createPepperProgram(); program.parse(['node', 'test', '--pepper']); - expect(program.pepper).toBe(true); + expect(program.opts().pepper).toBe(true); }); test('when boolean combo negative then value is false', () => { const program = createPepperProgram(); program.parse(['node', 'test', '--no-pepper']); - expect(program.pepper).toBe(false); + expect(program.opts().pepper).toBe(false); }); test('when boolean combo last is positive then value is true', () => { const program = createPepperProgram(); program.parse(['node', 'test', '--no-pepper', '--pepper']); - expect(program.pepper).toBe(true); + expect(program.opts().pepper).toBe(true); }); test('when boolean combo last is negative then value is false', () => { const program = createPepperProgram(); program.parse(['node', 'test', '--pepper', '--no-pepper']); - expect(program.pepper).toBe(false); + expect(program.opts().pepper).toBe(false); }); }); @@ -59,19 +59,19 @@ describe('boolean option combo, default true, long flags', () => { test('when boolean combo not specified then value is true', () => { const program = createPepperProgramWithDefault(true); program.parse(['node', 'test']); - expect(program.pepper).toBe(true); + expect(program.opts().pepper).toBe(true); }); test('when boolean combo positive then value is true', () => { const program = createPepperProgramWithDefault(true); program.parse(['node', 'test', '--pepper']); - expect(program.pepper).toBe(true); + expect(program.opts().pepper).toBe(true); }); test('when boolean combo negative then value is false', () => { const program = createPepperProgramWithDefault(true); program.parse(['node', 'test', '--no-pepper']); - expect(program.pepper).toBe(false); + expect(program.opts().pepper).toBe(false); }); }); @@ -80,19 +80,19 @@ describe('boolean option combo, default false, short flags', () => { test('when boolean combo not specified then value is false', () => { const program = createPepperProgramWithDefault(false); program.parse(['node', 'test']); - expect(program.pepper).toBe(false); + expect(program.opts().pepper).toBe(false); }); test('when boolean combo positive then value is true', () => { const program = createPepperProgramWithDefault(false); program.parse(['node', 'test', '-p']); - expect(program.pepper).toBe(true); + expect(program.opts().pepper).toBe(true); }); test('when boolean combo negative then value is false', () => { const program = createPepperProgramWithDefault(false); program.parse(['node', 'test', '-P']); - expect(program.pepper).toBe(false); + expect(program.opts().pepper).toBe(false); }); }); @@ -105,20 +105,20 @@ describe('boolean option combo with non-boolean default', () => { const flagValue = 'red'; const program = createPepperProgramWithDefault(flagValue); program.parse(['node', 'test']); - expect(program.pepper).toBeUndefined(); + expect(program.opts().pepper).toBeUndefined(); }); test('when boolean combo positive then value is "default" value', () => { const flagValue = 'red'; const program = createPepperProgramWithDefault(flagValue); program.parse(['node', 'test', '--pepper']); - expect(program.pepper).toBe(flagValue); + expect(program.opts().pepper).toBe(flagValue); }); test('when boolean combo negative then value is false', () => { const flagValue = 'red'; const program = createPepperProgramWithDefault(flagValue); program.parse(['node', 'test', '--no-pepper']); - expect(program.pepper).toBe(false); + expect(program.opts().pepper).toBe(false); }); }); diff --git a/tests/options.bool.small.combined.test.js b/tests/options.bool.small.combined.test.js index 7b5ef1974..318ca2991 100644 --- a/tests/options.bool.small.combined.test.js +++ b/tests/options.bool.small.combined.test.js @@ -8,6 +8,6 @@ test('when when multiple short flags specified then all values are true', () => program.parse(['node', 'test', '-pc']); - expect(program.pepper).toBe(true); - expect(program.cheese).toBe(true); + expect(program.opts().pepper).toBe(true); + expect(program.opts().cheese).toBe(true); }); diff --git a/tests/options.bool.test.js b/tests/options.bool.test.js index e05825183..79626c7ed 100644 --- a/tests/options.bool.test.js +++ b/tests/options.bool.test.js @@ -9,7 +9,7 @@ describe('boolean flag on program', () => { program .option('--pepper', 'add pepper'); program.parse(['node', 'test']); - expect(program.pepper).toBeUndefined(); + expect(program.opts().pepper).toBeUndefined(); }); test('when boolean flag specified then value is true', () => { @@ -17,7 +17,7 @@ describe('boolean flag on program', () => { program .option('--pepper', 'add pepper'); program.parse(['node', 'test', '--pepper']); - expect(program.pepper).toBe(true); + expect(program.opts().pepper).toBe(true); }); test('when negatable boolean flag not specified then value is true', () => { @@ -25,7 +25,7 @@ describe('boolean flag on program', () => { program .option('--no-cheese', 'remove cheese'); program.parse(['node', 'test']); - expect(program.cheese).toBe(true); + expect(program.opts().cheese).toBe(true); }); test('when negatable boolean flag specified then value is false', () => { @@ -33,54 +33,54 @@ describe('boolean flag on program', () => { program .option('--no-cheese', 'remove cheese'); program.parse(['node', 'test', '--no-cheese']); - expect(program.cheese).toBe(false); + expect(program.opts().cheese).toBe(false); }); }); // boolean flag on command describe('boolean flag on command', () => { test('when boolean flag not specified then value is undefined', () => { - let subCommand; + let subCommandOptions; const program = new commander.Command(); program .command('sub') .option('--pepper', 'add pepper') - .action((cmd) => { subCommand = cmd; }); + .action((options) => { subCommandOptions = options; }); program.parse(['node', 'test', 'sub']); - expect(subCommand.pepper).toBeUndefined(); + expect(subCommandOptions.pepper).toBeUndefined(); }); test('when boolean flag specified then value is true', () => { - let subCommand; + let subCommandOptions; const program = new commander.Command(); program .command('sub') .option('--pepper', 'add pepper') - .action((cmd) => { subCommand = cmd; }); + .action((options) => { subCommandOptions = options; }); program.parse(['node', 'test', 'sub', '--pepper']); - expect(subCommand.pepper).toBe(true); + expect(subCommandOptions.pepper).toBe(true); }); test('when negatable boolean flag not specified then value is true', () => { - let subCommand; + let subCommandOptions; const program = new commander.Command(); program .command('sub') .option('--no-cheese', 'remove cheese') - .action((cmd) => { subCommand = cmd; }); + .action((options) => { subCommandOptions = options; }); program.parse(['node', 'test', 'sub']); - expect(subCommand.cheese).toBe(true); + expect(subCommandOptions.cheese).toBe(true); }); test('when negatable boolean flag specified then value is false', () => { - let subCommand; + let subCommandOptions; const program = new commander.Command(); program .command('sub') .option('--no-cheese', 'remove cheese') - .action((cmd) => { subCommand = cmd; }); + .action((options) => { subCommandOptions = options; }); program.parse(['node', 'test', 'sub', '--no-cheese']); - expect(subCommand.cheese).toBe(false); + expect(subCommandOptions.cheese).toBe(false); }); }); @@ -95,7 +95,7 @@ describe('boolean flag with non-boolean default', () => { program .option('--olives', 'Add olives? Sorry we only have black.', flagValue); program.parse(['node', 'test']); - expect(program.olives).toBeUndefined(); + expect(program.opts().olives).toBeUndefined(); }); test('when flag specified then value is "default" value', () => { @@ -104,7 +104,7 @@ describe('boolean flag with non-boolean default', () => { program .option('-v, --olives', 'Add olives? Sorry we only have black.', flagValue); program.parse(['node', 'test', '--olives']); - expect(program.olives).toBe(flagValue); + expect(program.opts().olives).toBe(flagValue); }); }); @@ -115,7 +115,7 @@ describe('regression test for -no- in middle of option flag', () => { program .option('--module-no-parse'); program.parse(['node', 'test']); - expect(program.moduleNoParse).toBeUndefined(); + expect(program.opts().moduleNoParse).toBeUndefined(); }); test('when flag specified then value is true', () => { @@ -123,6 +123,6 @@ describe('regression test for -no- in middle of option flag', () => { program .option('--module-no-parse'); program.parse(['node', 'test', '--module-no-parse']); - expect(program.moduleNoParse).toEqual(true); + expect(program.opts().moduleNoParse).toEqual(true); }); }); diff --git a/tests/options.camelcase.test.js b/tests/options.camelcase.test.js index 9de695cc7..2494350b9 100644 --- a/tests/options.camelcase.test.js +++ b/tests/options.camelcase.test.js @@ -7,7 +7,7 @@ test('when option defined with --word-word then option property is wordWord', () program .option('--my-option', 'description'); program.parse(['node', 'test', '--my-option']); - expect(program.myOption).toBe(true); + expect(program.opts().myOption).toBe(true); }); test('when option defined with --word-wORD then option property is wordWORD', () => { @@ -15,7 +15,7 @@ test('when option defined with --word-wORD then option property is wordWORD', () program .option('--my-oPTION', 'description'); program.parse(['node', 'test', '--my-oPTION']); - expect(program.myOPTION).toBe(true); + expect(program.opts().myOPTION).toBe(true); }); test('when option defined with --word-WORD then option property is wordWORD', () => { @@ -23,7 +23,7 @@ test('when option defined with --word-WORD then option property is wordWORD', () program .option('--my-OPTION', 'description'); program.parse(['node', 'test', '--my-OPTION']); - expect(program.myOPTION).toBe(true); + expect(program.opts().myOPTION).toBe(true); }); test('when option defined with --word-word-word then option property is wordWordWord', () => { @@ -31,7 +31,7 @@ test('when option defined with --word-word-word then option property is wordWord program .option('--my-special-option', 'description'); program.parse(['node', 'test', '--my-special-option']); - expect(program.mySpecialOption).toBe(true); + expect(program.opts().mySpecialOption).toBe(true); }); test('when option defined with --word-WORD-word then option property is wordWORDWord', () => { @@ -39,7 +39,7 @@ test('when option defined with --word-WORD-word then option property is wordWORD program .option('--my-SPECIAL-option', 'description'); program.parse(['node', 'test', '--my-SPECIAL-option']); - expect(program.mySPECIALOption).toBe(true); + expect(program.opts().mySPECIALOption).toBe(true); }); test('when option defined with --Word then option property is Word', () => { @@ -47,5 +47,5 @@ test('when option defined with --Word then option property is Word', () => { program .option('--Myoption', 'description'); program.parse(['node', 'test', '--Myoption']); - expect(program.Myoption).toBe(true); + expect(program.opts().Myoption).toBe(true); }); diff --git a/tests/options.custom-processing.test.js b/tests/options.custom-processing.test.js index 27956fe30..fb48c1661 100644 --- a/tests/options.custom-processing.test.js +++ b/tests/options.custom-processing.test.js @@ -31,7 +31,7 @@ test('when option not specified then value is undefined', () => { program .option('-i, --integer ', 'number', myParseInt); program.parse(['node', 'test']); - expect(program.integer).toBeUndefined(); + expect(program.opts().integer).toBeUndefined(); }); test('when starting value is defined and option not specified then callback not called', () => { @@ -50,7 +50,7 @@ test('when starting value is defined and option not specified then value is star program .option('-i, --integer ', 'number', myParseInt, startingValue); program.parse(['node', 'test']); - expect(program.integer).toBe(startingValue); + expect(program.opts().integer).toBe(startingValue); }); test('when option specified then callback called with value', () => { @@ -71,7 +71,7 @@ test('when option specified then value is as returned from callback', () => { return callbackResult; }); program.parse(['node', 'test', '-i', '0']); - expect(program.integer).toBe(callbackResult); + expect(program.opts().integer).toBe(callbackResult); }); test('when starting value is defined and option specified then callback called with value and starting value', () => { @@ -105,7 +105,7 @@ test('when option argument in choices then option set', () => { .exitOverride() .addOption(new commander.Option('--colour ').choices(['red', 'blue'])); program.parse(['--colour', 'red'], { from: 'user' }); - expect(program.colour).toBe('red'); + expect(program.opts().colour).toBe('red'); }); // Now some functional tests like the examples in the README! @@ -115,7 +115,7 @@ test('when parseFloat "1e2" then value is 100', () => { program .option('-f, --float ', 'float argument', parseFloat); program.parse(['node', 'test', '-f', '1e2']); - expect(program.float).toBe(100); + expect(program.opts().float).toBe(100); }); test('when myParseInt "1" then value is 1', () => { @@ -123,7 +123,7 @@ test('when myParseInt "1" then value is 1', () => { program .option('-i, --integer ', 'integer argument', myParseInt); program.parse(['node', 'test', '-i', '1']); - expect(program.integer).toBe(1); + expect(program.opts().integer).toBe(1); }); test('when increaseVerbosity -v -v -v then value is 3', () => { @@ -131,7 +131,7 @@ test('when increaseVerbosity -v -v -v then value is 3', () => { program .option('-v, --verbose', 'verbosity that can be increased', increaseVerbosity, 0); program.parse(['node', 'test', '-v', '-v', '-v']); - expect(program.verbose).toBe(3); + expect(program.opts().verbose).toBe(3); }); test('when collect -c a -c b -c c then value is [a, b, c]', () => { @@ -139,7 +139,7 @@ test('when collect -c a -c b -c c then value is [a, b, c]', () => { program .option('-c, --collect ', 'repeatable value', collect, []); program.parse(['node', 'test', '-c', 'a', '-c', 'b', '-c', 'c']); - expect(program.collect).toEqual(['a', 'b', 'c']); + expect(program.opts().collect).toEqual(['a', 'b', 'c']); }); test('when commaSeparatedList x,y,z then value is [x, y, z]', () => { @@ -147,5 +147,5 @@ test('when commaSeparatedList x,y,z then value is [x, y, z]', () => { program .option('-l, --list ', 'comma separated list', commaSeparatedList); program.parse(['node', 'test', '--list', 'x,y,z']); - expect(program.list).toEqual(['x', 'y', 'z']); + expect(program.opts().list).toEqual(['x', 'y', 'z']); }); diff --git a/tests/options.detectClash.test.js b/tests/options.detectClash.test.js deleted file mode 100644 index e9e9efd30..000000000 --- a/tests/options.detectClash.test.js +++ /dev/null @@ -1,52 +0,0 @@ -const commander = require('../'); - -// Check detection of likely name clashes - -test('when option clashes with property then throw', () => { - const program = new commander.Command(); - expect(() => { - program.option('-n, --name '); - }).toThrow(); -}); - -test('when option clashes with property and storeOptionsAsProperties(true) then ok', () => { - const program = new commander.Command(); - program.storeOptionsAsProperties(true); - expect(() => { - program.option('-n, --name '); - }).not.toThrow(); -}); - -test('when option would clash with property but storeOptionsAsProperties(false) then ok', () => { - const program = new commander.Command(); - program.storeOptionsAsProperties(false); - expect(() => { - program.option('-n, --name '); - }).not.toThrow(); -}); - -test('when negated option clashes with property then throw', () => { - const program = new commander.Command(); - expect(() => { - program.option('-E, --no-emit'); - }).toThrow(); -}); - -test('when positive and negative option then ok', () => { - const program = new commander.Command(); - expect(() => { - program - .option('-c, --colour', 'red') - .option('-C, --no-colour'); - }).not.toThrow(); -}); - -test('when negative and positive option then ok', () => { - // Not a likely pattern, but possible and not an error. - const program = new commander.Command(); - expect(() => { - program - .option('-C, --no-colour') - .option('-c, --colour'); - }).not.toThrow(); -}); diff --git a/tests/options.flags.test.js b/tests/options.flags.test.js index a43ce9c74..7e773b8b1 100644 --- a/tests/options.flags.test.js +++ b/tests/options.flags.test.js @@ -7,7 +7,7 @@ test('when only short flag defined and not specified then value is undefined', ( program .option('-p', 'add pepper'); program.parse(['node', 'test']); - expect(program.p).toBeUndefined(); + expect(program.opts().p).toBeUndefined(); }); test('when only short flag defined and specified then value is true', () => { @@ -15,7 +15,7 @@ test('when only short flag defined and specified then value is true', () => { program .option('-p', 'add pepper'); program.parse(['node', 'test', '-p']); - expect(program.p).toBe(true); + expect(program.opts().p).toBe(true); }); test('when only long flag defined and not specified then value is undefined', () => { @@ -23,7 +23,7 @@ test('when only long flag defined and not specified then value is undefined', () program .option('--pepper', 'add pepper'); program.parse(['node', 'test']); - expect(program.pepper).toBeUndefined(); + expect(program.opts().pepper).toBeUndefined(); }); test('when only long flag defined and specified then value is true', () => { @@ -31,7 +31,7 @@ test('when only long flag defined and specified then value is true', () => { program .option('--pepper', 'add pepper'); program.parse(['node', 'test', '--pepper']); - expect(program.pepper).toBe(true); + expect(program.opts().pepper).toBe(true); }); test('when "short,long" flags defined and short specified then value is true', () => { @@ -39,7 +39,7 @@ test('when "short,long" flags defined and short specified then value is true', ( program .option('-p,--pepper', 'add pepper'); program.parse(['node', 'test', '-p']); - expect(program.pepper).toBe(true); + expect(program.opts().pepper).toBe(true); }); test('when "short,long" flags defined and long specified then value is true', () => { @@ -47,7 +47,7 @@ test('when "short,long" flags defined and long specified then value is true', () program .option('-p,--pepper', 'add pepper'); program.parse(['node', 'test', '--pepper']); - expect(program.pepper).toBe(true); + expect(program.opts().pepper).toBe(true); }); test('when "short|long" flags defined and short specified then value is true', () => { @@ -55,7 +55,7 @@ test('when "short|long" flags defined and short specified then value is true', ( program .option('-p|--pepper', 'add pepper'); program.parse(['node', 'test', '-p']); - expect(program.pepper).toBe(true); + expect(program.opts().pepper).toBe(true); }); test('when "short|long" flags defined and long specified then value is true', () => { @@ -63,7 +63,7 @@ test('when "short|long" flags defined and long specified then value is true', () program .option('-p|--pepper', 'add pepper'); program.parse(['node', 'test', '--pepper']); - expect(program.pepper).toBe(true); + expect(program.opts().pepper).toBe(true); }); test('when "short long" flags defined and short specified then value is true', () => { @@ -71,7 +71,7 @@ test('when "short long" flags defined and short specified then value is true', ( program .option('-p --pepper', 'add pepper'); program.parse(['node', 'test', '-p']); - expect(program.pepper).toBe(true); + expect(program.opts().pepper).toBe(true); }); test('when "short long" flags defined and long specified then value is true', () => { @@ -79,5 +79,5 @@ test('when "short long" flags defined and long specified then value is true', () program .option('-p --pepper', 'add pepper'); program.parse(['node', 'test', '--pepper']); - expect(program.pepper).toBe(true); + expect(program.opts().pepper).toBe(true); }); diff --git a/tests/options.mandatory.test.js b/tests/options.mandatory.test.js index 4abb019f3..0d1f44cfe 100644 --- a/tests/options.mandatory.test.js +++ b/tests/options.mandatory.test.js @@ -10,7 +10,7 @@ describe('required program option with mandatory value specified', () => { .exitOverride() .requiredOption('--cheese ', 'cheese type'); program.parse(['node', 'test', '--cheese', 'blue']); - expect(program.cheese).toBe('blue'); + expect(program.opts().cheese).toBe('blue'); }); test('when program has option with name different than property then still recognised', () => { @@ -19,7 +19,7 @@ describe('required program option with mandatory value specified', () => { .exitOverride() .requiredOption('--cheese-type ', 'cheese type'); program.parse(['node', 'test', '--cheese-type', 'blue']); - expect(program.cheeseType).toBe('blue'); + expect(program.opts().cheeseType).toBe('blue'); }); test('when program has required value default then default value', () => { @@ -28,7 +28,7 @@ describe('required program option with mandatory value specified', () => { .exitOverride() .requiredOption('--cheese ', 'cheese type', 'default'); program.parse(['node', 'test']); - expect(program.cheese).toBe('default'); + expect(program.opts().cheese).toBe('default'); }); test('when program has optional value flag specified then true', () => { @@ -37,7 +37,7 @@ describe('required program option with mandatory value specified', () => { .exitOverride() .requiredOption('--cheese [type]', 'cheese type'); program.parse(['node', 'test', '--cheese']); - expect(program.cheese).toBe(true); + expect(program.opts().cheese).toBe(true); }); test('when program has optional value default then default value', () => { @@ -46,7 +46,7 @@ describe('required program option with mandatory value specified', () => { .exitOverride() .requiredOption('--cheese [type]', 'cheese type', 'default'); program.parse(['node', 'test']); - expect(program.cheese).toBe('default'); + expect(program.opts().cheese).toBe('default'); }); test('when program has value/no flag specified with value then specified value', () => { @@ -56,7 +56,7 @@ describe('required program option with mandatory value specified', () => { .requiredOption('--cheese ', 'cheese type') .requiredOption('--no-cheese', 'no cheese thanks'); program.parse(['node', 'test', '--cheese', 'blue']); - expect(program.cheese).toBe('blue'); + expect(program.opts().cheese).toBe('blue'); }); test('when program has mandatory-yes/no flag specified with flag then true', () => { @@ -66,7 +66,7 @@ describe('required program option with mandatory value specified', () => { .requiredOption('--cheese', 'cheese type') .option('--no-cheese', 'no cheese thanks'); program.parse(['node', 'test', '--cheese']); - expect(program.cheese).toBe(true); + expect(program.opts().cheese).toBe(true); }); test('when program has mandatory-yes/mandatory-no flag specified with flag then true', () => { @@ -76,7 +76,7 @@ describe('required program option with mandatory value specified', () => { .requiredOption('--cheese', 'cheese type') .requiredOption('--no-cheese', 'no cheese thanks'); program.parse(['node', 'test', '--cheese']); - expect(program.cheese).toBe(true); + expect(program.opts().cheese).toBe(true); }); test('when program has yes/no flag specified negated then false', () => { @@ -86,7 +86,7 @@ describe('required program option with mandatory value specified', () => { .requiredOption('--cheese ', 'cheese type') .option('--no-cheese', 'no cheese thanks'); program.parse(['node', 'test', '--no-cheese']); - expect(program.cheese).toBe(false); + expect(program.opts().cheese).toBe(false); }); test('when program has required value specified and subcommand then specified value', () => { @@ -97,7 +97,7 @@ describe('required program option with mandatory value specified', () => { .command('sub') .action(() => {}); program.parse(['node', 'test', '--cheese', 'blue', 'sub']); - expect(program.cheese).toBe('blue'); + expect(program.opts().cheese).toBe('blue'); }); }); @@ -173,8 +173,8 @@ describe('required command option with mandatory value specified', () => { .exitOverride() .command('sub') .requiredOption('--subby ', 'description') - .action((cmd) => { - cmdOptions = cmd; + .action((options) => { + cmdOptions = options; }); program.parse(['node', 'test', 'sub', '--subby', 'blue']); diff --git a/tests/options.optional.test.js b/tests/options.optional.test.js index 52b766508..bf13dc9b6 100644 --- a/tests/options.optional.test.js +++ b/tests/options.optional.test.js @@ -7,7 +7,7 @@ describe('option with optional value, no default', () => { program .option('--cheese [type]', 'cheese type'); program.parse(['node', 'test']); - expect(program.cheese).toBeUndefined(); + expect(program.opts().cheese).toBeUndefined(); }); test('when option specified then value is as specified', () => { @@ -16,7 +16,7 @@ describe('option with optional value, no default', () => { .option('--cheese [type]', 'cheese type'); const cheeseType = 'blue'; program.parse(['node', 'test', '--cheese', cheeseType]); - expect(program.cheese).toBe(cheeseType); + expect(program.opts().cheese).toBe(cheeseType); }); test('when option specified without value then value is true', () => { @@ -24,7 +24,7 @@ describe('option with optional value, no default', () => { program .option('--cheese [type]', 'cheese type'); program.parse(['node', 'test', '--cheese']); - expect(program.cheese).toBe(true); + expect(program.opts().cheese).toBe(true); }); test('when option specified without value and following option then value is true', () => { @@ -34,7 +34,7 @@ describe('option with optional value, no default', () => { .option('--cheese [type]', 'cheese type') .option('--some-option'); program.parse(['node', 'test', '--cheese', '--some-option']); - expect(program.cheese).toBe(true); + expect(program.opts().cheese).toBe(true); }); }); @@ -46,7 +46,7 @@ describe('option with optional value, with default', () => { program .option('--cheese [type]', 'cheese type', defaultValue); program.parse(['node', 'test']); - expect(program.cheese).toBe(defaultValue); + expect(program.opts().cheese).toBe(defaultValue); }); test('when option specified then value is as specified', () => { @@ -56,7 +56,7 @@ describe('option with optional value, with default', () => { .option('--cheese [type]', 'cheese type', defaultValue); const cheeseType = 'blue'; program.parse(['node', 'test', '--cheese', cheeseType]); - expect(program.cheese).toBe(cheeseType); + expect(program.opts().cheese).toBe(cheeseType); }); test('when option specified without value then value is default', () => { @@ -65,6 +65,6 @@ describe('option with optional value, with default', () => { program .option('--cheese [type]', 'cheese type', defaultValue); program.parse(['node', 'test', '--cheese']); - expect(program.cheese).toBe(defaultValue); + expect(program.opts().cheese).toBe(defaultValue); }); }); diff --git a/tests/options.opts.test.js b/tests/options.opts.test.js index 214afa7ec..a3655a623 100644 --- a/tests/options.opts.test.js +++ b/tests/options.opts.test.js @@ -3,10 +3,11 @@ const commander = require('../'); // Test the `.opts()` way of accessing option values. // Basic coverage of the main option types (leaving out negatable flags and options with optional values). -test('when .version used then version in opts', () => { +test('when .version used with storeOptionsAsProperties() then version in opts', () => { const program = new commander.Command(); const version = '0.0.1'; program + .storeOptionsAsProperties() .version(version); program.parse(['node', 'test']); expect(program.opts()).toEqual({ version }); diff --git a/tests/options.required.test.js b/tests/options.required.test.js index 3e0a72225..77b376f57 100644 --- a/tests/options.required.test.js +++ b/tests/options.required.test.js @@ -7,7 +7,7 @@ describe('option with required value, no default', () => { program .option('--cheese ', 'cheese type'); program.parse(['node', 'test']); - expect(program.cheese).toBeUndefined(); + expect(program.opts().cheese).toBeUndefined(); }); test('when option specified then value is as specified', () => { @@ -16,7 +16,7 @@ describe('option with required value, no default', () => { .option('--cheese ', 'cheese type'); const cheeseType = 'blue'; program.parse(['node', 'test', '--cheese', cheeseType]); - expect(program.cheese).toBe(cheeseType); + expect(program.opts().cheese).toBe(cheeseType); }); test('when option value not specified then error', () => { @@ -47,7 +47,7 @@ describe('option with required value, with default', () => { program .option('--cheese ', 'cheese type', defaultValue); program.parse(['node', 'test']); - expect(program.cheese).toBe(defaultValue); + expect(program.opts().cheese).toBe(defaultValue); }); test('when option specified then value is as specified', () => { @@ -57,7 +57,7 @@ describe('option with required value, with default', () => { .option('--cheese ', 'cheese type', defaultValue); const cheeseType = 'blue'; program.parse(['node', 'test', '--cheese', cheeseType]); - expect(program.cheese).toBe(cheeseType); + expect(program.opts().cheese).toBe(cheeseType); }); test('when option value not specified then error', () => { diff --git a/tests/options.values.test.js b/tests/options.values.test.js index 43117558e..65d338d55 100644 --- a/tests/options.values.test.js +++ b/tests/options.values.test.js @@ -18,25 +18,25 @@ describe.each([['str'], ['80'], ['-'], ['-5'], ['--flag']])( test('when short flag followed by value then value is as specified', () => { const program = createPortProgram(); program.parse(['node', 'test', '-p', value]); - expect(program.port).toBe(value); + expect(program.opts().port).toBe(value); }); test('when short flag concatenated with value then value is as specified', () => { const program = createPortProgram(); program.parse(['node', 'test', `-p${value}`]); - expect(program.port).toBe(value); + expect(program.opts().port).toBe(value); }); test('when long flag followed by value then value is as specified', () => { const program = createPortProgram(); program.parse(['node', 'test', '--port', value]); - expect(program.port).toBe(value); + expect(program.opts().port).toBe(value); }); test('when long flag = value then value is as specified', () => { const program = createPortProgram(); program.parse(['node', 'test', `--port=${value}`]); - expect(program.port).toBe(value); + expect(program.opts().port).toBe(value); }); }); @@ -52,25 +52,25 @@ describe('option with optional value', () => { test('when short flag followed by value then value is as specified', () => { const program = createPortProgram(); program.parse('node test -p 80'.split(' ')); - expect(program.port).toBe('80'); + expect(program.opts().port).toBe('80'); }); test('when short flag concatenated with value then value is as specified', () => { const program = createPortProgram(); program.parse('node test -p80'.split(' ')); - expect(program.port).toBe('80'); + expect(program.opts().port).toBe('80'); }); test('when long flag followed by value then value is as specified', () => { const program = createPortProgram(); program.parse('node test --port 80'.split(' ')); - expect(program.port).toBe('80'); + expect(program.opts().port).toBe('80'); }); test('when long flag = value then value is as specified', () => { const program = createPortProgram(); program.parse('node test --port=80'.split(' ')); - expect(program.port).toBe('80'); + expect(program.opts().port).toBe('80'); }); test('when long flag followed empty string then value is empty string', () => { @@ -78,6 +78,6 @@ describe('option with optional value', () => { program .option('-c, --cheese [type]', 'optionally specify the type of cheese'); program.parse(['node', 'test', '--cheese', '']); - expect(program.cheese).toBe(''); + expect(program.opts().cheese).toBe(''); }); }); diff --git a/typings/commander-tests.ts b/typings/commander-tests.ts index fed35998c..05b310829 100644 --- a/typings/commander-tests.ts +++ b/typings/commander-tests.ts @@ -147,10 +147,6 @@ const addOptionThis: commander.Command = program.addOption(new commander.Option( const storeOptionsAsPropertiesThis1: commander.Command = program.storeOptionsAsProperties(); const storeOptionsAsPropertiesThis2: commander.Command = program.storeOptionsAsProperties(false); -// passCommandToAction -const passCommandToActionThis1: commander.Command = program.passCommandToAction(); -const passCommandToActionThis2: commander.Command = program.passCommandToAction(false); - // combineFlagAndOptionalValue const combineFlagAndOptionalValueThis1: commander.Command = program.combineFlagAndOptionalValue(); const combineFlagAndOptionalValueThis2: commander.Command = program.combineFlagAndOptionalValue(false); diff --git a/typings/index.d.ts b/typings/index.d.ts index 1baa20922..bf3628525 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -376,14 +376,6 @@ declare namespace commander { */ storeOptionsAsProperties(storeAsProperties?: boolean): this; - /** - * Whether to pass command to action handler, - * or just the options (specify false). - * - * @returns `this` command for chaining - */ - passCommandToAction(passCommand?: boolean): this; - /** * Alter parsing of short flags with optional values. * From 9ab4ad278d609aa9344b1541de2279af15c164b8 Mon Sep 17 00:00:00 2001 From: John Gee Date: Fri, 4 Dec 2020 11:33:26 +1300 Subject: [PATCH 02/24] Pass command as options for legacy behaviour --- index.js | 6 ++++- tests/commander.configureCommand.test.js | 28 +++++++++++++++++++++++- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/index.js b/index.js index 5d3c8d54a..2c8d5fc58 100644 --- a/index.js +++ b/index.js @@ -890,7 +890,11 @@ class Command extends EventEmitter { // The .action callback takes an extra parameter which is the command or options. const expectedArgsCount = this._args.length; const actionArgs = args.slice(0, expectedArgsCount); - actionArgs[expectedArgsCount] = this.opts(); + if (this._storeOptionsAsProperties) { + actionArgs[expectedArgsCount] = this; // backwards compatible "options" + } else { + actionArgs[expectedArgsCount] = this.opts(); + } actionArgs.push(this); const actionResult = fn.apply(this, actionArgs); diff --git a/tests/commander.configureCommand.test.js b/tests/commander.configureCommand.test.js index 82f3d59ca..3595d820a 100644 --- a/tests/commander.configureCommand.test.js +++ b/tests/commander.configureCommand.test.js @@ -4,11 +4,12 @@ const commander = require('../'); // Default behaviours -test('when default then options stored on command', () => { +test('when default then options not stored on command', () => { const program = new commander.Command(); program .option('--foo ', 'description'); program.parse(['node', 'test', '--foo', 'bar']); + expect(program.foo).toBeUndefined(); expect(program.opts().foo).toBe('bar'); }); @@ -31,6 +32,7 @@ test('when storeOptionsAsProperties() then options stored on command', () => { .option('--foo ', 'description'); program.parse(['node', 'test', '--foo', 'bar']); expect(program.foo).toBe('bar'); + expect(program.opts().foo).toBe('bar'); }); test('when storeOptionsAsProperties(true) then options stored on command', () => { @@ -40,6 +42,7 @@ test('when storeOptionsAsProperties(true) then options stored on command', () => .option('--foo ', 'description'); program.parse(['node', 'test', '--foo', 'bar']); expect(program.foo).toBe('bar'); + expect(program.opts().foo).toBe('bar'); }); test('when storeOptionsAsProperties(false) then options not stored on command', () => { @@ -49,4 +52,27 @@ test('when storeOptionsAsProperties(false) then options not stored on command', .option('--foo ', 'description'); program.parse(['node', 'test', '--foo', 'bar']); expect(program.foo).toBeUndefined(); + expect(program.opts().foo).toBe('bar'); +}); + +test('when storeOptionsAsProperties() then command+command passed to action', () => { + const program = new commander.Command(); + const callback = jest.fn(); + program + .storeOptionsAsProperties() + .arguments('') + .action(callback); + program.parse(['node', 'test', 'value']); + expect(callback).toHaveBeenCalledWith('value', program, program); +}); + +test('when storeOptionsAsProperties(false) then opts+command passed to action', () => { + const program = new commander.Command(); + const callback = jest.fn(); + program + .storeOptionsAsProperties(false) + .arguments('') + .action(callback); + program.parse(['node', 'test', 'value']); + expect(callback).toHaveBeenCalledWith('value', program.opts(), program); }); From d3ce415443f502e397d96f2bb51a2716df8c9ffa Mon Sep 17 00:00:00 2001 From: John Gee Date: Fri, 4 Dec 2020 20:30:38 +1300 Subject: [PATCH 03/24] Update examples for new patterns --- examples/custom-command-class.js | 8 ++-- examples/custom-command-function.js | 8 ++-- examples/custom-help-description | 2 +- examples/defaultCommand.js | 4 +- examples/storeOptionsAsProperties-action.js | 40 -------------------- examples/storeOptionsAsProperties-opts.js | 39 ------------------- examples/storeOptionsAsProperties-problem.js | 36 ------------------ tests/options.mandatory.test.js | 4 +- 8 files changed, 13 insertions(+), 128 deletions(-) delete mode 100755 examples/storeOptionsAsProperties-action.js delete mode 100755 examples/storeOptionsAsProperties-opts.js delete mode 100755 examples/storeOptionsAsProperties-problem.js diff --git a/examples/custom-command-class.js b/examples/custom-command-class.js index cda3d06ac..e76e7a880 100755 --- a/examples/custom-command-class.js +++ b/examples/custom-command-class.js @@ -18,14 +18,14 @@ const program = new MyCommand(); program .command('serve') .option('--port ', 'specify port number', 80) - .action((cmd) => { - if (cmd.debug) { + .action((options) => { + if (options.debug) { console.log('Options:'); - console.log(cmd.opts()); + console.log(options); console.log(); } - console.log(`Start serve on port ${cmd.port}`); + console.log(`Start serve on port ${options.port}`); }); program.parse(); diff --git a/examples/custom-command-function.js b/examples/custom-command-function.js index f4d58c0e5..af3125085 100755 --- a/examples/custom-command-function.js +++ b/examples/custom-command-function.js @@ -18,14 +18,14 @@ program.createCommand = (name) => { program .command('serve') .option('--port ', 'specify port number', 80) - .action((cmd) => { - if (cmd.debug) { + .action((options) => { + if (options.debug) { console.log('Options:'); - console.log(cmd.opts()); + console.log(options); console.log(); } - console.log(`Start serve on port ${cmd.port}`); + console.log(`Start serve on port ${options.port}`); }); program.parse(); diff --git a/examples/custom-help-description b/examples/custom-help-description index 6872ee6f0..7191f7fc7 100755 --- a/examples/custom-help-description +++ b/examples/custom-help-description @@ -14,7 +14,7 @@ program program .command('child') .option('--gender', 'specific gender of child') - .action((cmd) => { + .action((options) => { console.log('Childsubcommand...'); }); diff --git a/examples/defaultCommand.js b/examples/defaultCommand.js index 58e2edcfe..13ef0195c 100755 --- a/examples/defaultCommand.js +++ b/examples/defaultCommand.js @@ -31,8 +31,8 @@ program .command('serve', { isDefault: true }) .description('launch web server') .option('-p,--port ', 'web port') - .action((opts) => { - console.log(`server on port ${opts.port}`); + .action((options) => { + console.log(`server on port ${options.port}`); }); program.parse(process.argv); diff --git a/examples/storeOptionsAsProperties-action.js b/examples/storeOptionsAsProperties-action.js deleted file mode 100755 index 7005da739..000000000 --- a/examples/storeOptionsAsProperties-action.js +++ /dev/null @@ -1,40 +0,0 @@ -#!/usr/bin/env node - -// 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 command object. This allows simpler code, and is more consistent -// with the previous behaviour so less code changes from old code. -// -// Example output: -// -// $ node storeOptionsAsProperties-action.js show -// undefined -// undefined -// -// $ node storeOptionsAsProperties-action.js --name foo show --action jump -// jump -// foo - -// const commander = require('commander'); // (normal include) -const commander = require('../'); // include commander in git clone of commander repo -const program = new commander.Command(); - -program - .storeOptionsAsProperties(false); // <--- change behaviour - -program - .name('my-program-name') - .option('-n,--name '); - -program - .command('show') - .option('-a,--action ') - .action((options) => { // <--- passed options, not Command - console.log(options.action); // <--- matches old code - }); - -program.parse(process.argv); - -const programOptions = program.opts(); // <--- use opts to access option values -console.log(programOptions.name); diff --git a/examples/storeOptionsAsProperties-opts.js b/examples/storeOptionsAsProperties-opts.js deleted file mode 100755 index 56185f36e..000000000 --- a/examples/storeOptionsAsProperties-opts.js +++ /dev/null @@ -1,39 +0,0 @@ -#!/usr/bin/env node - -// To avoid possible name clashes, you can change the default behaviour -// of storing the options values as properties on the command object. -// You access the option values using the .opts() function. -// -// Example output: -// -// $ node storeOptionsAsProperties-opts.js show -// undefined -// undefined -// -// $ node storeOptionsAsProperties-opts.js --name foo show --action jump -// jump -// foo - -// const commander = require('commander'); // (normal include) -const commander = require('../'); // include commander in git clone of commander repo -const program = new commander.Command(); - -program - .storeOptionsAsProperties(false); // <--- change behaviour - -program - .name('my-program-name') - .option('-n,--name '); - -program - .command('show') - .option('-a,--action ') - .action((cmd) => { - const options = cmd.opts(); // <--- use opts to access option values - console.log(options.action); - }); - -program.parse(process.argv); - -const programOptions = program.opts(); // <--- use opts to access option values -console.log(programOptions.name); diff --git a/examples/storeOptionsAsProperties-problem.js b/examples/storeOptionsAsProperties-problem.js deleted file mode 100755 index 62bcf45d5..000000000 --- a/examples/storeOptionsAsProperties-problem.js +++ /dev/null @@ -1,36 +0,0 @@ -#!/usr/bin/env node - -// The original and default behaviour is that the option values are stored -// as properties on the program (Command). The action handler is passed a -// command object (Command) with the options values also stored as properties. -// This is very convenient to code, but the downside is possible clashes with -// existing properties of Command. -// -// Example output, note the issues in the first call: -// -// $ node storeOptionsAsProperties-problem.js show -// [Function] -// [Function] -// -// $ node storeOptionsAsProperties-problem.js --name foo show --action jump -// jump -// foo - -// const commander = require('commander'); // (normal include) -const commander = require('../'); // include commander in git clone of commander repo -const program = new commander.Command(); - -program - .name('my-program-name') - .option('-n,--name '); // Oops, clash with .name() - -program - .command('show') - .option('-a,--action ') // Oops, clash with .action() - .action((cmd) => { - console.log(cmd.action); - }); - -program.parse(process.argv); - -console.log(program.name); diff --git a/tests/options.mandatory.test.js b/tests/options.mandatory.test.js index 0d1f44cfe..11221b1ec 100644 --- a/tests/options.mandatory.test.js +++ b/tests/options.mandatory.test.js @@ -205,7 +205,7 @@ describe('required command option with mandatory value not specified', () => { .exitOverride() .command('sub') .requiredOption('--subby ', 'description') - .action((cmd) => {}); + .action(() => {}); expect(() => { program.parse(['node', 'test', 'sub']); @@ -218,7 +218,7 @@ describe('required command option with mandatory value not specified', () => { .exitOverride() .command('sub') .requiredOption('--subby ', 'description') - .action((cmd) => {}); + .action(() => {}); program .command('sub2'); From 5031893512d79eb688ea4219cc184ffa5c64ae74 Mon Sep 17 00:00:00 2001 From: John Gee Date: Fri, 4 Dec 2020 20:46:31 +1300 Subject: [PATCH 04/24] Rework README on using storeOptionsAsProperties --- Readme.md | 40 +++++++++++----------------------------- 1 file changed, 11 insertions(+), 29 deletions(-) diff --git a/Readme.md b/Readme.md index 2370206c8..d1f66a853 100644 --- a/Readme.md +++ b/Readme.md @@ -35,7 +35,7 @@ Read this in other languages: English | [简体中文](./Readme_zh-CN.md) - [Custom event listeners](#custom-event-listeners) - [Bits and pieces](#bits-and-pieces) - [.parse() and .parseAsync()](#parse-and-parseasync) - - [Avoiding option name clashes](#avoiding-option-name-clashes) + - [Legacy options as properties](#legacy-options-as-properties) - [TypeScript](#typescript) - [createCommand()](#createcommand) - [Import into ECMAScript Module](#import-into-ecmascript-module) @@ -688,39 +688,21 @@ program.parse(); // Implicit, and auto-detect electron program.parse(['-f', 'filename'], { from: 'user' }); ``` -### Avoiding option name clashes +### Legacy options as properties -The original and default behaviour is that the option values are stored -as properties on the program, and the action handler is passed a -command object with the options values stored as properties. -This is very convenient to code, but the downside is possible clashes with -existing properties of Command. - -There are two new routines to change the behaviour, and the default behaviour may change in the future: - -- `storeOptionsAsProperties`: whether to store option values as properties on command object, or store separately (specify false) and access using `.opts()` - -Example file: [storeOptionsAsProperties-action.js](./examples/storeOptionsAsProperties-action.js) +Before Commander 7, the option values were stored as properties on the command. +This was convenient to code but the downside was possible clashes with +existing properties of `Command`. You can revert to the old behaviour to run old code by using `.storeOptionsAsProperties()`. ```js program - .storeOptionsAsProperties(false); - -program - .name('my-program-name') - .option('-n,--name '); - -program - .command('show') - .option('-a,--action ') - .action((options) => { - console.log(options.action); + .storeOptionsAsProperties() + .option('-d, --debug') + .action((commandAndOptions) => { + if (commandAndOptions.debug) { + console.error(`Called ${commandAndOptions.name()}`); + } }); - -program.parse(process.argv); - -const programOptions = program.opts(); -console.log(programOptions.name); ``` ### TypeScript From b5f165f7ad4350f63d4ee5ceb9aef43263a1ea48 Mon Sep 17 00:00:00 2001 From: John Gee Date: Fri, 4 Dec 2020 20:52:22 +1300 Subject: [PATCH 05/24] Tweak wording --- Readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Readme.md b/Readme.md index d1f66a853..b51db4458 100644 --- a/Readme.md +++ b/Readme.md @@ -692,7 +692,7 @@ program.parse(['-f', 'filename'], { from: 'user' }); Before Commander 7, the option values were stored as properties on the command. This was convenient to code but the downside was possible clashes with -existing properties of `Command`. You can revert to the old behaviour to run old code by using `.storeOptionsAsProperties()`. +existing properties of `Command`. You can revert to the old behaviour to run unmodified legacy code by using `.storeOptionsAsProperties()`. ```js program From eefb4f9a892643d721049ddda328c0332c2a549c Mon Sep 17 00:00:00 2001 From: John Gee Date: Fri, 4 Dec 2020 21:05:24 +1300 Subject: [PATCH 06/24] Updat example for options-common --- Readme.md | 9 +++++---- examples/options-common.js | 30 ++++++++++-------------------- 2 files changed, 15 insertions(+), 24 deletions(-) diff --git a/Readme.md b/Readme.md index b51db4458..4429d8e37 100644 --- a/Readme.md +++ b/Readme.md @@ -77,7 +77,7 @@ program.version('0.0.1'); Options are defined with the `.option()` method, also serving as documentation for the options. Each option can have a short flag (single character) and a long name, separated by a comma or space or vertical bar ('|'). -The options can be accessed as properties on the Command object. Multi-word options such as "--template-engine" are camel-cased, becoming `program.templateEngine` etc. See also optional new behaviour to [avoid name clashes](#avoiding-option-name-clashes). +The parsed options can be accessed by calling `.opts()` on a `Command` object. Multi-word options such as "--template-engine" are camel-cased, becoming `program.opts().templateEngine` etc. Multiple short flags may optionally be combined in a single argument following the dash: boolean flags, followed by a single option taking a value (possibly followed by the value). For example `-a -b -p 80` may be written as `-ab -p80` or even `-abp80`. @@ -101,10 +101,11 @@ program program.parse(process.argv); -if (program.debug) console.log(program.opts()); +const options = program.opts(); +if (options.debug) console.log(options); console.log('pizza details:'); -if (program.small) console.log('- small pizza size'); -if (program.pizzaType) console.log(`- ${program.pizzaType}`); +if (options.small) console.log('- small pizza size'); +if (options.pizzaType) console.log(`- ${options.pizzaType}`); ``` ```bash diff --git a/examples/options-common.js b/examples/options-common.js index 74c8b7750..05b238912 100755 --- a/examples/options-common.js +++ b/examples/options-common.js @@ -2,23 +2,6 @@ // This is used as an example in the README for: // Common option types, boolean and value -// The two most used option types are a boolean flag, and an option which takes a value (declared using angle brackets). -// -// Example output pretending command called pizza-options (or try directly with `node options-common.js`) -// -// $ pizza-options -d -// { debug: true, small: undefined, pizzaType: undefined } -// pizza details: -// $ pizza-options -p -// error: option '-p, --pizza-type ' argument missing -// $ pizza-options -ds -p vegetarian -// { debug: true, small: true, pizzaType: 'vegetarian' } -// pizza details: -// - small pizza size -// - vegetarian -// $ pizza-options --pizza-type=cheese -// pizza details: -// - cheese // const commander = require('commander'); // (normal include) const commander = require('../'); // include commander in git clone of commander repo @@ -31,7 +14,14 @@ program program.parse(process.argv); -if (program.debug) console.log(program.opts()); +const options = program.opts(); +if (options.debug) console.log(options); console.log('pizza details:'); -if (program.small) console.log('- small pizza size'); -if (program.pizzaType) console.log(`- ${program.pizzaType}`); +if (options.small) console.log('- small pizza size'); +if (options.pizzaType) console.log(`- ${options.pizzaType}`); + +// Try the following: +// node options-common.js -d +// node options-common.js -p +// node options-common.js -ds -p vegetarian +// node options-common.js --pizza-type=cheese From b0241d912f6629beab994a4c706785440a26847a Mon Sep 17 00:00:00 2001 From: John Gee Date: Fri, 4 Dec 2020 21:09:34 +1300 Subject: [PATCH 07/24] Update options-defaults --- Readme.md | 4 ++-- examples/options-defaults.js | 16 ++++++---------- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/Readme.md b/Readme.md index 4429d8e37..d695bae0f 100644 --- a/Readme.md +++ b/Readme.md @@ -136,9 +136,9 @@ Example file: [options-defaults.js](./examples/options-defaults.js) program .option('-c, --cheese ', 'add the specified type of cheese', 'blue'); -program.parse(process.argv); +program.parse(); -console.log(`cheese: ${program.cheese}`); +console.log(`cheese: ${program.opts().cheese}`); ``` ```bash diff --git a/examples/options-defaults.js b/examples/options-defaults.js index 1f0929fb9..d7738b5b1 100755 --- a/examples/options-defaults.js +++ b/examples/options-defaults.js @@ -2,14 +2,6 @@ // This is used as an example in the README for: // Default option value -// You can specify a default value for an option which takes a value. -// -// Example output pretending command called pizza-options (or try directly with `node options-defaults.js`) -// -// $ pizza-options -// cheese: blue -// $ pizza-options --cheese stilton -// cheese: stilton // const commander = require('commander'); // (normal include) const commander = require('../'); // include commander in git clone of commander repo @@ -18,6 +10,10 @@ const program = new commander.Command(); program .option('-c, --cheese ', 'Add the specified type of cheese', 'blue'); -program.parse(process.argv); +program.parse(); -console.log(`cheese: ${program.cheese}`); +console.log(`cheese: ${program.opts().cheese}`); + +// Try the following: +// node options-defaults.js +// node options-defaults.js --cheese stilton From b7e5857b995a56f3602a29a247b57e5ad603d6c0 Mon Sep 17 00:00:00 2001 From: John Gee Date: Fri, 4 Dec 2020 21:14:49 +1300 Subject: [PATCH 08/24] Update options-negatable example --- Readme.md | 7 ++++--- examples/options-negatable.js | 24 ++++++++++-------------- 2 files changed, 14 insertions(+), 17 deletions(-) diff --git a/Readme.md b/Readme.md index d695bae0f..beccf8025 100644 --- a/Readme.md +++ b/Readme.md @@ -163,10 +163,11 @@ program .option('--no-sauce', 'Remove sauce') .option('--cheese ', 'cheese flavour', 'mozzarella') .option('--no-cheese', 'plain with no cheese') - .parse(process.argv); + .parse(); -const sauceStr = program.sauce ? 'sauce' : 'no sauce'; -const cheeseStr = (program.cheese === false) ? 'no cheese' : `${program.cheese} cheese`; +const options = program.opts(); +const sauceStr = options.sauce ? 'sauce' : 'no sauce'; +const cheeseStr = (options.cheese === false) ? 'no cheese' : `${options.cheese} cheese`; console.log(`You ordered a pizza with ${sauceStr} and ${cheeseStr}`); ``` diff --git a/examples/options-negatable.js b/examples/options-negatable.js index 0a8130474..2d517dec7 100755 --- a/examples/options-negatable.js +++ b/examples/options-negatable.js @@ -3,17 +3,6 @@ // This is used as an example in the README for: // Other option types, negatable boolean // You can specify a boolean option long name with a leading `no-` to make it true by default and able to be negated. -// -// Example output pretending command called pizza-options (or try directly with `node options-negatable.js`) -// -// $ pizza-options -// You ordered a pizza with sauce and mozzarella cheese -// $ pizza-options --sauce -// error: unknown option '--sauce' -// $ pizza-options --cheese=blue -// You ordered a pizza with sauce and blue cheese -// $ pizza-options --no-sauce --no-cheese -// You ordered a pizza with no sauce and no cheese // const commander = require('commander'); // (normal include) const commander = require('../'); // include commander in git clone of commander repo @@ -24,8 +13,15 @@ program .option('--cheese ', 'cheese flavour', 'mozzarella') .option('--no-cheese', 'plain with no cheese'); -program.parse(process.argv); +program.parse(); -const sauceStr = program.sauce ? 'sauce' : 'no sauce'; -const cheeseStr = (program.cheese === false) ? 'no cheese' : `${program.cheese} cheese`; +const options = program.opts(); +const sauceStr = options.sauce ? 'sauce' : 'no sauce'; +const cheeseStr = (options.cheese === false) ? 'no cheese' : `${options.cheese} cheese`; console.log(`You ordered a pizza with ${sauceStr} and ${cheeseStr}`); + +// Try the following: +// node options-negatable.js +// node options-negatable.js --sauce +// node options-negatable.js --cheese=blue +// node options-negatable.js --no-sauce --no-cheese From ef3435488666bc4e080c6572c2582ca553c00314 Mon Sep 17 00:00:00 2001 From: John Gee Date: Fri, 4 Dec 2020 21:18:10 +1300 Subject: [PATCH 09/24] Update options-boolean-or-value example --- Readme.md | 7 ++++--- examples/options-boolean-or-value.js | 21 +++++++++------------ 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/Readme.md b/Readme.md index beccf8025..bc7c819e8 100644 --- a/Readme.md +++ b/Readme.md @@ -193,9 +193,10 @@ program program.parse(process.argv); -if (program.cheese === undefined) console.log('no cheese'); -else if (program.cheese === true) console.log('add cheese'); -else console.log(`add cheese type ${program.cheese}`); +const options = program.opts(); +if (options.cheese === undefined) console.log('no cheese'); +else if (options.cheese === true) console.log('add cheese'); +else console.log(`add cheese type ${options.cheese}`); ``` ```bash diff --git a/examples/options-boolean-or-value.js b/examples/options-boolean-or-value.js index b895569a0..af27678a5 100755 --- a/examples/options-boolean-or-value.js +++ b/examples/options-boolean-or-value.js @@ -3,15 +3,6 @@ // This is used as an example in the README for: // Other option types, flag|value // You can specify an option which functions as a flag but may also take a value (declared using square brackets). -// -// Example output pretending command called pizza-options (or try directly with `node options-flag-or-value.js`) -// -// $ pizza-options -// no cheese -// $ pizza-options --cheese -// add cheese -// $ pizza-options --cheese mozzarella -// add cheese type mozzarella // const commander = require('commander'); // (normal include) const commander = require('../'); // include commander in git clone of commander repo @@ -22,6 +13,12 @@ program program.parse(process.argv); -if (program.cheese === undefined) console.log('no cheese'); -else if (program.cheese === true) console.log('add cheese'); -else console.log(`add cheese type ${program.cheese}`); +const options = program.opts(); +if (options.cheese === undefined) console.log('no cheese'); +else if (options.cheese === true) console.log('add cheese'); +else console.log(`add cheese type ${options.cheese}`); + +// Try the following: +// node options-boolean-or-value +// node options-boolean-or-value --cheese +// node options-boolean-or-value --cheese mozzarella From de5d2b6e1d88f194baa089f85bccbe7ad4a695d1 Mon Sep 17 00:00:00 2001 From: John Gee Date: Fri, 4 Dec 2020 21:22:21 +1300 Subject: [PATCH 10/24] Update options-custom-processing example --- Readme.md | 13 ++++----- examples/options-custom-processing.js | 38 +++++++++++---------------- 2 files changed, 23 insertions(+), 28 deletions(-) diff --git a/Readme.md b/Readme.md index bc7c819e8..85f5bc0e8 100644 --- a/Readme.md +++ b/Readme.md @@ -350,13 +350,14 @@ program .option('-l, --list ', 'comma separated list', commaSeparatedList) ; -program.parse(process.argv); +program.parse(); -if (program.float !== undefined) console.log(`float: ${program.float}`); -if (program.integer !== undefined) console.log(`integer: ${program.integer}`); -if (program.verbose > 0) console.log(`verbosity: ${program.verbose}`); -if (program.collect.length > 0) console.log(program.collect); -if (program.list !== undefined) console.log(program.list); +const options = program.opts(); +if (options.float !== undefined) console.log(`float: ${options.float}`); +if (options.integer !== undefined) console.log(`integer: ${options.integer}`); +if (options.verbose > 0) console.log(`verbosity: ${options.verbose}`); +if (options.collect.length > 0) console.log(options.collect); +if (options.list !== undefined) console.log(options.list); ``` ```bash diff --git a/examples/options-custom-processing.js b/examples/options-custom-processing.js index 5a44ba1b8..3e601e5df 100755 --- a/examples/options-custom-processing.js +++ b/examples/options-custom-processing.js @@ -2,22 +2,7 @@ // This is used as an example in the README for: // Custom option processing -// You may specify a function to do custom processing of option values. ... -// -// Example output pretending command called custom (or try directly with `node options-custom-processing.js`) -// -// $ custom -f 1e2 -// float: 100 -// $ custom --integer 2 -// integer: 2 -// $ custom -v -v -v -// verbose: 3 -// $ custom -c a -c b -c c -// [ 'a', 'b', 'c' ] -// $ custom --list x,y,z -// [ 'x', 'y', 'z' ] -// $ custom --integer oops -// error: option '-i, --integer ' argument 'oops' is invalid. Not a number. +// You may specify a function to do custom processing of option values. // const commander = require('commander'); // (normal include) const commander = require('../'); // include commander in git clone of commander repo @@ -52,9 +37,18 @@ program .option('-l, --list ', 'comma separated list', commaSeparatedList) ; -program.parse(process.argv); -if (program.float !== undefined) console.log(`float: ${program.float}`); -if (program.integer !== undefined) console.log(`integer: ${program.integer}`); -if (program.verbose > 0) console.log(`verbosity: ${program.verbose}`); -if (program.collect.length > 0) console.log(program.collect); -if (program.list !== undefined) console.log(program.list); +program.parse(); + +const options = program.opts(); +if (options.float !== undefined) console.log(`float: ${options.float}`); +if (options.integer !== undefined) console.log(`integer: ${options.integer}`); +if (options.verbose > 0) console.log(`verbosity: ${options.verbose}`); +if (options.collect.length > 0) console.log(options.collect); +if (options.list !== undefined) console.log(options.list); + +// Try the following: +// node options-custom-processing -f 1e2 +// node options-custom-processing --integer 2 +// node options-custom-processing -v -v -v +// node options-custom-processing -c a -c b -c c +// node options-custom-processing --list x,y,z From de31e1cb001f696cbf34a8cea0ec641aa140c160 Mon Sep 17 00:00:00 2001 From: John Gee Date: Sat, 5 Dec 2020 17:42:21 +1300 Subject: [PATCH 11/24] Turn on error for excess arguments, and update tests accordingly --- index.js | 2 +- tests/command.allowExcessArguments.test.js | 8 ++++---- tests/command.allowUnknownOption.test.js | 1 + tests/command.asterisk.test.js | 2 ++ tests/command.default.test.js | 2 ++ tests/command.unknownCommand.test.js | 3 ++- 6 files changed, 12 insertions(+), 6 deletions(-) diff --git a/index.js b/index.js index a9137c8eb..73deef361 100644 --- a/index.js +++ b/index.js @@ -535,7 +535,7 @@ class Command extends EventEmitter { this.options = []; this.parent = null; this._allowUnknownOption = false; - this._allowExcessArguments = true; + this._allowExcessArguments = false; this._args = []; this.rawArgs = null; this._scriptPath = null; diff --git a/tests/command.allowExcessArguments.test.js b/tests/command.allowExcessArguments.test.js index 6ce203fc9..fae40384a 100644 --- a/tests/command.allowExcessArguments.test.js +++ b/tests/command.allowExcessArguments.test.js @@ -18,7 +18,7 @@ describe('allowUnknownOption', () => { writeErrorSpy.mockRestore(); }); - test('when specify excess program argument then ignored by default', () => { + test('when specify excess program argument then error by default', () => { const program = new commander.Command(); program .exitOverride() @@ -26,7 +26,7 @@ describe('allowUnknownOption', () => { expect(() => { program.parse(['excess'], { from: 'user' }); - }).not.toThrow(); + }).toThrow(); }); test('when specify excess program argument and allowExcessArguments(false) then error', () => { @@ -65,7 +65,7 @@ describe('allowUnknownOption', () => { }).not.toThrow(); }); - test('when specify excess command argument then no error (by default)', () => { + test('when specify excess command argument then error (by default)', () => { const program = new commander.Command(); program .exitOverride() @@ -74,7 +74,7 @@ describe('allowUnknownOption', () => { expect(() => { program.parse(['sub', 'excess'], { from: 'user' }); - }).not.toThrow(); + }).toThrow(); }); test('when specify excess command argument and allowExcessArguments(false) then error', () => { diff --git a/tests/command.allowUnknownOption.test.js b/tests/command.allowUnknownOption.test.js index 4425802dd..5459c5154 100644 --- a/tests/command.allowUnknownOption.test.js +++ b/tests/command.allowUnknownOption.test.js @@ -83,6 +83,7 @@ describe('allowUnknownOption', () => { program .exitOverride() .command('sub') + .arguments('[args...]') // unknown option will be passed as an argument .allowUnknownOption() .option('-p, --pepper', 'add pepper') .action(() => { }); diff --git a/tests/command.asterisk.test.js b/tests/command.asterisk.test.js index 14711b2d4..cf0e7f004 100644 --- a/tests/command.asterisk.test.js +++ b/tests/command.asterisk.test.js @@ -33,6 +33,7 @@ describe(".command('*')", () => { const program = new commander.Command(); program .command('*') + .arguments('[args...]') .action(mockAction); program.parse(['node', 'test', 'unrecognised-command']); expect(mockAction).toHaveBeenCalled(); @@ -58,6 +59,7 @@ describe(".command('*')", () => { .command('install'); program .command('*') + .arguments('[args...]') .action(mockAction); program.parse(['node', 'test', 'unrecognised-command']); expect(mockAction).toHaveBeenCalled(); diff --git a/tests/command.default.test.js b/tests/command.default.test.js index 6f1d79210..cf45d7947 100644 --- a/tests/command.default.test.js +++ b/tests/command.default.test.js @@ -34,6 +34,7 @@ describe('default action command', () => { program .command('default', { isDefault: true }) .allowUnknownOption() + .allowExcessArguments() .action(actionMock); return { program, actionMock }; } @@ -62,6 +63,7 @@ describe('default added command', () => { const actionMock = jest.fn(); const defaultCmd = new commander.Command('default') .allowUnknownOption() + .allowExcessArguments() .action(actionMock); const program = new commander.Command(); diff --git a/tests/command.unknownCommand.test.js b/tests/command.unknownCommand.test.js index 183d7afb0..83bf87dc8 100644 --- a/tests/command.unknownCommand.test.js +++ b/tests/command.unknownCommand.test.js @@ -24,12 +24,13 @@ describe('unknownOption', () => { }).not.toThrow(); }); - test('when unknown command but action handler then no error', () => { + test('when unknown command but action handler taking arg then no error', () => { const program = new commander.Command(); program .exitOverride() .command('sub'); program + .arguments('[args...]') .action(() => { }); expect(() => { program.parse('node test.js unknown'.split(' ')); From 0adf3c199fae1e2b100c98539d937661fbc115dc Mon Sep 17 00:00:00 2001 From: John Gee Date: Sun, 6 Dec 2020 09:56:31 +1300 Subject: [PATCH 12/24] Add first cut at migration tips --- CHANGELOG.md | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0aabbf1a9..9e5af5ddd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,56 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. +## [Unreleased] (date goes here) + +### Migration Tips + +**program options** + +The parsed option values were previously stored by default as properties on the command object, and are now stored separately. +Use the `.opts()` method to access the options. + +```js +program.option('-d, --debug'); +program.parse(); +// Old code +if (program.debug) console.log(`Program name is ${program.name()}`); +``` + +```js +// New code +const options = program.opts(); +if (options.debug) console.log(`Program name is ${program.name()}`); +``` + +**action handler** + +The action handler was previously passed by default a command object with the options as properties. Now it is passed two +extra parameters, the options and the command. + +```js +.command('compress ') +.option('-t, --trace') +// Old code +.action((filename, cmd)) => { + if (cmd.trace) console.log(`Command name is ${cmd.name()}`); +}); +``` + +```js +// New code +.action((filename, options, command)) => { + if (options.trace) console.log(`Command name is ${command.name()}`); +}); +``` + +**legacy behaviour** + +If you do not want to update your code, you can call `.storeOptionsAsProperties()` to restore the old behavior and store the parsed option values as properties on the command. + +An action handler will be passed the command _twice_, as the "options" and "command". This lets you run unmodified code, but allows you to incrementally move to use the "options" and "command" pattern +and remove the legacy behaviour when you are ready. + ## [7.0.0-1] (2020-11-21) ### Added From 0b0454ab84b156864d293a090c45bcf4e4205fef Mon Sep 17 00:00:00 2001 From: John Gee Date: Sun, 6 Dec 2020 10:54:03 +1300 Subject: [PATCH 13/24] Second cut at migration tips --- CHANGELOG.md | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9e5af5ddd..0a71abdc7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,10 +12,15 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Migration Tips +The biggest change is the parsed option values were previously stored by default as properties on the command object, and are now stored separately. + +If you wish to restore the old behaviour and get running quickly you can call `.storeOptionsAsProperties()`. +To allow you to move to the new code patterns incrementally, the action handler will be passed the command _twice_, +to match the new "options" and "command" parameters. + **program options** -The parsed option values were previously stored by default as properties on the command object, and are now stored separately. -Use the `.opts()` method to access the options. +Use the `.opts()` method to access the options. This is available on any command but is used most with the program. ```js program.option('-d, --debug'); @@ -32,8 +37,8 @@ if (options.debug) console.log(`Program name is ${program.name()}`); **action handler** -The action handler was previously passed by default a command object with the options as properties. Now it is passed two -extra parameters, the options and the command. +The action handler was previously passed by default a command object with the options as properties. Now that is split into two parameters which are the options and the command. If you +only access the options there may be no code changes required. ```js .command('compress ') @@ -51,13 +56,6 @@ extra parameters, the options and the command. }); ``` -**legacy behaviour** - -If you do not want to update your code, you can call `.storeOptionsAsProperties()` to restore the old behavior and store the parsed option values as properties on the command. - -An action handler will be passed the command _twice_, as the "options" and "command". This lets you run unmodified code, but allows you to incrementally move to use the "options" and "command" pattern -and remove the legacy behaviour when you are ready. - ## [7.0.0-1] (2020-11-21) ### Added From 03389c7910b52ead86493b9c454d57062f626acc Mon Sep 17 00:00:00 2001 From: John Gee Date: Sun, 6 Dec 2020 10:56:58 +1300 Subject: [PATCH 14/24] Migration wording tweaks --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a71abdc7..5fa97d962 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,11 +12,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Migration Tips -The biggest change is the parsed option values were previously stored by default as properties on the command object, and are now stored separately. +The biggest change is the parsed option values which were previously stored by default as properties on the command object, and are now stored separately. If you wish to restore the old behaviour and get running quickly you can call `.storeOptionsAsProperties()`. To allow you to move to the new code patterns incrementally, the action handler will be passed the command _twice_, -to match the new "options" and "command" parameters. +to match the new "options" and "command" parameters (see below). **program options** From 46f8a379b74822c9fde73aabeff677a0e663321f Mon Sep 17 00:00:00 2001 From: John Gee Date: Sun, 6 Dec 2020 11:25:22 +1300 Subject: [PATCH 15/24] Reworking README for new patterns --- CHANGELOG.md | 2 +- Readme.md | 22 +++++++++++----------- examples/options-required.js | 2 +- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5fa97d962..70094541b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,7 +37,7 @@ if (options.debug) console.log(`Program name is ${program.name()}`); **action handler** -The action handler was previously passed by default a command object with the options as properties. Now that is split into two parameters which are the options and the command. If you +The action handler gets passed a parameter for each command-argument you declared. Previously by default the next parameter was the command object with the options as properties. Now the next two parameters are instead the options and the command. If you only access the options there may be no code changes required. ```js diff --git a/Readme.md b/Readme.md index 85f5bc0e8..046c71df8 100644 --- a/Readme.md +++ b/Readme.md @@ -23,7 +23,7 @@ Read this in other languages: English | [简体中文](./Readme_zh-CN.md) - [Custom option processing](#custom-option-processing) - [Commands](#commands) - [Specify the argument syntax](#specify-the-argument-syntax) - - [Action handler (sub)commands](#action-handler-subcommands) + - [Action handler](#action-handler) - [Stand-alone executable (sub)commands](#stand-alone-executable-subcommands) - [Automated help](#automated-help) - [Custom help](#custom-help) @@ -77,7 +77,7 @@ program.version('0.0.1'); Options are defined with the `.option()` method, also serving as documentation for the options. Each option can have a short flag (single character) and a long name, separated by a comma or space or vertical bar ('|'). -The parsed options can be accessed by calling `.opts()` on a `Command` object. Multi-word options such as "--template-engine" are camel-cased, becoming `program.opts().templateEngine` etc. +The parsed options can be accessed by calling `.opts()` on a `Command` object, and are passed to the action handler. Multi-word options such as "--template-engine" are camel-cased, becoming `program.opts().templateEngine` etc. Multiple short flags may optionally be combined in a single argument following the dash: boolean flags, followed by a single option taking a value (possibly followed by the value). For example `-a -b -p 80` may be written as `-ab -p80` or even `-abp80`. @@ -124,7 +124,7 @@ pizza details: - cheese ``` -`program.parse(arguments)` processes the arguments, leaving any args not consumed by the program options in the `program.args` array. +`program.parse(arguments)` processes the arguments, leaving any args not consumed by the program options in the `program.args` array. The parameter is optional and defaults to `process.argv`. ### Default option value @@ -220,7 +220,7 @@ Example file: [options-required.js](./examples/options-required.js) program .requiredOption('-c, --cheese ', 'pizza must have cheese'); -program.parse(process.argv); +program.parse(); ``` ```bash @@ -457,11 +457,10 @@ program.parse(process.argv); The variadic argument is passed to the action handler as an array. -### Action handler (sub)commands +### Action handler -You can add options to a command that uses an action handler. -The action handler gets passed a parameter for each argument you declared, and one additional argument which is the -command object itself. This command argument has the values for the command-specific options added as properties. +The action handler gets passed a parameter for each command-argument you declared, and two additional parameters which are the parsed options and the +command object itself. ```js const { program } = require('commander'); @@ -469,8 +468,9 @@ const { program } = require('commander'); program .command('rm ') .option('-r, --recursive', 'Remove recursively') - .action(function (dir, cmdObj) { - console.log('remove ' + dir + (cmdObj.recursive ? ' recursively' : '')) + .action(function (dir, options, command) { + const recursively = options.recursive ? ' recursively' : ''; + console.log(`${command.name}${recursively}: ${dir}`) }) program.parse(process.argv) @@ -489,7 +489,7 @@ async function main() { } ``` -A command's options on the command line are validated when the command is used. Any unknown options will be reported as an error. +A command's options and arguments on the command line are validated when the command is used. Any unknown options or missing arguments or unexpected arguments will be reported as an error. ### Stand-alone executable (sub)commands diff --git a/examples/options-required.js b/examples/options-required.js index 9feaef395..457f13f18 100755 --- a/examples/options-required.js +++ b/examples/options-required.js @@ -17,4 +17,4 @@ const program = new commander.Command(); program .requiredOption('-c, --cheese ', 'pizza must have cheese'); -program.parse(process.argv); +program.parse(); From 4013a2d3566f01c77db85af642c1df61519e3e31 Mon Sep 17 00:00:00 2001 From: John Gee Date: Sun, 6 Dec 2020 12:10:27 +1300 Subject: [PATCH 16/24] Goodbye to option properties on Command --- typings/commander-tests.ts | 16 ++-------------- typings/index.d.ts | 2 -- 2 files changed, 2 insertions(+), 16 deletions(-) diff --git a/typings/commander-tests.ts b/typings/commander-tests.ts index c968d0043..607402dd3 100644 --- a/typings/commander-tests.ts +++ b/typings/commander-tests.ts @@ -9,16 +9,7 @@ import * as commander from './index'; // This conflicts with esline rule saying no space! /* eslint-disable @typescript-eslint/space-before-function-paren */ -// Defined stricter type, as the options as properties `[key: string]: any` -// makes the type checking very weak. -// https://github.com/Microsoft/TypeScript/issues/25987#issuecomment-441224690 -type KnownKeys = { - [K in keyof T]: string extends K ? never : number extends K ? never : K -} extends {[_ in keyof T]: infer U} ? ({} extends U ? never : U) : never; -type CommandWithoutOptionsAsProperties = Pick>; - -const program: CommandWithoutOptionsAsProperties = commander.program; -const programWithOptions = commander.program; +const program: commander.Command = new commander.Command(); // program.silly; // <-- Error, hurrah! // Check for exported global Command objects @@ -33,10 +24,7 @@ const errorInstance = new commander.CommanderError(1, 'code', 'message'); const invalidOptionErrorInstance = new commander.InvalidOptionArgumentError('message'); // Command properties -console.log(programWithOptions.someOption); -// eslint-disable-next-line @typescript-eslint/dot-notation -console.log(programWithOptions['someOption']); -const theArgs = program.args; +const theArgs: string[] = program.args; const theCommands: commander.Command[] = program.commands; // version diff --git a/typings/index.d.ts b/typings/index.d.ts index 05a42587f..3399adbc0 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -149,8 +149,6 @@ declare namespace commander { type AddHelpTextPosition = 'beforeAll' | 'before' | 'after' | 'afterAll'; interface Command { - [key: string]: any; // options as properties - args: string[]; commands: Command[]; From f6aa9c929e6fefc68820117aff91a6b4986a50b0 Mon Sep 17 00:00:00 2001 From: John Gee Date: Sun, 6 Dec 2020 14:16:30 +1300 Subject: [PATCH 17/24] Add migration tip for excess arguments --- CHANGELOG.md | 71 +++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 59 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 70094541b..5fb197ca3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Migration Tips -The biggest change is the parsed option values which were previously stored by default as properties on the command object, and are now stored separately. +The biggest change is the parsed option values. Previously the options were stored by default as properties on the command object, and now the options are now stored separately. If you wish to restore the old behaviour and get running quickly you can call `.storeOptionsAsProperties()`. To allow you to move to the new code patterns incrementally, the action handler will be passed the command _twice_, @@ -25,7 +25,7 @@ Use the `.opts()` method to access the options. This is available on any command ```js program.option('-d, --debug'); program.parse(); -// Old code +// Old code before Commander 7 if (program.debug) console.log(`Program name is ${program.name()}`); ``` @@ -41,19 +41,66 @@ The action handler gets passed a parameter for each command-argument you declare only access the options there may be no code changes required. ```js -.command('compress ') -.option('-t, --trace') -// Old code -.action((filename, cmd)) => { - if (cmd.trace) console.log(`Command name is ${cmd.name()}`); -}); +program + .command('compress ') + .option('-t, --trace') + // Old code before Commander 7 + .action((filename, cmd)) => { + if (cmd.trace) console.log(`Command name is ${cmd.name()}`); + }); ``` ```js -// New code -.action((filename, options, command)) => { - if (options.trace) console.log(`Command name is ${command.name()}`); -}); + // New code + .action((filename, options, command)) => { + if (options.trace) console.log(`Command name is ${command.name()}`); + }); +``` + +If you already set `.storeOptionsAsProperties(false)` you may still need to adjust your code. + +```js +program + .command('compress ') + .storeOptionsAsProperties(false) + .option('-t, --trace') + // Old code before Commander 7 + .action((filename, command)) => { + if (command.opts().trace) console.log(`Command name is ${command.name()}`); + }); +``` + +```js + // New code + .action((filename, options, command)) => { + if (command.opts().trace) console.log(`Command name is ${command.name()}`); + }); +``` + +**excess command-arguments** + +There is now an error if there are too many command-arguments on the command line (only checked if there is an action handler). +If the extra arguments are supported by your command then you can either declare the expected arguments, or allow excess arguments. + +```js +// Old code before Commander 7 +program + .action(() => {}); +program.parse(['a', 'b', 'c'], { from: 'user' }); // no error +``` + +```js +// New code, declare arguments +program + .arguments('[args...]) + .action(() => {}); +``` + +```js +// New code, allow excess arguments +program + .allowExcessArguments() + .action(() => {}); ``` ## [7.0.0-1] (2020-11-21) From 6f4238b9c7f138b62965c16f4364976af9fb9629 Mon Sep 17 00:00:00 2001 From: John Gee Date: Sun, 6 Dec 2020 14:25:08 +1300 Subject: [PATCH 18/24] Minor improvements to migrations tips --- CHANGELOG.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5fb197ca3..dd8207b8d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Migration Tips -The biggest change is the parsed option values. Previously the options were stored by default as properties on the command object, and now the options are now stored separately. +The biggest change is the parsed option values. Previously the options were stored by default as properties on the command object, and now the options are stored separately. If you wish to restore the old behaviour and get running quickly you can call `.storeOptionsAsProperties()`. To allow you to move to the new code patterns incrementally, the action handler will be passed the command _twice_, @@ -38,7 +38,7 @@ if (options.debug) console.log(`Program name is ${program.name()}`); **action handler** The action handler gets passed a parameter for each command-argument you declared. Previously by default the next parameter was the command object with the options as properties. Now the next two parameters are instead the options and the command. If you -only access the options there may be no code changes required. +only accessed the options there may be no code changes required. ```js program @@ -86,13 +86,13 @@ If the extra arguments are supported by your command then you can either declare // Old code before Commander 7 program .action(() => {}); -program.parse(['a', 'b', 'c'], { from: 'user' }); // no error +program.parse(['a', 'b', 'c'], { from: 'user' }); // now causes an error ``` ```js // New code, declare arguments program - .arguments('[args...]) + .arguments('[args...]') .action(() => {}); ``` From 31990ee75b7c52c41884c78594ba0a6b57385f76 Mon Sep 17 00:00:00 2001 From: John Gee Date: Sun, 6 Dec 2020 17:01:43 +1300 Subject: [PATCH 19/24] Update examples for arguments --- Readme.md | 33 ++++++++++++--------------------- examples/arguments.js | 26 ++++++++++++++++++++++++++ examples/env | 29 ----------------------------- 3 files changed, 38 insertions(+), 50 deletions(-) create mode 100755 examples/arguments.js delete mode 100755 examples/env diff --git a/Readme.md b/Readme.md index 046c71df8..dd607952a 100644 --- a/Readme.md +++ b/Readme.md @@ -416,43 +416,34 @@ included in the `.command` call. Angled brackets (e.g. ``) indicate re Square brackets (e.g. `[optional]`) indicate optional command-arguments. You can optionally describe the arguments in the help by supplying a hash as second parameter to `.description()`. -Example file: [env](./examples/env) +Example file: [env](./examples/arguments.js) ```js program .version('0.1.0') - .arguments(' [env]') + .arguments(' [password]') .description('test command', { - cmd: 'command to run', - env: 'environment to run test in' + username: 'user to login', + password: 'password for user, if required' }) - .action(function (cmd, env) { - console.log('command:', cmd); - console.log('environment:', env || 'no environment given'); + .action((username, password) => { + console.log('username:', username); + console.log('environment:', password || 'no password given'); }); - -program.parse(process.argv); ``` The last argument of a command can be variadic, and only the last argument. To make an argument variadic you append `...` to the argument name. For example: ```js -const { program } = require('commander'); - program .version('0.1.0') - .command('rmdir [otherDirs...]') - .action(function (dir, otherDirs) { - console.log('rmdir %s', dir); - if (otherDirs) { - otherDirs.forEach(function (oDir) { - console.log('rmdir %s', oDir); - }); - } + .command('rmdir ') + .action(function (dirs) { + dirs.forEach((dir) => { + console.log('rmdir %s', dir); + }); }); - -program.parse(process.argv); ``` The variadic argument is passed to the action handler as an array. diff --git a/examples/arguments.js b/examples/arguments.js new file mode 100755 index 000000000..48372c648 --- /dev/null +++ b/examples/arguments.js @@ -0,0 +1,26 @@ +#!/usr/bin/env node + +// This example shows specifying the arguments for the program to pass to the action handler. + +// const { Command } = require('commander'); // (normal include) +const { Command } = require('../'); // include commander in git clone of commander repo +const program = new Command(); + +program + .version('0.1.0') + .arguments(' [password]') + .description('test command', { + username: 'user to login', + password: 'password for user, if required' + }) + .action((username, password) => { + console.log('username:', username); + console.log('environment:', password || 'no password given'); + }); + +program.parse(); + +// Try the following: +// node arguments.js --help +// node arguments.js user +// node arguments.js user secret diff --git a/examples/env b/examples/env deleted file mode 100755 index 836a72c13..000000000 --- a/examples/env +++ /dev/null @@ -1,29 +0,0 @@ -#!/usr/bin/env node - -// This example shows specifying the arguments for the program to pass to the action handler. - -// const { Command } = require('commander'); // (normal include) -const { Command } = require('../'); // include commander in git clone of commander repo -const program = new Command(); - -let cmdValue; -let envValue; - -program - .version('0.0.1') - .arguments(' [env]') - .description('test command', { - cmd: 'command to run', - env: 'environment to run test in' - }) - .action(function(cmdValue, envValue) { - console.log('command:', cmdValue); - console.log('environment:', envValue || 'no environment given'); - }); - -program.parse(process.argv); - -// Try the following: -// node env --help -// node env add -// node env add browser From 768974a037462905895cd7ca9844b9edf4f144f5 Mon Sep 17 00:00:00 2001 From: John Gee Date: Sun, 6 Dec 2020 17:29:24 +1300 Subject: [PATCH 20/24] Update example for action handler --- Readme.md | 28 +++++++++++++++------------- examples/thank.js | 26 ++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 13 deletions(-) create mode 100644 examples/thank.js diff --git a/Readme.md b/Readme.md index dd607952a..489cd05e4 100644 --- a/Readme.md +++ b/Readme.md @@ -416,7 +416,7 @@ included in the `.command` call. Angled brackets (e.g. ``) indicate re Square brackets (e.g. `[optional]`) indicate optional command-arguments. You can optionally describe the arguments in the help by supplying a hash as second parameter to `.description()`. -Example file: [env](./examples/arguments.js) +Example file: [arguments.js](./examples/arguments.js) ```js program @@ -450,21 +450,23 @@ The variadic argument is passed to the action handler as an array. ### Action handler -The action handler gets passed a parameter for each command-argument you declared, and two additional parameters which are the parsed options and the -command object itself. +The action handler gets passed a parameter for each command-argument you declared, and two additional parameters +which are the parsed options and the command object itself. -```js -const { program } = require('commander'); +Example file: [thank.js](./examples/thank.js) +```js program - .command('rm ') - .option('-r, --recursive', 'Remove recursively') - .action(function (dir, options, command) { - const recursively = options.recursive ? ' recursively' : ''; - console.log(`${command.name}${recursively}: ${dir}`) - }) - -program.parse(process.argv) + .arguments('') + .option('-t, --title ', 'title to use before name') + .option('-d, --debug', 'display some debugging') + .action((name, options, command) => { + if (options.debug) { + console.error('Called %s with options %o', command.name(), options); + } + const title = options.title ? `${options.title} ` : ''; + console.log(`Thank-you ${title}${name}`); + }); ``` You may supply an `async` action handler, in which case you call `.parseAsync` rather than `.parse`. diff --git a/examples/thank.js b/examples/thank.js new file mode 100644 index 000000000..acceb6b6b --- /dev/null +++ b/examples/thank.js @@ -0,0 +1,26 @@ +#!/usr/bin/env node + +// This example is used as an example in the README for the action handler. + +// const { Command } = require('commander'); // (normal include) +const { Command } = require('../'); // include commander in git clone of commander repo +const program = new Command(); + +program + .arguments('') + .option('-t, --title ', 'title to use before name') + .option('-d, --debug', 'display some debugging') + .action((name, options, command) => { + if (options.debug) { + console.error('Called %s with options %o', command.name(), options); + } + const title = options.title ? `${options.title} ` : ''; + console.log(`Thank-you ${title}${name}`); + }); + +program.parse(); + +// Try the following: +// node thank.js John +// node thank.js Doe --title Mr +// node thank.js --debug Doe --title Mr From de93d9cbaad917c2124d636f67823c205d3b4b66 Mon Sep 17 00:00:00 2001 From: John Gee Date: Sun, 6 Dec 2020 18:19:39 +1300 Subject: [PATCH 21/24] Add allowUnknownOption and allowExcessArguments to README --- Readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Readme.md b/Readme.md index 489cd05e4..fcb3160da 100644 --- a/Readme.md +++ b/Readme.md @@ -482,7 +482,7 @@ async function main() { } ``` -A command's options and arguments on the command line are validated when the command is used. Any unknown options or missing arguments or unexpected arguments will be reported as an error. +A command's options and arguments on the command line are validated when the command is used. Any unknown options or unexpected command-arguments will be reported as an error, or you can suppress these checks with `.allowUnknownOption()` and `.allowExcessArguments()`. ### Stand-alone executable (sub)commands From 4de66a8d4855e4cdfcc46cbe915ebffa4efea5a8 Mon Sep 17 00:00:00 2001 From: John Gee Date: Sun, 6 Dec 2020 18:37:42 +1300 Subject: [PATCH 22/24] Update pizza example --- examples/pizza | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/examples/pizza b/examples/pizza index 2e55b58df..9d4d3ae7e 100755 --- a/examples/pizza +++ b/examples/pizza @@ -13,12 +13,13 @@ program program.parse(process.argv); +const options = program.opts(); console.log('you ordered a pizza with:'); -if (program.peppers) console.log(' - peppers'); -if (program.pineapple) console.log(' - pineapple'); -if (program.bbq) console.log(' - bbq'); +if (options.peppers) console.log(' - peppers'); +if (options.pineapple) console.log(' - pineapple'); +if (options.bbq) console.log(' - bbq'); -const cheese = !program.cheese ? 'no' : program.cheese; +const cheese = !options.cheese ? 'no' : options.cheese; console.log(' - %s cheese', cheese); -console.log(program.args); +console.log(options.args); From 6727ccb0bf389bc9f8d10b190c2cc09899d122ff Mon Sep 17 00:00:00 2001 From: John Gee Date: Mon, 7 Dec 2020 21:41:37 +1300 Subject: [PATCH 23/24] Reuse pizza as single command example. Tidy up pizza and deploy. --- Readme.md | 57 ++++++++++++++++++++++++++++++++++--------------- examples/deploy | 21 +++++++++--------- examples/pizza | 10 ++------- 3 files changed, 52 insertions(+), 36 deletions(-) diff --git a/Readme.md b/Readme.md index fcb3160da..f484ec297 100644 --- a/Readme.md +++ b/Readme.md @@ -518,10 +518,9 @@ Example file: [pizza](./examples/pizza) $ node ./examples/pizza --help Usage: pizza [options] -An application for pizzas ordering +An application for pizza ordering Options: - -V, --version output the version number -p, --peppers Add peppers -c, --cheese Add the specified type of cheese (default: "marble") -C, --no-cheese You do not want any cheese @@ -801,44 +800,68 @@ There is more information available about: ## Examples -Example file: [deploy](./examples/deploy) +In a single command program, you might not need an action handler. + +Example file: [pizza](./examples/pizza) ```js const { program } = require('commander'); program - .version('0.1.0') - .option('-C, --chdir ', 'change the working directory') - .option('-c, --config ', 'set config path. defaults to ./deploy.conf') - .option('-T, --no-tests', 'ignore test hook'); + .description('An application for pizza ordering') + .option('-p, --peppers', 'Add peppers') + .option('-c, --cheese ', 'Add the specified type of cheese', 'marble') + .option('-C, --no-cheese', 'You do not want any cheese'); + +program.parse(); + +const options = program.opts(); +console.log('you ordered a pizza with:'); +if (options.peppers) console.log(' - peppers'); +const cheese = !options.cheese ? 'no' : options.cheese; +console.log(' - %s cheese', cheese); +``` + +In a multi-command file, you will have action handlers for each command (or stand-alone executables for the commands). + +Example file: [deploy](./examples/deploy) + +```js +const { Command } = require('commander'); +const program = new Command(); + +program + .version('0.0.1') + .option('-c, --config ', 'set config path', './deploy.conf'); program .command('setup [env]') .description('run setup commands for all envs') - .option("-s, --setup_mode [mode]", "Which setup mode to use") - .action(function(env, options){ - const mode = options.setup_mode || "normal"; + .option('-s, --setup_mode ', 'Which setup mode to use', 'normal') + .action((env, options) => { env = env || 'all'; - console.log('setup for %s env(s) with %s mode', env, mode); + console.log('read config from %s', program.opts().config); + console.log('setup for %s env(s) with %s mode', env, options.setup_mode); }); program - .command('exec ') + .command('exec