diff --git a/Readme.md b/Readme.md index ce3f25d59..39c532f10 100644 --- a/Readme.md +++ b/Readme.md @@ -23,6 +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) + - [Custom argument processing](#custom-argument-processing) - [Action handler](#action-handler) - [Stand-alone executable (sub)commands](#stand-alone-executable-subcommands) - [Automated help](#automated-help) @@ -342,7 +343,7 @@ function myParseInt(value, dummyPrevious) { // parseInt takes a string and a radix const parsedValue = parseInt(value, 10); if (isNaN(parsedValue)) { - throw new commander.InvalidOptionArgumentError('Not a number.'); + throw new commander.InvalidArgumentError('Not a number.'); } return parsedValue; } @@ -434,6 +435,8 @@ you can instead use the following method. To configure a command, you can use `.argument()` to specify each expected command-argument. You supply the argument name and an optional description. The argument may be `` or `[optional]`. +You can specify a default value for an optional command-argument. + Example file: [argument.js](./examples/argument.js) @@ -441,10 +444,10 @@ Example file: [argument.js](./examples/argument.js) program .version('0.1.0') .argument('', 'user to login') - .argument('[password]', 'password for user, if required') + .argument('[password]', 'password for user, if required', 'no password given') .action((username, password) => { console.log('username:', username); - console.log('password:', password || 'no password given'); + console.log('password:', password); }); ``` @@ -470,6 +473,27 @@ program .arguments(' '); ``` +### Custom argument processing + +You may specify a function to do custom processing of command-arguments before they are passed to the action handler. +The callback function receives two parameters, the user specified command-argument and the previous value for the argument. +It returns the new value for the argument. + +You can optionally specify the default/starting value for the argument after the function parameter. + +Example file: [arguments-custom-processing.js](./examples/arguments-custom-processing.js) + +```js +program + .command('add') + .argument('', 'integer argument', myParseInt) + .argument('[second]', 'integer argument', myParseInt, 1000) + .action((first, second) => { + console.log(`${first} + ${second} = ${first + second}`); + }) +; +``` + ### Action handler The action handler gets passed a parameter for each command-argument you declared, and two additional parameters diff --git a/Readme_zh-CN.md b/Readme_zh-CN.md index c3cfd2433..65865b7ab 100644 --- a/Readme_zh-CN.md +++ b/Readme_zh-CN.md @@ -314,7 +314,7 @@ function myParseInt(value, dummyPrevious) { // parseInt takes a string and a radix const parsedValue = parseInt(value, 10); if (isNaN(parsedValue)) { - throw new commander.InvalidOptionArgumentError('Not a number.'); + throw new commander.InvalidArgumentError('Not a number.'); } return parsedValue; } @@ -877,4 +877,4 @@ program.parse(process.argv); 现在 Commander 已作为 Tidelift 订阅的一部分。 -Commander 和很多其他包的维护者已与 Tidelift 合作,面向企业提供开源依赖的商业支持与维护。企业可以向相关依赖包的维护者支付一定的费用,帮助企业节省时间,降低风险,改进代码运行情况。[了解更多](https://tidelift.com/subscription/pkg/npm-commander?utm_source=npm-commander&utm_medium=referral&utm_campaign=enterprise&utm_term=repo) \ No newline at end of file +Commander 和很多其他包的维护者已与 Tidelift 合作,面向企业提供开源依赖的商业支持与维护。企业可以向相关依赖包的维护者支付一定的费用,帮助企业节省时间,降低风险,改进代码运行情况。[了解更多](https://tidelift.com/subscription/pkg/npm-commander?utm_source=npm-commander&utm_medium=referral&utm_campaign=enterprise&utm_term=repo) diff --git a/docs/deprecated.md b/docs/deprecated.md index d81d12073..148ba18b6 100644 --- a/docs/deprecated.md +++ b/docs/deprecated.md @@ -111,3 +111,32 @@ program Deprecated from Commander v8. +## InvalidOptionArgumentError + +This was used for throwing an error from custom option processing, for a nice error message. + +```js +function myParseInt(value, dummyPrevious) { + // parseInt takes a string and a radix + const parsedValue = parseInt(value, 10); + if (isNaN(parsedValue)) { + throw new commander.InvalidOptionArgumentError('Not a number.'); + } + return parsedValue; +} +``` + +The replacement is `InvalidArgumentError` since can be used now for custom command-argument processing too. + +```js +function myParseInt(value, dummyPrevious) { + // parseInt takes a string and a radix + const parsedValue = parseInt(value, 10); + if (isNaN(parsedValue)) { + throw new commander.InvalidArgumentError('Not a number.'); + } + return parsedValue; +} +``` + +Deprecated from Commander v8. diff --git a/esm.mjs b/esm.mjs index 5ed09724e..dc6bd11c9 100644 --- a/esm.mjs +++ b/esm.mjs @@ -7,7 +7,7 @@ export const { createArgument, createOption, CommanderError, - InvalidOptionArgumentError, + InvalidArgumentError, Command, Argument, Option, diff --git a/examples/argument.js b/examples/argument.js index 83c779eae..30515e919 100644 --- a/examples/argument.js +++ b/examples/argument.js @@ -9,11 +9,11 @@ const program = new Command(); program .version('0.1.0') .argument('', 'user to login') - .argument('[password]', 'password for user, if required') + .argument('[password]', 'password for user, if required', 'no password given') .description('example program for argument') .action((username, password) => { console.log('username:', username); - console.log('password:', password || 'no password given'); + console.log('password:', password); }); program.parse(); diff --git a/examples/arguments-custom-processing.js b/examples/arguments-custom-processing.js new file mode 100644 index 000000000..ac92855f6 --- /dev/null +++ b/examples/arguments-custom-processing.js @@ -0,0 +1,47 @@ +#!/usr/bin/env node + +// This is used as an example in the README for: +// Custom argument processing +// You may specify a function to do custom processing of argument values. + +// const commander = require('commander'); // (normal include) +const commander = require('../'); // include commander in git clone of commander repo +const program = new commander.Command(); + +function myParseInt(value, dummyPrevious) { + // parseInt takes a string and a radix + const parsedValue = parseInt(value, 10); + if (isNaN(parsedValue)) { + throw new commander.InvalidArgumentError('Not a number.'); + } + return parsedValue; +} + +// The previous value passed to the custom processing is used when processing variadic values. +function mySum(value, total) { + return total + myParseInt(value); +} + +program + .command('add') + .argument('', 'integer argument', myParseInt) + .argument('[second]', 'integer argument', myParseInt, 1000) + .action((first, second) => { + console.log(`${first} + ${second} = ${first + second}`); + }); + +program + .command('sum') + .argument('', 'values to be summed', mySum, 0) + .action((total) => { + console.log(`sum is ${total}`); + }); + +program.parse(); + +// Try the following: +// node arguments-custom-processing add --help +// node arguments-custom-processing add 2 +// node arguments-custom-processing add 12 56 +// node arguments-custom-processing sum 1 2 3 +// node arguments-custom-processing sum silly diff --git a/examples/options-custom-processing.js b/examples/options-custom-processing.js index 3e601e5df..1325fee3b 100755 --- a/examples/options-custom-processing.js +++ b/examples/options-custom-processing.js @@ -12,7 +12,7 @@ function myParseInt(value, dummyPrevious) { // parseInt takes a string and a radix const parsedValue = parseInt(value, 10); if (isNaN(parsedValue)) { - throw new commander.InvalidOptionArgumentError('Not a number.'); + throw new commander.InvalidArgumentError('Not a number.'); } return parsedValue; } diff --git a/index.js b/index.js index 3b17151c9..9ae19a90d 100644 --- a/index.js +++ b/index.js @@ -258,6 +258,13 @@ class Help { */ argumentDescription(argument) { + const extraInfo = []; + if (argument.defaultValue !== undefined) { + extraInfo.push(`default: ${argument.defaultValueDescription || JSON.stringify(argument.defaultValue)}`); + } + if (extraInfo.length > 0) { + return `${argument.description} (${extraInfo.join(', ')})`; + } return argument.description; } @@ -385,6 +392,9 @@ class Argument { constructor(name, description) { this.description = description || ''; this.variadic = false; + this.parseArg = undefined; + this.defaultValue = undefined; + this.defaultValueDescription = undefined; switch (name[0]) { case '<': // e.g. @@ -416,6 +426,32 @@ class Argument { name() { return this._name; }; + + /** + * Set the default value, and optionally supply the description to be displayed in the help. + * + * @param {any} value + * @param {string} [description] + * @return {Argument} + */ + + default(value, description) { + this.defaultValue = value; + this.defaultValueDescription = description; + return this; + }; + + /** + * Set the custom handler for processing CLI command arguments into argument values. + * + * @param {Function} [fn] + * @return {Argument} + */ + + argParser(fn) { + this.parseArg = fn; + return this; + }; } class Option { @@ -522,7 +558,7 @@ class Option { this.argChoices = values; this.parseArg = (arg, previous) => { if (!values.includes(arg)) { - throw new InvalidOptionArgumentError(`Allowed choices are ${values.join(', ')}.`); + throw new InvalidArgumentError(`Allowed choices are ${values.join(', ')}.`); } if (this.variadic) { return this._concatValue(arg, previous); @@ -594,17 +630,17 @@ class CommanderError extends Error { } /** - * InvalidOptionArgumentError class + * InvalidArgumentError class * @class */ -class InvalidOptionArgumentError extends CommanderError { +class InvalidArgumentError extends CommanderError { /** - * Constructs the InvalidOptionArgumentError class + * Constructs the InvalidArgumentError class * @param {string} [message] explanation of why argument is invalid * @constructor */ constructor(message) { - super(1, 'commander.invalidOptionArgument', message); + super(1, 'commander.invalidArgument', message); // properly capture stack trace in Node.js Error.captureStackTrace(this, this.constructor); this.name = this.constructor.name; @@ -864,10 +900,17 @@ class Command extends EventEmitter { * * @param {string} name * @param {string} [description] + * @param {Function|*} [fn] - custom argument processing function + * @param {*} [defaultValue] * @return {Command} `this` command for chaining */ - argument(name, description) { + argument(name, description, fn, defaultValue) { const argument = this.createArgument(name, description); + if (typeof fn === 'function') { + argument.default(defaultValue).argParser(fn); + } else { + argument.default(fn); + } this.addArgument(argument); return this; } @@ -903,6 +946,9 @@ class Command extends EventEmitter { if (previousArgument && previousArgument.variadic) { throw new Error(`only the last argument can be variadic '${previousArgument.name()}'`); } + if (argument.required && argument.defaultValue !== undefined && argument.parseArg === undefined) { + throw new Error(`a default value for a required argument is never used: '${argument.name()}'`); + } this._args.push(argument); return this; } @@ -1076,7 +1122,7 @@ class Command extends EventEmitter { try { val = option.parseArg(val, oldValue === undefined ? defaultValue : oldValue); } catch (err) { - if (err.code === 'commander.invalidOptionArgument') { + if (err.code === 'commander.invalidArgument') { const message = `error: option '${option.flags}' argument '${val}' is invalid. ${err.message}`; this._displayError(err.exitCode, err.code, message); } @@ -1525,6 +1571,7 @@ class Command extends EventEmitter { /** * @api private */ + _dispatchSubcommand(commandName, operands, unknown) { const subCommand = this._findCommand(commandName); if (!subCommand) this.help({ error: true }); @@ -1536,6 +1583,57 @@ class Command extends EventEmitter { } }; + /** + * Package arguments (this.args) for passing to action handler based + * on declared arguments (this._args). + * + * @api private + */ + + _getActionArguments() { + const myParseArg = (argument, value, previous) => { + // Extra processing for nice error message on parsing failure. + let parsedValue = value; + if (value !== null && argument.parseArg) { + try { + parsedValue = argument.parseArg(value, previous); + } catch (err) { + if (err.code === 'commander.invalidArgument') { + const message = `error: command-argument value '${value}' is invalid for argument '${argument.name()}'. ${err.message}`; + this._displayError(err.exitCode, err.code, message); + } + throw err; + } + } + return parsedValue; + }; + + const actionArgs = []; + this._args.forEach((declaredArg, index) => { + let value = declaredArg.defaultValue; + if (declaredArg.variadic) { + // Collect together remaining arguments for passing together as an array. + if (index < this.args.length) { + value = this.args.slice(index); + if (declaredArg.parseArg) { + value = value.reduce((processed, v) => { + return myParseArg(declaredArg, v, processed); + }, declaredArg.defaultValue); + } + } else if (value === undefined) { + value = []; + } + } else if (index < this.args.length) { + value = this.args[index]; + if (declaredArg.parseArg) { + value = myParseArg(declaredArg, value, declaredArg.defaultValue); + } + } + actionArgs[index] = value; + }); + return actionArgs; + } + /** * Process arguments in context of this command. * @@ -1594,14 +1692,7 @@ class Command extends EventEmitter { if (this._actionHandler) { checkForUnknownOptions(); checkNumberOfArguments(); - // Collect trailing args into variadic. - let actionArgs = this.args; - const declaredArgCount = this._args.length; - if (declaredArgCount > 0 && this._args[declaredArgCount - 1].variadic) { - actionArgs = this.args.slice(0, declaredArgCount - 1); - actionArgs[declaredArgCount - 1] = this.args.slice(declaredArgCount - 1); - } - this._actionHandler(actionArgs); + this._actionHandler(this._getActionArguments()); if (this.parent) this.parent.emit(commandEvent, operands, unknown); // legacy } else if (this.parent && this.parent.listenerCount(commandEvent)) { checkForUnknownOptions(); @@ -2204,7 +2295,8 @@ exports.Command = Command; exports.Option = Option; exports.Argument = Argument; exports.CommanderError = CommanderError; -exports.InvalidOptionArgumentError = InvalidOptionArgumentError; +exports.InvalidArgumentError = InvalidArgumentError; +exports.InvalidOptionArgumentError = InvalidArgumentError; // Deprecated exports.Help = Help; /** diff --git a/tests/argument.custom-processing.test.js b/tests/argument.custom-processing.test.js new file mode 100644 index 000000000..480fa73d6 --- /dev/null +++ b/tests/argument.custom-processing.test.js @@ -0,0 +1,144 @@ +const commander = require('../'); + +// Testing default value and custom processing behaviours. + +test('when argument not specified then callback not called', () => { + const mockCoercion = jest.fn(); + const program = new commander.Command(); + program + .argument('[n]', 'number', mockCoercion) + .action(() => {}); + program.parse([], { from: 'user' }); + expect(mockCoercion).not.toHaveBeenCalled(); +}); + +test('when argument not specified then action argument undefined', () => { + let actionValue = 'foo'; + const program = new commander.Command(); + program + .argument('[n]', 'number', parseFloat) + .action((arg) => { + actionValue = arg; + }); + program.parse([], { from: 'user' }); + expect(actionValue).toBeUndefined(); +}); + +test('when custom with starting value and argument not specified then callback not called', () => { + const mockCoercion = jest.fn(); + const program = new commander.Command(); + program + .argument('[n]', 'number', parseFloat, 1) + .action(() => {}); + program.parse([], { from: 'user' }); + expect(mockCoercion).not.toHaveBeenCalled(); +}); + +test('when custom with starting value and argument not specified then action argument is starting value', () => { + const startingValue = 1; + let actionValue; + const program = new commander.Command(); + program + .argument('[n]', 'number', parseFloat, startingValue) + .action((arg) => { + actionValue = arg; + }); + program.parse([], { from: 'user' }); + expect(actionValue).toEqual(startingValue); +}); + +test('when default value is defined (without custom processing) and argument not specified then action argument is default value', () => { + const defaultValue = 1; + let actionValue; + const program = new commander.Command(); + program + .argument('[n]', 'number', defaultValue) + .action((arg) => { + actionValue = arg; + }); + program.parse([], { from: 'user' }); + expect(actionValue).toEqual(defaultValue); +}); + +test('when argument specified then callback called with value', () => { + const mockCoercion = jest.fn(); + const value = '1'; + const program = new commander.Command(); + program + .argument('[n]', 'number', mockCoercion) + .action(() => {}); + program.parse([value], { from: 'user' }); + expect(mockCoercion).toHaveBeenCalledWith(value, undefined); +}); + +test('when argument specified then action value is as returned from callback', () => { + const callbackResult = 2; + let actionValue; + const program = new commander.Command(); + program + .argument('[n]', 'number', () => { + return callbackResult; + }) + .action((arg) => { + actionValue = arg; + }); + program.parse(['node', 'test', 'alpha']); + expect(actionValue).toEqual(callbackResult); +}); + +test('when argument specified then program.args has original rather than custom', () => { + // This is as intended, so check behaviour. + const callbackResult = 2; + const program = new commander.Command(); + program + .argument('[n]', 'number', () => { + return callbackResult; + }) + .action(() => {}); + program.parse(['node', 'test', 'alpha']); + expect(program.args).toEqual(['alpha']); +}); + +test('when custom with starting value and argument specified then callback called with value and starting value', () => { + const mockCoercion = jest.fn(); + const startingValue = 1; + const value = '2'; + const program = new commander.Command() + .argument('[n]', 'number', mockCoercion, startingValue) + .action(() => {}); + program.parse(['node', 'test', value]); + expect(mockCoercion).toHaveBeenCalledWith(value, startingValue); +}); + +test('when variadic argument specified multiple times then callback called with value and previousValue', () => { + const mockCoercion = jest.fn().mockImplementation(() => { + return 'callback'; + }); + const program = new commander.Command(); + program + .argument('', 'number', mockCoercion) + .action(() => {}); + program.parse(['1', '2'], { from: 'user' }); + expect(mockCoercion).toHaveBeenCalledTimes(2); + expect(mockCoercion).toHaveBeenNthCalledWith(1, '1', undefined); + expect(mockCoercion).toHaveBeenNthCalledWith(2, '2', 'callback'); +}); + +test('when parseFloat "1e2" then action argument is 100', () => { + let actionValue; + const program = new commander.Command(); + program + .argument('', 'float argument', parseFloat) + .action((arg) => { + actionValue = arg; + }); + program.parse(['1e2'], { from: 'user' }); + expect(actionValue).toEqual(100); +}); + +test('when defined default value for required argument then throw', () => { + const program = new commander.Command(); + expect(() => { + program.argument('', 'float argument', 4); + }).toThrow(); +}); diff --git a/tests/command.exitOverride.test.js b/tests/command.exitOverride.test.js index 6bf3b134b..e40856047 100644 --- a/tests/command.exitOverride.test.js +++ b/tests/command.exitOverride.test.js @@ -272,12 +272,12 @@ describe('.exitOverride and error details', () => { caughtErr = err; } - expectCommanderError(caughtErr, 1, 'commander.invalidOptionArgument', "error: option '--colour ' argument 'green' is invalid. Allowed choices are red, blue."); + expectCommanderError(caughtErr, 1, 'commander.invalidArgument', "error: option '--colour ' argument 'green' is invalid. Allowed choices are red, blue."); }); - test('when custom processing throws InvalidOptionArgumentError then throw CommanderError', () => { + test('when custom processing for option throws InvalidArgumentError then catch CommanderError', () => { function justSayNo(value) { - throw new commander.InvalidOptionArgumentError('NO'); + throw new commander.InvalidArgumentError('NO'); } const optionFlags = '--colour '; const program = new commander.Command(); @@ -292,7 +292,27 @@ describe('.exitOverride and error details', () => { caughtErr = err; } - expectCommanderError(caughtErr, 1, 'commander.invalidOptionArgument', "error: option '--colour ' argument 'green' is invalid. NO"); + expectCommanderError(caughtErr, 1, 'commander.invalidArgument', "error: option '--colour ' argument 'green' is invalid. NO"); + }); + + test('when custom processing for argument throws InvalidArgumentError then catch CommanderError', () => { + function justSayNo(value) { + throw new commander.InvalidArgumentError('NO'); + } + const program = new commander.Command(); + program + .exitOverride() + .argument('[n]', 'number', justSayNo) + .action(() => {}); + + let caughtErr; + try { + program.parse(['green'], { from: 'user' }); + } catch (err) { + caughtErr = err; + } + + expectCommanderError(caughtErr, 1, 'commander.invalidArgument', "error: command-argument value 'green' is invalid for argument 'n'. NO"); }); }); diff --git a/tests/command.help.test.js b/tests/command.help.test.js index ec1ba4172..ce76cbfa4 100644 --- a/tests/command.help.test.js +++ b/tests/command.help.test.js @@ -253,6 +253,15 @@ test('when argument described then included in helpInformation', () => { expect(helpInformation).toMatch(/Arguments:\n +file +input source/); }); +test('when argument described with default then included in helpInformation', () => { + const program = new commander.Command(); + program + .argument('[file]', 'input source', 'test.txt') + .helpOption(false); + const helpInformation = program.helpInformation(); + expect(helpInformation).toMatch(/Arguments:\n +file +input source \(default: "test.txt"\)/); +}); + test('when arguments described in deprecated way then included in helpInformation', () => { const program = new commander.Command(); program diff --git a/tests/esm-imports-test.mjs b/tests/esm-imports-test.mjs index 03b25bb8c..1ad478ac6 100644 --- a/tests/esm-imports-test.mjs +++ b/tests/esm-imports-test.mjs @@ -1,4 +1,4 @@ -import { program, Command, Option, Argument, CommanderError, InvalidOptionArgumentError, Help, createCommand, createArgument, createOption } from '../esm.mjs'; +import { program, Command, Option, Argument, CommanderError, InvalidArgumentError, Help, createCommand, createArgument, createOption } from '../esm.mjs'; // Do some simple checks that expected imports are available at runtime. // Run using `npm run test-esm`. @@ -24,7 +24,7 @@ check(program.constructor.name === 'Command', 'program is class Command'); checkClass(new Command(), 'Command'); checkClass(new Option('-e, --example'), 'Option'); checkClass(new CommanderError(1, 'code', 'failed'), 'CommanderError'); -checkClass(new InvalidOptionArgumentError('failed'), 'InvalidOptionArgumentError'); +checkClass(new InvalidArgumentError('failed'), 'InvalidArgumentError'); checkClass(new Help(), 'Help'); checkClass(new Argument(''), 'Argument'); diff --git a/tests/help.argumentDescription.test.js b/tests/help.argumentDescription.test.js new file mode 100644 index 000000000..a2ada4ec9 --- /dev/null +++ b/tests/help.argumentDescription.test.js @@ -0,0 +1,30 @@ +const commander = require('../'); + +// These are tests of the Help class, not of the Command help. + +describe('argumentDescription', () => { + test('when argument has no description then empty string', () => { + const argument = new commander.Argument('[n]'); + const helper = new commander.Help(); + expect(helper.argumentDescription(argument)).toEqual(''); + }); + + test('when argument has description then return description', () => { + const description = 'description'; + const argument = new commander.Argument('[n]', description); + const helper = new commander.Help(); + expect(helper.argumentDescription(argument)).toEqual(description); + }); + + test('when argument has default value then return description and default value', () => { + const argument = new commander.Argument('[n]', 'description').default('default'); + const helper = new commander.Help(); + expect(helper.argumentDescription(argument)).toEqual('description (default: "default")'); + }); + + test('when argument has default value description then return description and custom default description', () => { + const argument = new commander.Argument('[n]', 'description').default('default value', 'custom'); + const helper = new commander.Help(); + expect(helper.argumentDescription(argument)).toEqual('description (default: custom)'); + }); +}); diff --git a/tests/ts-imports.test.ts b/tests/ts-imports.test.ts index 70030a082..955c155ab 100644 --- a/tests/ts-imports.test.ts +++ b/tests/ts-imports.test.ts @@ -1,4 +1,4 @@ -import { program, Command, Option, CommanderError, InvalidOptionArgumentError, Help, createCommand } from '../'; +import { program, Command, Option, CommanderError, InvalidArgumentError, InvalidOptionArgumentError, Help, createCommand } from '../'; import * as commander from '../'; @@ -35,10 +35,15 @@ test('CommanderError', () => { checkClass(new CommanderError(1, 'code', 'failed'), 'CommanderError'); }); -test('InvalidOptionArgumentError', () => { - checkClass(new InvalidOptionArgumentError('failed'), 'InvalidOptionArgumentError'); +test('InvalidArgumentError', () => { + checkClass(new InvalidArgumentError('failed'), 'InvalidArgumentError'); }); +test('InvalidOptionArgumentError', () => { // Deprecated + checkClass(new InvalidOptionArgumentError('failed'), 'InvalidArgumentError'); +}); + + test('Help', () => { checkClass(new Help(), 'Help'); }); diff --git a/typings/index.d.ts b/typings/index.d.ts index 904c48704..64e08d00d 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -16,9 +16,9 @@ declare namespace commander { type CommanderErrorConstructor = new (exitCode: number, code: string, message: string) => CommanderError; // eslint-disable-next-line @typescript-eslint/no-empty-interface - interface InvalidOptionArgumentError extends CommanderError { + interface InvalidArgumentError extends CommanderError { } - type InvalidOptionArgumentErrorConstructor = new (message: string) => InvalidOptionArgumentError; + type InvalidArgumentErrorConstructor = new (message: string) => InvalidArgumentError; interface Argument { description: string; @@ -270,7 +270,8 @@ declare namespace commander { * * @returns `this` command for chaining */ - argument(name: string, description?: string): this; + argument(flags: string, description: string, fn: (value: string, previous: T) => T, defaultValue?: T): this; + argument(name: string, description?: string, defaultValue?: any): this; /** * Define argument syntax for command, adding a prepared argument. @@ -671,7 +672,9 @@ declare namespace commander { Option: OptionConstructor; Argument: ArgumentConstructor; CommanderError: CommanderErrorConstructor; - InvalidOptionArgumentError: InvalidOptionArgumentErrorConstructor; + InvalidArgumentError: InvalidArgumentErrorConstructor; + /** @deprecated since v8, replaced by InvalidArgumentError */ + InvalidOptionArgumentError: InvalidArgumentErrorConstructor; Help: HelpConstructor; } diff --git a/typings/index.test-d.ts b/typings/index.test-d.ts index 4e42c5854..0f5495952 100644 --- a/typings/index.test-d.ts +++ b/typings/index.test-d.ts @@ -15,7 +15,8 @@ expectType(new commander.Command()); expectType(new commander.Command('name')); expectType(new commander.Option('-f')); expectType(new commander.CommanderError(1, 'code', 'message')); -expectType(new commander.InvalidOptionArgumentError('message')); +expectType(new commander.InvalidArgumentError('message')); +expectType(new commander.InvalidOptionArgumentError('message')); expectType(commander.createCommand()); // Command properties @@ -40,6 +41,9 @@ expectType(program.addCommand(new commander.Command('abc'))); // argument expectType(program.argument('')); expectType(program.argument('', 'description')); +expectType(program.argument('[value]', 'description', 'default')); +expectType(program.argument('[value]', 'description', parseFloat)); +expectType(program.argument('[value]', 'description', parseFloat, 1.23)); // arguments expectType(program.arguments(' [env]'));