diff --git a/Readme.md b/Readme.md index 4cc640b94..6e84460ce 100644 --- a/Readme.md +++ b/Readme.md @@ -93,7 +93,6 @@ import { Command } from 'commander'; const program = new Command(); ``` - ## Options 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 ('|'). @@ -308,13 +307,14 @@ program.version('0.0.1', '-v, --vers', 'output the current version'); You can add most options using the `.option()` method, but there are some additional features available by constructing an `Option` explicitly for less common cases. -Example file: [options-extra.js](./examples/options-extra.js) +Example files: [options-extra.js](./examples/options-extra.js), [options-env.js](./examples/options-env.js) ```js program .addOption(new Option('-s, --secret').hideHelp()) .addOption(new Option('-t, --timeout ', 'timeout in seconds').default(60, 'one minute')) - .addOption(new Option('-d, --drink ', 'drink size').choices(['small', 'medium', 'large'])); + .addOption(new Option('-d, --drink ', 'drink size').choices(['small', 'medium', 'large'])) + .addOption(new Option('-p, --port ', 'port number').env('PORT')); ``` ```bash @@ -324,10 +324,14 @@ Usage: help [options] Options: -t, --timeout timeout in seconds (default: one minute) -d, --drink drink cup size (choices: "small", "medium", "large") + -p, --port port number (env: PORT) -h, --help display help for command $ extra --drink huge error: option '-d, --drink ' argument 'huge' is invalid. Allowed choices are small, medium, large. + +$ PORT=80 extra +Options: { timeout: 60, port: '80' } ``` ### Custom option processing @@ -903,7 +907,6 @@ You can modify this behaviour for custom applications. In addition, you can modi Example file: [configure-output.js](./examples/configure-output.js) - ```js function errorColor(str) { // Add ANSI escape codes to display text in red. diff --git a/examples/options-env.js b/examples/options-env.js new file mode 100644 index 000000000..8889f666e --- /dev/null +++ b/examples/options-env.js @@ -0,0 +1,38 @@ +#!/usr/bin/env node +// const { Command, Option } = require('commander'); // (normal include) +const { Command, Option } = require('../'); // include commander in git clone of commander repo +const program = new Command(); + +program.addOption(new Option('-p, --port ', 'specify port number') + .default(80) + .env('PORT') +); +program.addOption(new Option('-c, --colour', 'turn on colour output') + .env('COLOUR') +); +program.addOption(new Option('-C, --no-colour', 'turn off colour output') + .env('NO_COLOUR') +); +program.addOption(new Option('-s, --size ', 'specify size of drink') + .choices(['small', 'medium', 'large']) + .env('SIZE') +); + +program.parse(); +console.log(program.opts()); + +// Try the following: +// node options-env.js --help +// +// node options-env.js +// PORT=9001 node options-env.js +// PORT=9001 node options-env.js --port 123 +// +// COLOUR= node options-env.js +// COLOUR= node options-env.js --no-colour +// NO_COLOUR= node options-env.js +// NO_COLOUR= node options-env.js --colour +// +// SIZE=small node options-env.js +// SIZE=enormous node options-env.js +// SIZE=enormous node options-env.js --size=large diff --git a/examples/options-extra.js b/examples/options-extra.js index 404b3e86a..41c26fead 100644 --- a/examples/options-extra.js +++ b/examples/options-extra.js @@ -1,20 +1,23 @@ #!/usr/bin/env node // This is used as an example in the README for extra option features. +// See also options-env.js for more extensive env examples. -// const commander = require('commander'); // (normal include) -const commander = require('../'); // include commander in git clone of commander repo -const program = new commander.Command(); +// const { Command, Option } = require('commander'); // (normal include) +const { Command, Option } = require('../'); // include commander in git clone of commander repo +const program = new Command(); program - .addOption(new commander.Option('-s, --secret').hideHelp()) - .addOption(new commander.Option('-t, --timeout ', 'timeout in seconds').default(60, 'one minute')) - .addOption(new commander.Option('-d, --drink ', 'drink cup size').choices(['small', 'medium', 'large'])); + .addOption(new Option('-s, --secret').hideHelp()) + .addOption(new Option('-t, --timeout ', 'timeout in seconds').default(60, 'one minute')) + .addOption(new Option('-d, --drink ', 'drink cup size').choices(['small', 'medium', 'large'])) + .addOption(new Option('-p, --port ', 'port number').env('PORT')); program.parse(); console.log('Options: ', program.opts()); // Try the following: -// node options-extra.js --help -// node options-extra.js --drink huge +// node options-extra.js --help +// node options-extra.js --drink huge +// PORT=80 node options-extra.js diff --git a/lib/command.js b/lib/command.js index 4d080b172..4b44f4568 100644 --- a/lib/command.js +++ b/lib/command.js @@ -35,6 +35,7 @@ class Command extends EventEmitter { this._scriptPath = null; this._name = name || ''; this._optionValues = {}; + this._optionValueSources = {}; // default < env < cli this._storeOptionsAsProperties = false; this._actionHandler = null; this._executableHandler = false; @@ -512,16 +513,16 @@ Expecting one of '${allowedValues.join("', '")}'`); } // preassign only if we have a default if (defaultValue !== undefined) { - this.setOptionValue(name, defaultValue); + this._setOptionValueWithSource(name, defaultValue, 'default'); } } // register the option this.options.push(option); - // when it's passed assign the value - // and conditionally invoke the callback - this.on('option:' + oname, (val) => { + // handler for cli and env supplied values + const handleOptionValue = (val, invalidValueMessage, valueSource) => { + // Note: using closure to access lots of lexical scoped variables. const oldValue = this.getOptionValue(name); // custom processing @@ -530,7 +531,7 @@ Expecting one of '${allowedValues.join("', '")}'`); val = option.parseArg(val, oldValue === undefined ? defaultValue : oldValue); } catch (err) { if (err.code === 'commander.invalidArgument') { - const message = `error: option '${option.flags}' argument '${val}' is invalid. ${err.message}`; + const message = `${invalidValueMessage} ${err.message}`; this._displayError(err.exitCode, err.code, message); } throw err; @@ -543,18 +544,28 @@ 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.setOptionValue(name, option.negate - ? false - : defaultValue || true); + this._setOptionValueWithSource(name, option.negate ? false : defaultValue || true, valueSource); } else { - this.setOptionValue(name, val); + this._setOptionValueWithSource(name, val, valueSource); } } else if (val !== null) { // reassign - this.setOptionValue(name, option.negate ? false : val); + this._setOptionValueWithSource(name, option.negate ? false : val, valueSource); } + }; + + this.on('option:' + oname, (val) => { + const invalidValueMessage = `error: option '${option.flags}' argument '${val}' is invalid.`; + handleOptionValue(val, invalidValueMessage, 'cli'); }); + if (option.envVar) { + this.on('optionEnv:' + oname, (val) => { + const invalidValueMessage = `error: option '${option.flags}' value '${val}' from env '${option.envVar}' is invalid.`; + handleOptionValue(val, invalidValueMessage, 'env'); + }); + } + return this; } @@ -767,6 +778,14 @@ Expecting one of '${allowedValues.join("', '")}'`); return this; }; + /** + * @api private + */ + _setOptionValueWithSource(key, value, source) { + this.setOptionValue(key, value); + this._optionValueSources[key] = source; + } + /** * Get user arguments implied or explicit arguments. * Side-effects: set _scriptPath if args included application, and use that to set implicit command name. @@ -1131,6 +1150,7 @@ Expecting one of '${allowedValues.join("', '")}'`); _parseCommand(operands, unknown) { const parsed = this.parseOptions(unknown); + this._parseOptionsEnv(); // after cli, so parseArg not called on both cli and env operands = operands.concat(parsed.operands); unknown = parsed.unknown; this.args = operands.concat(unknown); @@ -1411,6 +1431,30 @@ Expecting one of '${allowedValues.join("', '")}'`); this._exit(exitCode, code, message); } + /** + * Apply any option related environment variables, if option does + * not have a value from cli or client code. + * + * @api private + */ + _parseOptionsEnv() { + 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') { + 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]); + } else { // boolean + // keep very simple, only care that envVar defined and not the value + this.emit(`optionEnv:${option.name()}`); + } + } + } + }); + } + /** * Argument `name` is missing. * diff --git a/lib/help.js b/lib/help.js index 025bc0743..2187f2b97 100644 --- a/lib/help.js +++ b/lib/help.js @@ -234,21 +234,24 @@ class Help { */ optionDescription(option) { - if (option.negate) { - return option.description; - } const extraInfo = []; - if (option.argChoices) { + // Some of these do not make sense for negated boolean and suppress for backwards compatibility. + + if (option.argChoices && !option.negate) { extraInfo.push( // use stringify to match the display of the default value `choices: ${option.argChoices.map((choice) => JSON.stringify(choice)).join(', ')}`); } - if (option.defaultValue !== undefined) { + if (option.defaultValue !== undefined && !option.negate) { extraInfo.push(`default: ${option.defaultValueDescription || JSON.stringify(option.defaultValue)}`); } + if (option.envVar !== undefined) { + extraInfo.push(`env: ${option.envVar}`); + } if (extraInfo.length > 0) { return `${option.description} (${extraInfo.join(', ')})`; } + return option.description; }; diff --git a/lib/option.js b/lib/option.js index d8c3aa9c3..2ec204401 100644 --- a/lib/option.js +++ b/lib/option.js @@ -28,6 +28,7 @@ class Option { } this.defaultValue = undefined; this.defaultValueDescription = undefined; + this.envVar = undefined; this.parseArg = undefined; this.hidden = false; this.argChoices = undefined; @@ -47,6 +48,19 @@ class Option { return this; }; + /** + * Set environment variable to check for option value. + * Priority order of option values is default < env < cli + * + * @param {string} name + * @return {Option} + */ + + env(name) { + this.envVar = name; + return this; + }; + /** * Set the custom handler for processing CLI option arguments into option values. * diff --git a/tests/help.optionDescription.test.js b/tests/help.optionDescription.test.js index e849cf196..2653d1d99 100644 --- a/tests/help.optionDescription.test.js +++ b/tests/help.optionDescription.test.js @@ -24,6 +24,13 @@ describe('optionDescription', () => { expect(helper.optionDescription(option)).toEqual('description (default: "default")'); }); + test('when option has env then return description and env name', () => { + const description = 'description'; + const option = new commander.Option('-a', description).env('ENV'); + const helper = new commander.Help(); + expect(helper.optionDescription(option)).toEqual('description (env: ENV)'); + }); + test('when option has default value description then return description and custom default description', () => { const description = 'description'; const defaultValueDescription = 'custom'; diff --git a/tests/option.chain.test.js b/tests/option.chain.test.js index 700984d15..fd9d826ee 100644 --- a/tests/option.chain.test.js +++ b/tests/option.chain.test.js @@ -30,4 +30,10 @@ describe('Option methods that should return this for chaining', () => { const result = option.choices(['a']); expect(result).toBe(option); }); + + test('when call .env() then returns this', () => { + const option = new Option('-e,--example '); + const result = option.env('e'); + expect(result).toBe(option); + }); }); diff --git a/tests/options.env.test.js b/tests/options.env.test.js new file mode 100644 index 000000000..5dc53cd37 --- /dev/null +++ b/tests/options.env.test.js @@ -0,0 +1,308 @@ +const commander = require('../'); + +// treating optional same as required, treat as option taking value rather than as boolean +describe.each(['-f, --foo ', '-f, --foo [optional-arg]'])('option declared as: %s', (fooFlags) => { + test('when env undefined and no cli then option undefined', () => { + const program = new commander.Command(); + program.addOption(new commander.Option(fooFlags).env('BAR')); + program.parse([], { from: 'user' }); + expect(program.opts().foo).toBeUndefined(); + }); + + test('when env defined and no cli then option from env', () => { + const program = new commander.Command(); + process.env.BAR = 'env'; + program.addOption(new commander.Option(fooFlags).env('BAR')); + program.parse([], { from: 'user' }); + expect(program.opts().foo).toBe('env'); + delete process.env.BAR; + }); + + test('when env defined and cli then option from cli', () => { + const program = new commander.Command(); + process.env.BAR = 'env'; + program.addOption(new commander.Option(fooFlags).env('BAR')); + program.parse(['--foo', 'cli'], { from: 'user' }); + expect(program.opts().foo).toBe('cli'); + 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')); + program.parse([], { from: 'user' }); + expect(program.opts().foo).toBe('default'); + }); + + test('when default and env defined and no cli then option from env', () => { + const program = new commander.Command(); + process.env.BAR = 'env'; + program.addOption(new commander.Option(fooFlags).env('BAR').default('default')); + program.parse([], { from: 'user' }); + expect(program.opts().foo).toBe('env'); + delete process.env.BAR; + }); + + test('when default and env defined and cli then option from cli', () => { + const program = new commander.Command(); + process.env.BAR = 'env'; + program.addOption(new commander.Option(fooFlags).env('BAR').default('default')); + program.parse(['--foo', 'cli'], { from: 'user' }); + expect(program.opts().foo).toBe('cli'); + delete process.env.BAR; + }); +}); + +describe('boolean flag', () => { + test('when env undefined and no cli then option undefined', () => { + const program = new commander.Command(); + program.addOption(new commander.Option('-f, --foo').env('BAR')); + program.parse([], { from: 'user' }); + expect(program.opts().foo).toBeUndefined(); + }); + + test('when env defined with value and no cli then option true', () => { + const program = new commander.Command(); + process.env.BAR = 'env'; + program.addOption(new commander.Option('-f, --foo').env('BAR')); + program.parse([], { from: 'user' }); + expect(program.opts().foo).toBe(true); + delete process.env.BAR; + }); + + test('when env is "" and no cli then option true', () => { + // any string, including "" + const program = new commander.Command(); + process.env.BAR = ''; + program.addOption(new commander.Option('-f, --foo').env('BAR')); + program.parse([], { from: 'user' }); + expect(program.opts().foo).toBe(true); + delete process.env.BAR; + }); + + test('when env is "0" and no cli then option true', () => { + // any string, including "0" + const program = new commander.Command(); + process.env.BAR = '0'; + program.addOption(new commander.Option('-f, --foo').env('BAR')); + program.parse([], { from: 'user' }); + expect(program.opts().foo).toBe(true); + delete process.env.BAR; + }); + + test('when env is "false" and no cli then option true', () => { + // any string, including "false" + const program = new commander.Command(); + process.env.BAR = 'false'; + program.addOption(new commander.Option('-f, --foo').env('BAR')); + program.parse([], { from: 'user' }); + expect(program.opts().foo).toBe(true); + delete process.env.BAR; + }); +}); + +describe('boolean no-flag', () => { + test('when env undefined and no cli then option true', () => { + const program = new commander.Command(); + program.addOption(new commander.Option('-F, --no-foo').env('NO_BAR')); + program.parse([], { from: 'user' }); + expect(program.opts().foo).toBe(true); + }); + + test('when env defined and no cli then option false', () => { + const program = new commander.Command(); + process.env.NO_BAR = 'env'; + program.addOption(new commander.Option('-F, --no-foo').env('NO_BAR')); + program.parse([], { from: 'user' }); + expect(program.opts().foo).toBe(false); + delete process.env.NO_BAR; + }); +}); + +describe('boolean flag and negatable', () => { + test('when env undefined and no cli then option undefined', () => { + const program = new commander.Command(); + program + .addOption(new commander.Option('-f, --foo').env('BAR')) + .addOption(new commander.Option('-F, --no-foo').env('NO_BAR')); + program.parse([], { from: 'user' }); + expect(program.opts().foo).toBeUndefined(); + }); + + test('when env defined and no cli then option true', () => { + const program = new commander.Command(); + process.env.BAR = 'env'; + program + .addOption(new commander.Option('-f, --foo').env('BAR')) + .addOption(new commander.Option('-F, --no-foo').env('NO_BAR')); + program.parse([], { from: 'user' }); + expect(program.opts().foo).toBe(true); + delete process.env.BAR; + }); + + test('when env defined and cli --no-foo then option false', () => { + const program = new commander.Command(); + process.env.BAR = 'env'; + program + .addOption(new commander.Option('-f, --foo').env('BAR')) + .addOption(new commander.Option('-F, --no-foo').env('NO_BAR')); + program.parse(['--no-foo'], { from: 'user' }); + expect(program.opts().foo).toBe(false); + delete process.env.BAR; + }); + + test('when no_env defined and no cli then option false', () => { + const program = new commander.Command(); + process.env.NO_BAR = 'env'; + program + .addOption(new commander.Option('-f, --foo').env('BAR')) + .addOption(new commander.Option('-F, --no-foo').env('NO_BAR')); + program.parse([], { from: 'user' }); + expect(program.opts().foo).toBe(false); + delete process.env.NO_BAR; + }); + + test('when no_env defined and cli --foo then option true', () => { + const program = new commander.Command(); + process.env.NO_BAR = 'env'; + program + .addOption(new commander.Option('-f, --foo').env('BAR')) + .addOption(new commander.Option('-F, --no-foo').env('NO_BAR')); + program.parse(['--foo'], { from: 'user' }); + expect(program.opts().foo).toBe(true); + delete process.env.NO_BAR; + }); +}); + +describe('custom argParser', () => { + test('when env defined and no cli then custom parse from env', () => { + const program = new commander.Command(); + process.env.BAR = 'env'; + program.addOption(new commander.Option('-f, --foo ').env('BAR').argParser(str => str.toUpperCase())); + program.parse([], { from: 'user' }); + expect(program.opts().foo).toBe('ENV'); + delete process.env.BAR; + }); +}); + +describe('variadic', () => { + test('when env defined and no cli then array from env', () => { + const program = new commander.Command(); + process.env.BAR = 'env'; + program.addOption(new commander.Option('-f, --foo ').env('BAR')); + program.parse([], { from: 'user' }); + expect(program.opts().foo).toEqual(['env']); + delete process.env.BAR; + }); + + test('when env defined and cli then array from cli', () => { + const program = new commander.Command(); + process.env.BAR = 'env'; + program.addOption(new commander.Option('-f, --foo ').env('BAR')); + program.parse(['--foo', 'cli'], { from: 'user' }); + expect(program.opts().foo).toEqual(['cli']); + delete process.env.BAR; + }); +}); + +describe('env only processed when applies', () => { + test('when env defined on another subcommand then env not applied', () => { + // Doing selective processing. Not processing env at addOption time. + const program = new commander.Command(); + process.env.BAR = 'env'; + program.command('one') + .action(() => {}); + const two = program.command('two') + .addOption(new commander.Option('-f, --foo ').env('BAR').default('default')) + .action(() => {}); + program.parse(['one'], { from: 'user' }); + expect(two.opts().foo).toBe('default'); + delete process.env.BAR; + }); + + test('when env and cli defined then only emit option event for cli', () => { + const program = new commander.Command(); + const optionEventMock = jest.fn(); + const optionEnvEventMock = jest.fn(); + program.on('option:foo', optionEventMock); + program.on('optionEnv:foo', optionEnvEventMock); + process.env.BAR = 'env'; + program.addOption(new commander.Option('-f, --foo ').env('BAR')); + program.parse(['--foo', 'cli'], { from: 'user' }); + expect(optionEventMock).toHaveBeenCalledWith('cli'); + expect(optionEventMock).toHaveBeenCalledTimes(1); + expect(optionEnvEventMock).toHaveBeenCalledTimes(0); + delete process.env.BAR; + }); + + test('when env and cli defined then only parse value for cli', () => { + const program = new commander.Command(); + const parseMock = jest.fn(); + process.env.BAR = 'env'; + program.addOption(new commander.Option('-f, --foo ').env('BAR').argParser(parseMock)); + program.parse(['--foo', 'cli'], { from: 'user' }); + expect(parseMock).toHaveBeenCalledWith('cli', undefined); + expect(parseMock).toHaveBeenCalledTimes(1); + delete process.env.BAR; + }); +}); + +describe('events dispatched for env', () => { + const optionEnvEventMock = jest.fn(); + + afterEach(() => { + optionEnvEventMock.mockClear(); + delete process.env.BAR; + }); + + test('when env defined then emit "optionEnv" and not "option"', () => { + // Decided to do separate events, so test stays that way. + const program = new commander.Command(); + const optionEventMock = jest.fn(); + program.on('option:foo', optionEventMock); + program.on('optionEnv:foo', optionEnvEventMock); + process.env.BAR = 'env'; + program.addOption(new commander.Option('-f, --foo ').env('BAR')); + program.parse([], { from: 'user' }); + expect(optionEventMock).toHaveBeenCalledTimes(0); + expect(optionEnvEventMock).toHaveBeenCalledTimes(1); + }); + + test('when env defined for required then emit "optionEnv" with value', () => { + const program = new commander.Command(); + program.on('optionEnv:foo', optionEnvEventMock); + process.env.BAR = 'env'; + program.addOption(new commander.Option('-f, --foo ').env('BAR')); + program.parse([], { from: 'user' }); + expect(optionEnvEventMock).toHaveBeenCalledWith('env'); + }); + + test('when env defined for optional then emit "optionEnv" with value', () => { + const program = new commander.Command(); + program.on('optionEnv:foo', optionEnvEventMock); + process.env.BAR = 'env'; + program.addOption(new commander.Option('-f, --foo [optional]').env('BAR')); + program.parse([], { from: 'user' }); + expect(optionEnvEventMock).toHaveBeenCalledWith('env'); + }); + + test('when env defined for boolean then emit "optionEnv" with no param', () => { + // check matches historical boolean action event + const program = new commander.Command(); + program.on('optionEnv:foo', optionEnvEventMock); + process.env.BAR = 'anything'; + program.addOption(new commander.Option('-f, --foo').env('BAR')); + program.parse([], { from: 'user' }); + expect(optionEnvEventMock).toHaveBeenCalledWith(); + }); + + test('when env defined for negated boolean then emit "optionEnv" with no param', () => { + // check matches historical boolean action event + const program = new commander.Command(); + program.on('optionEnv:no-foo', optionEnvEventMock); + process.env.BAR = 'anything'; + program.addOption(new commander.Option('-F, --no-foo').env('BAR')); + program.parse([], { from: 'user' }); + expect(optionEnvEventMock).toHaveBeenCalledWith(); + }); +}); diff --git a/tests/options.mandatory.test.js b/tests/options.mandatory.test.js index 11221b1ec..0d06fbf27 100644 --- a/tests/options.mandatory.test.js +++ b/tests/options.mandatory.test.js @@ -166,7 +166,7 @@ describe('required program option with mandatory value not specified', () => { }); describe('required command option with mandatory value specified', () => { - test('when command has required value specified then specified value', () => { + test('when command has required value specified then no error and option has specified value', () => { const program = new commander.Command(); let cmdOptions; program @@ -181,6 +181,19 @@ describe('required command option with mandatory value specified', () => { expect(cmdOptions.subby).toBe('blue'); }); + + test('when command has required value specified using env then no error and option has specified value', () => { + const program = new commander.Command(); + program + .exitOverride() + .addOption(new commander.Option('-p, --port ', 'port number').makeOptionMandatory().env('FOO')); + + process.env.FOO = 'bar'; + program.parse([], { from: 'user' }); + delete process.env.FOO; + + expect(program.opts().port).toBe('bar'); + }); }); describe('required command option with mandatory value not specified', () => { diff --git a/typings/index.d.ts b/typings/index.d.ts index 2889e30e0..725764b6a 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -44,29 +44,29 @@ export class Argument { constructor(arg: string, description?: string); /** - * Return argument name. - */ + * Return argument name. + */ name(): string; /** * Set the default value, and optionally supply the description to be displayed in the help. */ - default(value: unknown, description?: string): this; + default(value: unknown, description?: string): this; /** * Set the custom handler for processing CLI command arguments into argument values. */ - argParser(fn: (value: string, previous: T) => T): this; + argParser(fn: (value: string, previous: T) => T): this; /** * Only allow argument value to be one of choices. */ - choices(values: string[]): this; + choices(values: string[]): this; /** * Make option-argument required. */ - argRequired(): this; + argRequired(): this; /** * Make option-argument optional. @@ -99,6 +99,12 @@ export class Option { */ default(value: unknown, description?: string): this; + /** + * Set environment variable to check for option value. + * Priority order of option values is default < env < cli + */ + env(name: string): this; + /** * Calculate the full description, including defaultValue etc. */ diff --git a/typings/index.test-d.ts b/typings/index.test-d.ts index 237fb37ee..d37a0e386 100644 --- a/typings/index.test-d.ts +++ b/typings/index.test-d.ts @@ -345,6 +345,9 @@ const baseOption = new commander.Option('-f,--foo', 'foo description'); expectType(baseOption.default(3)); expectType(baseOption.default(60, 'one minute')); +// env +expectType(baseOption.env('PORT')); + // fullDescription expectType(baseOption.fullDescription());