commander
Advanced tools
Comparing version 5.1.0 to 6.0.0-0
@@ -10,2 +10,10 @@ # Changelog | ||
## [6.0.0-0] (2020-06-20) | ||
### Added | ||
- add support for variadic options ([#1250]) | ||
- allow options to be added with just a short flag ([#1256]) | ||
- throw an error if there might be a clash between option name and a Command property, with advice on how to resolve ([#1275]) | ||
## [5.1.0] (2020-04-25) | ||
@@ -368,2 +376,5 @@ | ||
[#1248]: https://github.com/tj/commander.js/pull/1248 | ||
[#1250]: https://github.com/tj/commander.js/pull/1250 | ||
[#1256]: https://github.com/tj/commander.js/pull/1256 | ||
[#1275]: https://github.com/tj/commander.js/pull/1275 | ||
@@ -370,0 +381,0 @@ [Unreleased]: https://github.com/tj/commander.js/compare/master...develop |
188
index.js
@@ -23,9 +23,14 @@ /** | ||
this.flags = flags; | ||
this.required = flags.indexOf('<') >= 0; // A value must be supplied when the option is specified. | ||
this.optional = flags.indexOf('[') >= 0; // A value is optional when the option is specified. | ||
this.required = flags.includes('<'); // A value must be supplied when the option is specified. | ||
this.optional = flags.includes('['); // A value is optional when the option is specified. | ||
// variadic test ignores <value,...> et al which might be used to describe custom splitting of single argument | ||
this.variadic = /\w\.\.\.[>\]]$/.test(flags); // The option can take multiple values. | ||
this.mandatory = false; // The option must have a value after parsing, which usually means it must be specified on command line. | ||
this.negate = flags.indexOf('-no-') !== -1; | ||
const flagParts = flags.split(/[ ,|]+/); | ||
if (flagParts.length > 1 && !/^[[<]/.test(flagParts[1])) this.short = flagParts.shift(); | ||
this.long = flagParts.shift(); | ||
const optionFlags = _parseOptionFlags(flags); | ||
this.short = optionFlags.shortFlag; | ||
this.long = optionFlags.longFlag; | ||
this.negate = false; | ||
if (this.long) { | ||
this.negate = this.long.startsWith('--no-'); | ||
} | ||
this.description = description || ''; | ||
@@ -43,3 +48,6 @@ this.defaultValue = undefined; | ||
name() { | ||
return this.long.replace(/^--/, ''); | ||
if (this.long) { | ||
return this.long.replace(/^--/, ''); | ||
} | ||
return this.short.replace(/^-/, ''); | ||
}; | ||
@@ -115,2 +123,3 @@ | ||
this._storeOptionsAsProperties = true; // backwards compatible by default | ||
this._storeOptionsAsPropertiesCalled = false; | ||
this._passCommandToAction = true; // backwards compatible by default | ||
@@ -266,3 +275,3 @@ this._actionResults = []; | ||
* addHelpCommand(false); // force off | ||
* addHelpCommand('help [cmd]', 'display help for [cmd]'); // force on with custom detais | ||
* addHelpCommand('help [cmd]', 'display help for [cmd]'); // force on with custom details | ||
* | ||
@@ -430,2 +439,43 @@ * @return {Command} `this` command for chaining | ||
/** | ||
* 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`); | ||
} | ||
}; | ||
/** | ||
* Internal implementation shared by .option() and .requiredOption() | ||
@@ -436,3 +486,3 @@ * | ||
* @param {string} description | ||
* @param {Function|*} [fn] - custom option processing function or default vaue | ||
* @param {Function|*} [fn] - custom option processing function or default value | ||
* @param {*} [defaultValue] | ||
@@ -449,2 +499,4 @@ * @return {Command} `this` command for chaining | ||
this._checkForOptionNameClash(option); | ||
// default as 3rd arg | ||
@@ -486,9 +538,17 @@ if (typeof fn !== 'function') { | ||
this.on('option:' + oname, (val) => { | ||
// coercion | ||
const oldValue = this._getOptionValue(name); | ||
// custom processing | ||
if (val !== null && fn) { | ||
val = fn(val, this._getOptionValue(name) === undefined ? defaultValue : this._getOptionValue(name)); | ||
val = fn(val, oldValue === undefined ? defaultValue : oldValue); | ||
} else if (val !== null && option.variadic) { | ||
if (oldValue === defaultValue || !Array.isArray(oldValue)) { | ||
val = [val]; | ||
} else { | ||
val = oldValue.concat(val); | ||
} | ||
} | ||
// unassigned or boolean value | ||
if (typeof this._getOptionValue(name) === 'boolean' || typeof this._getOptionValue(name) === 'undefined') { | ||
if (typeof oldValue === 'boolean' || typeof oldValue === 'undefined') { | ||
// if no value, negate false, and we have a default, then use it! | ||
@@ -557,3 +617,3 @@ if (val == null) { | ||
* @param {string} description | ||
* @param {Function|*} [fn] - custom option processing function or default vaue | ||
* @param {Function|*} [fn] - custom option processing function or default value | ||
* @param {*} [defaultValue] | ||
@@ -568,3 +628,3 @@ * @return {Command} `this` command for chaining | ||
/* | ||
/** | ||
* Add a required option which must have a value after parsing. This usually means | ||
@@ -577,3 +637,3 @@ * the option must be specified on the command line. (Otherwise the same as .option().) | ||
* @param {string} description | ||
* @param {Function|*} [fn] - custom option processing function or default vaue | ||
* @param {Function|*} [fn] - custom option processing function or default value | ||
* @param {*} [defaultValue] | ||
@@ -610,2 +670,3 @@ * @return {Command} `this` command for chaining | ||
storeOptionsAsProperties(value) { | ||
this._storeOptionsAsPropertiesCalled = true; | ||
this._storeOptionsAsProperties = (value === undefined) || value; | ||
@@ -906,3 +967,3 @@ if (this.options.length) { | ||
if (this.commands.length && this.args.length === 0 && !this._actionHandler && !this._defaultCommandName) { | ||
// probaby missing subcommand and no handler, user needs help | ||
// probably missing subcommand and no handler, user needs help | ||
this._helpAndError(); | ||
@@ -1014,2 +1075,3 @@ } | ||
// parse options | ||
let activeVariadicOption = null; | ||
while (args.length) { | ||
@@ -1025,2 +1087,8 @@ const arg = args.shift(); | ||
if (activeVariadicOption && !maybeOption(arg)) { | ||
this.emit(`option:${activeVariadicOption.name()}`, arg); | ||
continue; | ||
} | ||
activeVariadicOption = null; | ||
if (maybeOption(arg)) { | ||
@@ -1044,2 +1112,3 @@ const option = this._findOption(arg); | ||
} | ||
activeVariadicOption = option.variadic ? option : null; | ||
continue; | ||
@@ -1206,5 +1275,5 @@ } | ||
const versionOption = new Option(flags, description); | ||
this._versionOptionName = versionOption.long.substr(2) || 'version'; | ||
this._versionOptionName = versionOption.attributeName(); | ||
this.options.push(versionOption); | ||
this.on('option:' + this._versionOptionName, () => { | ||
this.on('option:' + versionOption.name(), () => { | ||
process.stdout.write(str + '\n'); | ||
@@ -1572,9 +1641,6 @@ this._exit(0, 'commander.version', str); | ||
const splitFlags = this._helpFlags.split(/[ ,|]+/); | ||
const helpFlags = _parseOptionFlags(this._helpFlags); | ||
this._helpShortFlag = helpFlags.shortFlag; | ||
this._helpLongFlag = helpFlags.longFlag; | ||
this._helpShortFlag = undefined; | ||
if (splitFlags.length > 1) this._helpShortFlag = splitFlags.shift(); | ||
this._helpLongFlag = splitFlags.shift(); | ||
return this; | ||
@@ -1730,2 +1796,24 @@ }; | ||
/** | ||
* Parse the short and long flag out of something like '-m,--mixed <value>' | ||
* | ||
* @api private | ||
*/ | ||
function _parseOptionFlags(flags) { | ||
let shortFlag; | ||
let longFlag; | ||
// Use original very loose parsing to maintain backwards compatibility for now, | ||
// which allowed for example unintended `-sw, --short-word` [sic]. | ||
const flagParts = flags.split(/[ |,]+/); | ||
if (flagParts.length > 1 && !/^[[<]/.test(flagParts[1])) shortFlag = flagParts.shift(); | ||
longFlag = flagParts.shift(); | ||
// Add support for lone short flag without significantly changing parsing! | ||
if (!shortFlag && /^-[^-]$/.test(longFlag)) { | ||
shortFlag = longFlag; | ||
longFlag = undefined; | ||
} | ||
return { shortFlag, longFlag }; | ||
} | ||
/** | ||
* Scan arguments and increment port number for inspect calls (to avoid conflicts when spawning new command). | ||
@@ -1744,33 +1832,33 @@ * | ||
return args.map((arg) => { | ||
let result = arg; | ||
if (arg.indexOf('--inspect') === 0) { | ||
let debugOption; | ||
let debugHost = '127.0.0.1'; | ||
let debugPort = '9229'; | ||
let match; | ||
if ((match = arg.match(/^(--inspect(-brk)?)$/)) !== null) { | ||
// e.g. --inspect | ||
debugOption = match[1]; | ||
} else if ((match = arg.match(/^(--inspect(-brk|-port)?)=([^:]+)$/)) !== null) { | ||
debugOption = match[1]; | ||
if (/^\d+$/.test(match[3])) { | ||
// e.g. --inspect=1234 | ||
debugPort = match[3]; | ||
} else { | ||
// e.g. --inspect=localhost | ||
debugHost = match[3]; | ||
} | ||
} else if ((match = arg.match(/^(--inspect(-brk|-port)?)=([^:]+):(\d+)$/)) !== null) { | ||
// e.g. --inspect=localhost:1234 | ||
debugOption = match[1]; | ||
if (!arg.startsWith('--inspect')) { | ||
return arg; | ||
} | ||
let debugOption; | ||
let debugHost = '127.0.0.1'; | ||
let debugPort = '9229'; | ||
let match; | ||
if ((match = arg.match(/^(--inspect(-brk)?)$/)) !== null) { | ||
// e.g. --inspect | ||
debugOption = match[1]; | ||
} else if ((match = arg.match(/^(--inspect(-brk|-port)?)=([^:]+)$/)) !== null) { | ||
debugOption = match[1]; | ||
if (/^\d+$/.test(match[3])) { | ||
// e.g. --inspect=1234 | ||
debugPort = match[3]; | ||
} else { | ||
// e.g. --inspect=localhost | ||
debugHost = match[3]; | ||
debugPort = match[4]; | ||
} | ||
} else if ((match = arg.match(/^(--inspect(-brk|-port)?)=([^:]+):(\d+)$/)) !== null) { | ||
// e.g. --inspect=localhost:1234 | ||
debugOption = match[1]; | ||
debugHost = match[3]; | ||
debugPort = match[4]; | ||
} | ||
if (debugOption && debugPort !== '0') { | ||
result = `${debugOption}=${debugHost}:${parseInt(debugPort) + 1}`; | ||
} | ||
if (debugOption && debugPort !== '0') { | ||
return `${debugOption}=${debugHost}:${parseInt(debugPort) + 1}`; | ||
} | ||
return result; | ||
return arg; | ||
}); | ||
} |
{ | ||
"name": "commander", | ||
"version": "5.1.0", | ||
"version": "6.0.0-0", | ||
"description": "the complete solution for node.js command-line programs", | ||
@@ -35,8 +35,8 @@ "keywords": [ | ||
"@types/jest": "^25.2.1", | ||
"@types/node": "^12.12.36", | ||
"@typescript-eslint/eslint-plugin": "^2.29.0", | ||
"@types/node": "^12.12.38", | ||
"@typescript-eslint/eslint-plugin": "^2.31.0", | ||
"eslint": "^6.8.0", | ||
"eslint-config-standard-with-typescript": "^15.0.1", | ||
"eslint-plugin-jest": "^23.8.2", | ||
"jest": "^25.4.0", | ||
"eslint-config-standard-with-typescript": "^16.0.0", | ||
"eslint-plugin-jest": "^23.10.0", | ||
"jest": "^26.0.1", | ||
"standard": "^14.3.3", | ||
@@ -43,0 +43,0 @@ "typescript": "^3.7.5" |
104
Readme.md
@@ -21,2 +21,3 @@ # Commander.js | ||
- [Required option](#required-option) | ||
- [Variadic option](#variadic-option) | ||
- [Version option](#version-option) | ||
@@ -45,3 +46,2 @@ - [Commands](#commands) | ||
- [Examples](#examples) | ||
- [License](#license) | ||
- [Support](#support) | ||
@@ -68,7 +68,7 @@ - [Commander for enterprise](#commander-for-enterprise) | ||
```js | ||
const { Command } = require('commander'); | ||
const program = new Command(); | ||
program.version('0.0.1'); | ||
``` | ||
```js | ||
const { Command } = require('commander'); | ||
const program = new Command(); | ||
program.version('0.0.1'); | ||
``` | ||
@@ -94,5 +94,5 @@ ## Options | ||
Example file: [options-common.js](./examples/options-common.js) | ||
```js | ||
const { program } = require('commander'); | ||
program | ||
@@ -133,5 +133,5 @@ .option('-d, --debug', 'output extra debugging') | ||
Example file: [options-defaults.js](./examples/options-defaults.js) | ||
```js | ||
const { program } = require('commander'); | ||
program | ||
@@ -160,5 +160,5 @@ .option('-c, --cheese <type>', 'add the specified type of cheese', 'blue'); | ||
Example file: [options-negatable.js](./examples/options-negatable.js) | ||
```js | ||
const { program } = require('commander'); | ||
program | ||
@@ -188,5 +188,5 @@ .option('--no-sauce', 'Remove sauce') | ||
Example file: [options-flag-or-value.js](./examples/options-flag-or-value.js) | ||
```js | ||
const { program } = require('commander'); | ||
program | ||
@@ -220,5 +220,5 @@ .option('-c, --cheese [type]', 'Add cheese with optional type'); | ||
Example file: [options-custom-processing.js](./examples/options-custom-processing.js) | ||
```js | ||
const { program } = require('commander'); | ||
function myParseInt(value, dummyPrevious) { | ||
@@ -275,5 +275,5 @@ // parseInt takes a string and an optional radix | ||
Example file: [options-required.js](./examples/options-required.js) | ||
```js | ||
const { program } = require('commander'); | ||
program | ||
@@ -290,2 +290,34 @@ .requiredOption('-c, --cheese <type>', 'pizza must have cheese'); | ||
### Variadic option | ||
You may make an option variadic by appending `...` to the value placeholder when declaring the option. On the command line you | ||
can then specify multiple option arguments, and the parsed option value will be an array. The extra arguments | ||
are read until the first argument starting with a dash. The special argument `--` stops option processing entirely. If a value | ||
is specified in the same argument as the option then no further values are read. | ||
Example file: [options-variadic.js](./examples/options-variadic.js) | ||
```js | ||
program | ||
.option('-n, --number <numbers...>', 'specify numbers') | ||
.option('-l, --letter [letters...]', 'specify letters'); | ||
program.parse(); | ||
console.log('Options: ', program.opts()); | ||
console.log('Remaining arguments: ', program.args); | ||
``` | ||
```bash | ||
$ collect -n 1 2 3 --letter a b c | ||
Options: { number: [ '1', '2', '3' ], letter: [ 'a', 'b', 'c' ] } | ||
Remaining arguments: [] | ||
$ collect --letter=A -n80 operand | ||
Options: { number: [ '80' ], letter: [ 'A' ] } | ||
Remaining arguments: [ 'operand' ] | ||
$ collect --letter -n 1 -n 2 3 -- operand | ||
Options: { number: [ '1', '2', '3' ], letter: true } | ||
Remaining arguments: [ 'operand' ] | ||
``` | ||
### Version option | ||
@@ -305,3 +337,3 @@ | ||
You may change the flags and description by passing additional parameters to the `version` method, using | ||
the same syntax for flags as the `option` method. The version flags can be named anything, but a long name is required. | ||
the same syntax for flags as the `option` method. | ||
@@ -350,5 +382,5 @@ ```js | ||
Example file: [env](./examples/env) | ||
```js | ||
const { program } = require('commander'); | ||
program | ||
@@ -437,6 +469,5 @@ .version('0.1.0') | ||
Example file: [pm](./examples/pm) | ||
```js | ||
// file: ./examples/pm | ||
const { program } = require('commander'); | ||
program | ||
@@ -446,5 +477,6 @@ .version('0.1.0') | ||
.command('search [query]', 'search with optional query') | ||
.command('update', 'update installed packages', {executableFile: 'myUpdateSubCommand'}) | ||
.command('list', 'list packages installed', {isDefault: true}) | ||
.parse(process.argv); | ||
.command('update', 'update installed packages', { executableFile: 'myUpdateSubCommand' }) | ||
.command('list', 'list packages installed', { isDefault: true }); | ||
program.parse(process.argv); | ||
``` | ||
@@ -457,4 +489,6 @@ | ||
The help information is auto-generated based on the information commander already knows about your program. The default | ||
help option is `-h,--help`. ([example](./examples/pizza)) | ||
help option is `-h,--help`. | ||
Example file: [pizza](./examples/pizza) | ||
```bash | ||
@@ -487,4 +521,6 @@ $ node ./examples/pizza --help | ||
You can display extra information by listening for "--help". ([example](./examples/custom-help)) | ||
You can display extra information by listening for "--help". | ||
Example file: [custom-help](./examples/custom-help) | ||
```js | ||
@@ -616,3 +652,3 @@ program | ||
([example](./examples/storeOptionsAsProperties-action.js)) | ||
Example file: [storeOptionsAsProperties-action.js](./examples/storeOptionsAsProperties-action.js) | ||
@@ -688,3 +724,3 @@ ```js | ||
``` js | ||
```js | ||
program.exitOverride(); | ||
@@ -701,2 +737,4 @@ | ||
Example file: [deploy](./examples/deploy) | ||
```js | ||
@@ -741,6 +779,2 @@ const { program } = require('commander'); | ||
## License | ||
[MIT](https://github.com/tj/commander.js/blob/master/LICENSE) | ||
## Support | ||
@@ -747,0 +781,0 @@ |
@@ -78,3 +78,3 @@ // Type definitions for commander | ||
* .command('start <service>', 'start named service') | ||
* .command('stop [service]', 'stop named serice, or all if no name supplied'); | ||
* .command('stop [service]', 'stop named service, or all if no name supplied'); | ||
* ``` | ||
@@ -81,0 +81,0 @@ * |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
111420
1980
772
2