Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: tj/commander.js
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v8.2.0
Choose a base ref
...
head repository: tj/commander.js
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: v8.3.0
Choose a head ref
  • 7 commits
  • 17 files changed
  • 2 contributors

Commits on Sep 15, 2021

  1. Copy the full SHA
    c1472bc View commit details

Commits on Sep 25, 2021

  1. Copy the full SHA
    a546970 View commit details

Commits on Sep 27, 2021

  1. Add setOptionValueWithSource and getOptionValueSource (#1613)

    * Make setOptionValueWithSource public and add matching getOptionValueSource
    
    * Add mention in README
    shadowspawn authored Sep 27, 2021
    Copy the full SHA
    6e00f44 View commit details
  2. Copy the full SHA
    36c2f68 View commit details

Commits on Oct 20, 2021

  1. ci: update 'node-version'

    https://github.com/nodejs/Release
    
    - Drop 15.x
    - Add 17.x
    abetomo committed Oct 20, 2021
    Copy the full SHA
    0847a7f View commit details

Commits on Oct 22, 2021

  1. Prepare for 8.3.0

    shadowspawn authored and abetomo committed Oct 22, 2021
    Copy the full SHA
    34366fd View commit details
  2. Fix date

    shadowspawn authored and abetomo committed Oct 22, 2021
    Copy the full SHA
    43f4743 View commit details
2 changes: 1 addition & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
@@ -9,7 +9,7 @@ jobs:
strategy:
fail-fast: false
matrix:
node-version: [12.x, 14.x, 15.x, 16.x]
node-version: [12.x, 14.x, 16.x, 17.x]
os: [ubuntu-latest, windows-latest, macos-latest]

steps:
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -8,6 +8,18 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
<!-- markdownlint-disable MD024 -->
<!-- markdownlint-disable MD004 -->

## [8.3.0] (2021-10-22)

### Added

- `.getOptionValueSource()` and `.setOptionValueWithSource()`, where expected values for source are one of 'default', 'env', 'config', 'cli' ([#1613])

### Deprecated

- `.command('*')`, use default command instead ([#1612])
- `on('command:*')`, use `.showSuggestionAfterError()` instead ([#1612])


## [8.2.0] (2021-09-10)

### Added
@@ -397,8 +409,11 @@ program
[#1570]: https://github.com/tj/commander.js/pull/1570
[#1587]: https://github.com/tj/commander.js/pull/1587
[#1590]: https://github.com/tj/commander.js/pull/1590
[#1612]: https://github.com/tj/commander.js/pull/1612
[#1613]: https://github.com/tj/commander.js/pull/1613

[Unreleased]: https://github.com/tj/commander.js/compare/master...develop
[8.3.0]: https://github.com/tj/commander.js/compare/v8.2.0...v8.3.0
[8.2.0]: https://github.com/tj/commander.js/compare/v8.1.0...v8.2.0
[8.1.0]: https://github.com/tj/commander.js/compare/v8.0.0...v8.1.0
[8.0.0]: https://github.com/tj/commander.js/compare/v7.2.0...v8.0.0
10 changes: 2 additions & 8 deletions Readme.md
Original file line number Diff line number Diff line change
@@ -98,7 +98,8 @@ const program = new Command();
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, and are passed to the action handler.
You can also use `.getOptionValue()` and `.setOptionValue()` to work with a single option value.
(You can also use `.getOptionValue()` and `.setOptionValue()` to work with a single option value,
and `.getOptionValueSource()` and `.setOptionValueWithSource()` when it matters where the option value came from.)

Multi-word options such as "--template-engine" are camel-cased, becoming `program.opts().templateEngine` etc.

@@ -778,13 +779,6 @@ You can execute custom actions by listening to command and option events.
program.on('option:verbose', function () {
process.env.VERBOSE = this.opts().verbose;
});
program.on('command:*', function (operands) {
console.error(`error: unknown command '${operands[0]}'`);
const availableCommands = program.commands.map(cmd => cmd.name());
mySuggestBestMatch(operands[0], availableCommands);
process.exitCode = 1;
});
```
## Bits and pieces
7 changes: 0 additions & 7 deletions Readme_zh-CN.md
Original file line number Diff line number Diff line change
@@ -731,13 +731,6 @@ program.configureHelp({
program.on('option:verbose', function () {
process.env.VERBOSE = this.opts().verbose;
});
program.on('command:*', function (operands) {
console.error(`error: unknown command '${operands[0]}'`);
const availableCommands = program.commands.map(cmd => cmd.name());
mySuggestBestMatch(operands[0], availableCommands);
process.exitCode = 1;
});
```
## 零碎知识
49 changes: 45 additions & 4 deletions docs/deprecated.md
Original file line number Diff line number Diff line change
@@ -3,6 +3,17 @@
These features are deprecated, which means they may go away in a future major version of Commander.
They are currently still available for backwards compatibility, but should not be used in new code.

- [Deprecated](#deprecated)
- [RegExp .option() parameter](#regexp-option-parameter)
- [noHelp](#nohelp)
- [Default import of global Command object](#default-import-of-global-command-object)
- [Callback to .help() and .outputHelp()](#callback-to-help-and-outputhelp)
- [.on('--help')](#on--help)
- [.on('command:*')](#oncommand)
- [.command('*')](#command)
- [cmd.description(cmdDescription, argDescriptions)](#cmddescriptioncmddescription-argdescriptions)
- [InvalidOptionArgumentError](#invalidoptionargumenterror)

## RegExp .option() parameter

The `.option()` method allowed a RegExp as the third parameter to restrict what values were accepted.
@@ -23,7 +34,7 @@ This was an option passed to `.command()` to hide the command from the built-in
program.command('example', 'examnple command', { noHelp: true });
```

The option was renamed `hidden` in Commander v5.1. Deprecated from Commander v7.
The option was renamed `hidden` in Commander v5.1. Deprecated from Commander v7.

## Default import of global Command object

@@ -43,7 +54,7 @@ const program = new Command()
```

- Removed from README in Commander v5.
- Deprecated from Commander v7.
- Deprecated from Commander v7.
- Removed from TypeScript declarations in Commander v8.

## Callback to .help() and .outputHelp()
@@ -87,7 +98,38 @@ Examples:
);
```

Deprecated from Commander v7.
Deprecated from Commander v7.

## .on('command:*')

This was emitted when the command argument did not match a known subcommand (as part of the implementation of `.command('*')`).

One use was for adding an error for an unknown subcommand. An error is now the default built-in behaviour.

A second related use was for making a suggestion for an unknown subcommand. The replacement built-in support is `.showSuggestionAfterError()`,
or for custom behaviour catch the `commander.unknownCommand` error.

Deprecated from Commander v8.3.

## .command('*')

This was used to add a default command to the program.

```js
program
.command('*')
.action(() => console.log('List files by default...'));
```

You may now pass a configuration option of `isDefault: true` when adding a command, whether using a subcommand with an action handler or a stand-alone executable subcommand.

```js
program
.command('list', { isDefault: true })
.action(() => console.log('List files by default...'));
```

Removed from README in Commander v5. Deprecated from Commander v8.3.

## cmd.description(cmdDescription, argDescriptions)

@@ -110,7 +152,6 @@ program
.argument('<book>', 'ISBN number for book');
```


Deprecated from Commander v8.

## InvalidOptionArgumentError
6 changes: 5 additions & 1 deletion examples/custom-help-description
Original file line number Diff line number Diff line change
@@ -9,7 +9,7 @@ const program = new Command();
program
.helpOption('-c, --HELP', 'custom help message')
.option('-s, --sessions', 'add session support')
.option('-t, --template <engine>', 'specify template engine (jade|ejs) [jade]', 'jade');
.option('-t, --template <engine>', 'specify template engine', 'jade');

program
.command('child')
@@ -19,3 +19,7 @@ program
});

program.parse(process.argv);

// Try the following:
// node custom-help-description -c
// node custom-help-description child --HELP
6 changes: 5 additions & 1 deletion examples/custom-version
Original file line number Diff line number Diff line change
@@ -9,6 +9,10 @@ const program = new Command();
program
.version('0.0.1', '-v, --VERSION', 'new version message')
.option('-s, --sessions', 'add session support')
.option('-t, --template <engine>', 'specify template engine (jade|ejs) [jade]', 'jade');
.option('-t, --template <engine>', 'specify template engine', 'jade');

program.parse(process.argv);

// Try the following:
// node custom-version -v
// node custom-version --VERSION
23 changes: 0 additions & 23 deletions examples/defaults

This file was deleted.

2 changes: 1 addition & 1 deletion examples/pm-install
Original file line number Diff line number Diff line change
@@ -17,7 +17,7 @@ if (!pkgs.length) {
}

console.log();
if (program.force) console.log(' force: install');
if (program.opts().force) console.log(' force: install');
pkgs.forEach(function(pkg) {
console.log(' install : %s', pkg);
});
46 changes: 33 additions & 13 deletions lib/command.js
Original file line number Diff line number Diff line change
@@ -36,7 +36,7 @@ class Command extends EventEmitter {
this._scriptPath = null;
this._name = name || '';
this._optionValues = {};
this._optionValueSources = {}; // default < env < cli
this._optionValueSources = {}; // default < config < env < cli
this._storeOptionsAsProperties = false;
this._actionHandler = null;
this._executableHandler = false;
@@ -463,10 +463,10 @@ Expecting one of '${allowedValues.join("', '")}'`);
*
* @example
* program
* .command('help')
* .description('display verbose help')
* .command('serve')
* .description('start service')
* .action(function() {
* // output help here
* // do work here
* });
*
* @param {Function} fn
@@ -527,7 +527,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
}
// preassign only if we have a default
if (defaultValue !== undefined) {
this._setOptionValueWithSource(name, defaultValue, 'default');
this.setOptionValueWithSource(name, defaultValue, 'default');
}
}

@@ -558,13 +558,13 @@ Expecting one of '${allowedValues.join("', '")}'`);
if (typeof oldValue === 'boolean' || typeof oldValue === 'undefined') {
// if no value, negate false, and we have a default, then use it!
if (val == null) {
this._setOptionValueWithSource(name, option.negate ? false : defaultValue || true, valueSource);
this.setOptionValueWithSource(name, option.negate ? false : defaultValue || true, valueSource);
} else {
this._setOptionValueWithSource(name, val, valueSource);
this.setOptionValueWithSource(name, val, valueSource);
}
} else if (val !== null) {
// reassign
this._setOptionValueWithSource(name, option.negate ? false : val, valueSource);
this.setOptionValueWithSource(name, option.negate ? false : val, valueSource);
}
};

@@ -793,13 +793,32 @@ Expecting one of '${allowedValues.join("', '")}'`);
};

/**
* @api private
*/
_setOptionValueWithSource(key, value, source) {
* Store option value and where the value came from.
*
* @param {string} key
* @param {Object} value
* @param {string} source - expected values are default/config/env/cli
* @return {Command} `this` command for chaining
*/

setOptionValueWithSource(key, value, source) {
this.setOptionValue(key, value);
this._optionValueSources[key] = source;
return this;
}

/**
* Get source of option value.
* Expected values are default | config | env | cli
*
* @param {string} key
* @return {string}
*/

getOptionValueSource(key) {
return this._optionValueSources[key];
};

/**
* Get user arguments implied or explicit arguments.
* Side-effects: set _scriptPath if args included application, and use that to set implicit command name.
@@ -1112,6 +1131,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
* @param {Promise|undefined} promise
* @param {Function} fn
* @return {Promise|undefined}
* @api private
*/

_chainOrCall(promise, fn) {
@@ -1456,8 +1476,8 @@ Expecting one of '${allowedValues.join("', '")}'`);
this.options.forEach((option) => {
if (option.envVar && option.envVar in process.env) {
const optionKey = option.attributeName();
// env is second lowest priority source, above default
if (this.getOptionValue(optionKey) === undefined || this._optionValueSources[optionKey] === 'default') {
// Priority check. Do not overwrite cli or options from unknown source (client-code).
if (this.getOptionValue(optionKey) === undefined || ['default', 'config', 'env'].includes(this.getOptionValueSource(optionKey))) {
if (option.required || option.optional) { // option can take a value
// keep very simple, optional always takes value
this.emit(`optionEnv:${option.name()}`, process.env[option.envVar]);
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "commander",
"version": "8.2.0",
"version": "8.3.0",
"description": "the complete solution for node.js command-line programs",
"keywords": [
"commander",
8 changes: 7 additions & 1 deletion tests/command.chain.test.js
Original file line number Diff line number Diff line change
@@ -174,7 +174,13 @@ describe('Command methods that should return this for chaining', () => {

test('when call .setOptionValue() then returns this', () => {
const program = new Command();
const result = program.setOptionValue();
const result = program.setOptionValue('foo', 'bar');
expect(result).toBe(program);
});

test('when call .setOptionValueWithSource() then returns this', () => {
const program = new Command();
const result = program.setOptionValueWithSource('foo', 'bar', 'cli');
expect(result).toBe(program);
});

20 changes: 20 additions & 0 deletions tests/options.env.test.js
Original file line number Diff line number Diff line change
@@ -27,6 +27,26 @@ describe.each(['-f, --foo <required-arg>', '-f, --foo [optional-arg]'])('option
delete process.env.BAR;
});

test('when env defined and value source is config then option from env', () => {
const program = new commander.Command();
process.env.BAR = 'env';
program.addOption(new commander.Option(fooFlags).env('BAR'));
program.setOptionValueWithSource('foo', 'config', 'config');
program.parse([], { from: 'user' });
expect(program.opts().foo).toBe('env');
delete process.env.BAR;
});

test('when env defined and value source is unspecified then option unchanged', () => {
const program = new commander.Command();
process.env.BAR = 'env';
program.addOption(new commander.Option(fooFlags).env('BAR'));
program.setOptionValue('foo', 'client');
program.parse([], { from: 'user' });
expect(program.opts().foo).toBe('client');
delete process.env.BAR;
});

test('when default and env undefined and no cli then option from default', () => {
const program = new commander.Command();
program.addOption(new commander.Option(fooFlags).env('BAR').default('default'));
Loading