UNPKG

68.8 kBJavaScriptView Raw
1const EventEmitter = require('events').EventEmitter;
2const childProcess = require('child_process');
3const path = require('path');
4const fs = require('fs');
5const process = require('process');
6
7const { Argument, humanReadableArgName } = require('./argument.js');
8const { CommanderError } = require('./error.js');
9const { Help } = require('./help.js');
10const { Option, splitOptionFlags, DualOptions } = require('./option.js');
11const { suggestSimilar } = require('./suggestSimilar');
12
13// @ts-check
14
15class Command extends EventEmitter {
16 /**
17 * Initialize a new `Command`.
18 *
19 * @param {string} [name]
20 */
21
22 constructor(name) {
23 super();
24 /** @type {Command[]} */
25 this.commands = [];
26 /** @type {Option[]} */
27 this.options = [];
28 this.parent = null;
29 this._allowUnknownOption = false;
30 this._allowExcessArguments = true;
31 /** @type {Argument[]} */
32 this._args = [];
33 /** @type {string[]} */
34 this.args = []; // cli args with options removed
35 this.rawArgs = [];
36 this.processedArgs = []; // like .args but after custom processing and collecting variadic
37 this._scriptPath = null;
38 this._name = name || '';
39 this._optionValues = {};
40 this._optionValueSources = {}; // default < config < env < cli
41 this._storeOptionsAsProperties = false;
42 this._actionHandler = null;
43 this._executableHandler = false;
44 this._executableFile = null; // custom name for executable
45 this._executableDir = null; // custom search directory for subcommands
46 this._defaultCommandName = null;
47 this._exitCallback = null;
48 this._aliases = [];
49 this._combineFlagAndOptionalValue = true;
50 this._description = '';
51 this._summary = '';
52 this._argsDescription = undefined; // legacy
53 this._enablePositionalOptions = false;
54 this._passThroughOptions = false;
55 this._lifeCycleHooks = {}; // a hash of arrays
56 /** @type {boolean | string} */
57 this._showHelpAfterError = false;
58 this._showSuggestionAfterError = true;
59
60 // see .configureOutput() for docs
61 this._outputConfiguration = {
62 writeOut: (str) => process.stdout.write(str),
63 writeErr: (str) => process.stderr.write(str),
64 getOutHelpWidth: () => process.stdout.isTTY ? process.stdout.columns : undefined,
65 getErrHelpWidth: () => process.stderr.isTTY ? process.stderr.columns : undefined,
66 outputError: (str, write) => write(str)
67 };
68
69 this._hidden = false;
70 this._hasHelpOption = true;
71 this._helpFlags = '-h, --help';
72 this._helpDescription = 'display help for command';
73 this._helpShortFlag = '-h';
74 this._helpLongFlag = '--help';
75 this._addImplicitHelpCommand = undefined; // Deliberately undefined, not decided whether true or false
76 this._helpCommandName = 'help';
77 this._helpCommandnameAndArgs = 'help [command]';
78 this._helpCommandDescription = 'display help for command';
79 this._helpConfiguration = {};
80 }
81
82 /**
83 * Copy settings that are useful to have in common across root command and subcommands.
84 *
85 * (Used internally when adding a command using `.command()` so subcommands inherit parent settings.)
86 *
87 * @param {Command} sourceCommand
88 * @return {Command} `this` command for chaining
89 */
90 copyInheritedSettings(sourceCommand) {
91 this._outputConfiguration = sourceCommand._outputConfiguration;
92 this._hasHelpOption = sourceCommand._hasHelpOption;
93 this._helpFlags = sourceCommand._helpFlags;
94 this._helpDescription = sourceCommand._helpDescription;
95 this._helpShortFlag = sourceCommand._helpShortFlag;
96 this._helpLongFlag = sourceCommand._helpLongFlag;
97 this._helpCommandName = sourceCommand._helpCommandName;
98 this._helpCommandnameAndArgs = sourceCommand._helpCommandnameAndArgs;
99 this._helpCommandDescription = sourceCommand._helpCommandDescription;
100 this._helpConfiguration = sourceCommand._helpConfiguration;
101 this._exitCallback = sourceCommand._exitCallback;
102 this._storeOptionsAsProperties = sourceCommand._storeOptionsAsProperties;
103 this._combineFlagAndOptionalValue = sourceCommand._combineFlagAndOptionalValue;
104 this._allowExcessArguments = sourceCommand._allowExcessArguments;
105 this._enablePositionalOptions = sourceCommand._enablePositionalOptions;
106 this._showHelpAfterError = sourceCommand._showHelpAfterError;
107 this._showSuggestionAfterError = sourceCommand._showSuggestionAfterError;
108
109 return this;
110 }
111
112 /**
113 * Define a command.
114 *
115 * There are two styles of command: pay attention to where to put the description.
116 *
117 * @example
118 * // Command implemented using action handler (description is supplied separately to `.command`)
119 * program
120 * .command('clone <source> [destination]')
121 * .description('clone a repository into a newly created directory')
122 * .action((source, destination) => {
123 * console.log('clone command called');
124 * });
125 *
126 * // Command implemented using separate executable file (description is second parameter to `.command`)
127 * program
128 * .command('start <service>', 'start named service')
129 * .command('stop [service]', 'stop named service, or all if no name supplied');
130 *
131 * @param {string} nameAndArgs - command name and arguments, args are `<required>` or `[optional]` and last may also be `variadic...`
132 * @param {Object|string} [actionOptsOrExecDesc] - configuration options (for action), or description (for executable)
133 * @param {Object} [execOpts] - configuration options (for executable)
134 * @return {Command} returns new command for action handler, or `this` for executable command
135 */
136
137 command(nameAndArgs, actionOptsOrExecDesc, execOpts) {
138 let desc = actionOptsOrExecDesc;
139 let opts = execOpts;
140 if (typeof desc === 'object' && desc !== null) {
141 opts = desc;
142 desc = null;
143 }
144 opts = opts || {};
145 const [, name, args] = nameAndArgs.match(/([^ ]+) *(.*)/);
146
147 const cmd = this.createCommand(name);
148 if (desc) {
149 cmd.description(desc);
150 cmd._executableHandler = true;
151 }
152 if (opts.isDefault) this._defaultCommandName = cmd._name;
153 cmd._hidden = !!(opts.noHelp || opts.hidden); // noHelp is deprecated old name for hidden
154 cmd._executableFile = opts.executableFile || null; // Custom name for executable file, set missing to null to match constructor
155 if (args) cmd.arguments(args);
156 this.commands.push(cmd);
157 cmd.parent = this;
158 cmd.copyInheritedSettings(this);
159
160 if (desc) return this;
161 return cmd;
162 }
163
164 /**
165 * Factory routine to create a new unattached command.
166 *
167 * See .command() for creating an attached subcommand, which uses this routine to
168 * create the command. You can override createCommand to customise subcommands.
169 *
170 * @param {string} [name]
171 * @return {Command} new command
172 */
173
174 createCommand(name) {
175 return new Command(name);
176 }
177
178 /**
179 * You can customise the help with a subclass of Help by overriding createHelp,
180 * or by overriding Help properties using configureHelp().
181 *
182 * @return {Help}
183 */
184
185 createHelp() {
186 return Object.assign(new Help(), this.configureHelp());
187 }
188
189 /**
190 * You can customise the help by overriding Help properties using configureHelp(),
191 * or with a subclass of Help by overriding createHelp().
192 *
193 * @param {Object} [configuration] - configuration options
194 * @return {Command|Object} `this` command for chaining, or stored configuration
195 */
196
197 configureHelp(configuration) {
198 if (configuration === undefined) return this._helpConfiguration;
199
200 this._helpConfiguration = configuration;
201 return this;
202 }
203
204 /**
205 * The default output goes to stdout and stderr. You can customise this for special
206 * applications. You can also customise the display of errors by overriding outputError.
207 *
208 * The configuration properties are all functions:
209 *
210 * // functions to change where being written, stdout and stderr
211 * writeOut(str)
212 * writeErr(str)
213 * // matching functions to specify width for wrapping help
214 * getOutHelpWidth()
215 * getErrHelpWidth()
216 * // functions based on what is being written out
217 * outputError(str, write) // used for displaying errors, and not used for displaying help
218 *
219 * @param {Object} [configuration] - configuration options
220 * @return {Command|Object} `this` command for chaining, or stored configuration
221 */
222
223 configureOutput(configuration) {
224 if (configuration === undefined) return this._outputConfiguration;
225
226 Object.assign(this._outputConfiguration, configuration);
227 return this;
228 }
229
230 /**
231 * Display the help or a custom message after an error occurs.
232 *
233 * @param {boolean|string} [displayHelp]
234 * @return {Command} `this` command for chaining
235 */
236 showHelpAfterError(displayHelp = true) {
237 if (typeof displayHelp !== 'string') displayHelp = !!displayHelp;
238 this._showHelpAfterError = displayHelp;
239 return this;
240 }
241
242 /**
243 * Display suggestion of similar commands for unknown commands, or options for unknown options.
244 *
245 * @param {boolean} [displaySuggestion]
246 * @return {Command} `this` command for chaining
247 */
248 showSuggestionAfterError(displaySuggestion = true) {
249 this._showSuggestionAfterError = !!displaySuggestion;
250 return this;
251 }
252
253 /**
254 * Add a prepared subcommand.
255 *
256 * See .command() for creating an attached subcommand which inherits settings from its parent.
257 *
258 * @param {Command} cmd - new subcommand
259 * @param {Object} [opts] - configuration options
260 * @return {Command} `this` command for chaining
261 */
262
263 addCommand(cmd, opts) {
264 if (!cmd._name) {
265 throw new Error(`Command passed to .addCommand() must have a name
266- specify the name in Command constructor or using .name()`);
267 }
268
269 opts = opts || {};
270 if (opts.isDefault) this._defaultCommandName = cmd._name;
271 if (opts.noHelp || opts.hidden) cmd._hidden = true; // modifying passed command due to existing implementation
272
273 this.commands.push(cmd);
274 cmd.parent = this;
275 return this;
276 }
277
278 /**
279 * Factory routine to create a new unattached argument.
280 *
281 * See .argument() for creating an attached argument, which uses this routine to
282 * create the argument. You can override createArgument to return a custom argument.
283 *
284 * @param {string} name
285 * @param {string} [description]
286 * @return {Argument} new argument
287 */
288
289 createArgument(name, description) {
290 return new Argument(name, description);
291 }
292
293 /**
294 * Define argument syntax for command.
295 *
296 * The default is that the argument is required, and you can explicitly
297 * indicate this with <> around the name. Put [] around the name for an optional argument.
298 *
299 * @example
300 * program.argument('<input-file>');
301 * program.argument('[output-file]');
302 *
303 * @param {string} name
304 * @param {string} [description]
305 * @param {Function|*} [fn] - custom argument processing function
306 * @param {*} [defaultValue]
307 * @return {Command} `this` command for chaining
308 */
309 argument(name, description, fn, defaultValue) {
310 const argument = this.createArgument(name, description);
311 if (typeof fn === 'function') {
312 argument.default(defaultValue).argParser(fn);
313 } else {
314 argument.default(fn);
315 }
316 this.addArgument(argument);
317 return this;
318 }
319
320 /**
321 * Define argument syntax for command, adding multiple at once (without descriptions).
322 *
323 * See also .argument().
324 *
325 * @example
326 * program.arguments('<cmd> [env]');
327 *
328 * @param {string} names
329 * @return {Command} `this` command for chaining
330 */
331
332 arguments(names) {
333 names.split(/ +/).forEach((detail) => {
334 this.argument(detail);
335 });
336 return this;
337 }
338
339 /**
340 * Define argument syntax for command, adding a prepared argument.
341 *
342 * @param {Argument} argument
343 * @return {Command} `this` command for chaining
344 */
345 addArgument(argument) {
346 const previousArgument = this._args.slice(-1)[0];
347 if (previousArgument && previousArgument.variadic) {
348 throw new Error(`only the last argument can be variadic '${previousArgument.name()}'`);
349 }
350 if (argument.required && argument.defaultValue !== undefined && argument.parseArg === undefined) {
351 throw new Error(`a default value for a required argument is never used: '${argument.name()}'`);
352 }
353 this._args.push(argument);
354 return this;
355 }
356
357 /**
358 * Override default decision whether to add implicit help command.
359 *
360 * addHelpCommand() // force on
361 * addHelpCommand(false); // force off
362 * addHelpCommand('help [cmd]', 'display help for [cmd]'); // force on with custom details
363 *
364 * @return {Command} `this` command for chaining
365 */
366
367 addHelpCommand(enableOrNameAndArgs, description) {
368 if (enableOrNameAndArgs === false) {
369 this._addImplicitHelpCommand = false;
370 } else {
371 this._addImplicitHelpCommand = true;
372 if (typeof enableOrNameAndArgs === 'string') {
373 this._helpCommandName = enableOrNameAndArgs.split(' ')[0];
374 this._helpCommandnameAndArgs = enableOrNameAndArgs;
375 }
376 this._helpCommandDescription = description || this._helpCommandDescription;
377 }
378 return this;
379 }
380
381 /**
382 * @return {boolean}
383 * @api private
384 */
385
386 _hasImplicitHelpCommand() {
387 if (this._addImplicitHelpCommand === undefined) {
388 return this.commands.length && !this._actionHandler && !this._findCommand('help');
389 }
390 return this._addImplicitHelpCommand;
391 }
392
393 /**
394 * Add hook for life cycle event.
395 *
396 * @param {string} event
397 * @param {Function} listener
398 * @return {Command} `this` command for chaining
399 */
400
401 hook(event, listener) {
402 const allowedValues = ['preAction', 'postAction'];
403 if (!allowedValues.includes(event)) {
404 throw new Error(`Unexpected value for event passed to hook : '${event}'.
405Expecting one of '${allowedValues.join("', '")}'`);
406 }
407 if (this._lifeCycleHooks[event]) {
408 this._lifeCycleHooks[event].push(listener);
409 } else {
410 this._lifeCycleHooks[event] = [listener];
411 }
412 return this;
413 }
414
415 /**
416 * Register callback to use as replacement for calling process.exit.
417 *
418 * @param {Function} [fn] optional callback which will be passed a CommanderError, defaults to throwing
419 * @return {Command} `this` command for chaining
420 */
421
422 exitOverride(fn) {
423 if (fn) {
424 this._exitCallback = fn;
425 } else {
426 this._exitCallback = (err) => {
427 if (err.code !== 'commander.executeSubCommandAsync') {
428 throw err;
429 } else {
430 // Async callback from spawn events, not useful to throw.
431 }
432 };
433 }
434 return this;
435 }
436
437 /**
438 * Call process.exit, and _exitCallback if defined.
439 *
440 * @param {number} exitCode exit code for using with process.exit
441 * @param {string} code an id string representing the error
442 * @param {string} message human-readable description of the error
443 * @return never
444 * @api private
445 */
446
447 _exit(exitCode, code, message) {
448 if (this._exitCallback) {
449 this._exitCallback(new CommanderError(exitCode, code, message));
450 // Expecting this line is not reached.
451 }
452 process.exit(exitCode);
453 }
454
455 /**
456 * Register callback `fn` for the command.
457 *
458 * @example
459 * program
460 * .command('serve')
461 * .description('start service')
462 * .action(function() {
463 * // do work here
464 * });
465 *
466 * @param {Function} fn
467 * @return {Command} `this` command for chaining
468 */
469
470 action(fn) {
471 const listener = (args) => {
472 // The .action callback takes an extra parameter which is the command or options.
473 const expectedArgsCount = this._args.length;
474 const actionArgs = args.slice(0, expectedArgsCount);
475 if (this._storeOptionsAsProperties) {
476 actionArgs[expectedArgsCount] = this; // backwards compatible "options"
477 } else {
478 actionArgs[expectedArgsCount] = this.opts();
479 }
480 actionArgs.push(this);
481
482 return fn.apply(this, actionArgs);
483 };
484 this._actionHandler = listener;
485 return this;
486 }
487
488 /**
489 * Factory routine to create a new unattached option.
490 *
491 * See .option() for creating an attached option, which uses this routine to
492 * create the option. You can override createOption to return a custom option.
493 *
494 * @param {string} flags
495 * @param {string} [description]
496 * @return {Option} new option
497 */
498
499 createOption(flags, description) {
500 return new Option(flags, description);
501 }
502
503 /**
504 * Add an option.
505 *
506 * @param {Option} option
507 * @return {Command} `this` command for chaining
508 */
509 addOption(option) {
510 const oname = option.name();
511 const name = option.attributeName();
512
513 // store default value
514 if (option.negate) {
515 // --no-foo is special and defaults foo to true, unless a --foo option is already defined
516 const positiveLongFlag = option.long.replace(/^--no-/, '--');
517 if (!this._findOption(positiveLongFlag)) {
518 this.setOptionValueWithSource(name, option.defaultValue === undefined ? true : option.defaultValue, 'default');
519 }
520 } else if (option.defaultValue !== undefined) {
521 this.setOptionValueWithSource(name, option.defaultValue, 'default');
522 }
523
524 // register the option
525 this.options.push(option);
526
527 // handler for cli and env supplied values
528 const handleOptionValue = (val, invalidValueMessage, valueSource) => {
529 // val is null for optional option used without an optional-argument.
530 // val is undefined for boolean and negated option.
531 if (val == null && option.presetArg !== undefined) {
532 val = option.presetArg;
533 }
534
535 // custom processing
536 const oldValue = this.getOptionValue(name);
537 if (val !== null && option.parseArg) {
538 try {
539 val = option.parseArg(val, oldValue);
540 } catch (err) {
541 if (err.code === 'commander.invalidArgument') {
542 const message = `${invalidValueMessage} ${err.message}`;
543 this.error(message, { exitCode: err.exitCode, code: err.code });
544 }
545 throw err;
546 }
547 } else if (val !== null && option.variadic) {
548 val = option._concatValue(val, oldValue);
549 }
550
551 // Fill-in appropriate missing values. Long winded but easy to follow.
552 if (val == null) {
553 if (option.negate) {
554 val = false;
555 } else if (option.isBoolean() || option.optional) {
556 val = true;
557 } else {
558 val = ''; // not normal, parseArg might have failed or be a mock function for testing
559 }
560 }
561 this.setOptionValueWithSource(name, val, valueSource);
562 };
563
564 this.on('option:' + oname, (val) => {
565 const invalidValueMessage = `error: option '${option.flags}' argument '${val}' is invalid.`;
566 handleOptionValue(val, invalidValueMessage, 'cli');
567 });
568
569 if (option.envVar) {
570 this.on('optionEnv:' + oname, (val) => {
571 const invalidValueMessage = `error: option '${option.flags}' value '${val}' from env '${option.envVar}' is invalid.`;
572 handleOptionValue(val, invalidValueMessage, 'env');
573 });
574 }
575
576 return this;
577 }
578
579 /**
580 * Internal implementation shared by .option() and .requiredOption()
581 *
582 * @api private
583 */
584 _optionEx(config, flags, description, fn, defaultValue) {
585 if (typeof flags === 'object' && flags instanceof Option) {
586 throw new Error('To add an Option object use addOption() instead of option() or requiredOption()');
587 }
588 const option = this.createOption(flags, description);
589 option.makeOptionMandatory(!!config.mandatory);
590 if (typeof fn === 'function') {
591 option.default(defaultValue).argParser(fn);
592 } else if (fn instanceof RegExp) {
593 // deprecated
594 const regex = fn;
595 fn = (val, def) => {
596 const m = regex.exec(val);
597 return m ? m[0] : def;
598 };
599 option.default(defaultValue).argParser(fn);
600 } else {
601 option.default(fn);
602 }
603
604 return this.addOption(option);
605 }
606
607 /**
608 * Define option with `flags`, `description` and optional
609 * coercion `fn`.
610 *
611 * The `flags` string contains the short and/or long flags,
612 * separated by comma, a pipe or space. The following are all valid
613 * all will output this way when `--help` is used.
614 *
615 * "-p, --pepper"
616 * "-p|--pepper"
617 * "-p --pepper"
618 *
619 * @example
620 * // simple boolean defaulting to undefined
621 * program.option('-p, --pepper', 'add pepper');
622 *
623 * program.pepper
624 * // => undefined
625 *
626 * --pepper
627 * program.pepper
628 * // => true
629 *
630 * // simple boolean defaulting to true (unless non-negated option is also defined)
631 * program.option('-C, --no-cheese', 'remove cheese');
632 *
633 * program.cheese
634 * // => true
635 *
636 * --no-cheese
637 * program.cheese
638 * // => false
639 *
640 * // required argument
641 * program.option('-C, --chdir <path>', 'change the working directory');
642 *
643 * --chdir /tmp
644 * program.chdir
645 * // => "/tmp"
646 *
647 * // optional argument
648 * program.option('-c, --cheese [type]', 'add cheese [marble]');
649 *
650 * @param {string} flags
651 * @param {string} [description]
652 * @param {Function|*} [fn] - custom option processing function or default value
653 * @param {*} [defaultValue]
654 * @return {Command} `this` command for chaining
655 */
656
657 option(flags, description, fn, defaultValue) {
658 return this._optionEx({}, flags, description, fn, defaultValue);
659 }
660
661 /**
662 * Add a required option which must have a value after parsing. This usually means
663 * the option must be specified on the command line. (Otherwise the same as .option().)
664 *
665 * The `flags` string contains the short and/or long flags, separated by comma, a pipe or space.
666 *
667 * @param {string} flags
668 * @param {string} [description]
669 * @param {Function|*} [fn] - custom option processing function or default value
670 * @param {*} [defaultValue]
671 * @return {Command} `this` command for chaining
672 */
673
674 requiredOption(flags, description, fn, defaultValue) {
675 return this._optionEx({ mandatory: true }, flags, description, fn, defaultValue);
676 }
677
678 /**
679 * Alter parsing of short flags with optional values.
680 *
681 * @example
682 * // for `.option('-f,--flag [value]'):
683 * program.combineFlagAndOptionalValue(true); // `-f80` is treated like `--flag=80`, this is the default behaviour
684 * program.combineFlagAndOptionalValue(false) // `-fb` is treated like `-f -b`
685 *
686 * @param {Boolean} [combine=true] - if `true` or omitted, an optional value can be specified directly after the flag.
687 */
688 combineFlagAndOptionalValue(combine = true) {
689 this._combineFlagAndOptionalValue = !!combine;
690 return this;
691 }
692
693 /**
694 * Allow unknown options on the command line.
695 *
696 * @param {Boolean} [allowUnknown=true] - if `true` or omitted, no error will be thrown
697 * for unknown options.
698 */
699 allowUnknownOption(allowUnknown = true) {
700 this._allowUnknownOption = !!allowUnknown;
701 return this;
702 }
703
704 /**
705 * Allow excess command-arguments on the command line. Pass false to make excess arguments an error.
706 *
707 * @param {Boolean} [allowExcess=true] - if `true` or omitted, no error will be thrown
708 * for excess arguments.
709 */
710 allowExcessArguments(allowExcess = true) {
711 this._allowExcessArguments = !!allowExcess;
712 return this;
713 }
714
715 /**
716 * Enable positional options. Positional means global options are specified before subcommands which lets
717 * subcommands reuse the same option names, and also enables subcommands to turn on passThroughOptions.
718 * The default behaviour is non-positional and global options may appear anywhere on the command line.
719 *
720 * @param {Boolean} [positional=true]
721 */
722 enablePositionalOptions(positional = true) {
723 this._enablePositionalOptions = !!positional;
724 return this;
725 }
726
727 /**
728 * Pass through options that come after command-arguments rather than treat them as command-options,
729 * so actual command-options come before command-arguments. Turning this on for a subcommand requires
730 * positional options to have been enabled on the program (parent commands).
731 * The default behaviour is non-positional and options may appear before or after command-arguments.
732 *
733 * @param {Boolean} [passThrough=true]
734 * for unknown options.
735 */
736 passThroughOptions(passThrough = true) {
737 this._passThroughOptions = !!passThrough;
738 if (!!this.parent && passThrough && !this.parent._enablePositionalOptions) {
739 throw new Error('passThroughOptions can not be used without turning on enablePositionalOptions for parent command(s)');
740 }
741 return this;
742 }
743
744 /**
745 * Whether to store option values as properties on command object,
746 * or store separately (specify false). In both cases the option values can be accessed using .opts().
747 *
748 * @param {boolean} [storeAsProperties=true]
749 * @return {Command} `this` command for chaining
750 */
751
752 storeOptionsAsProperties(storeAsProperties = true) {
753 this._storeOptionsAsProperties = !!storeAsProperties;
754 if (this.options.length) {
755 throw new Error('call .storeOptionsAsProperties() before adding options');
756 }
757 return this;
758 }
759
760 /**
761 * Retrieve option value.
762 *
763 * @param {string} key
764 * @return {Object} value
765 */
766
767 getOptionValue(key) {
768 if (this._storeOptionsAsProperties) {
769 return this[key];
770 }
771 return this._optionValues[key];
772 }
773
774 /**
775 * Store option value.
776 *
777 * @param {string} key
778 * @param {Object} value
779 * @return {Command} `this` command for chaining
780 */
781
782 setOptionValue(key, value) {
783 if (this._storeOptionsAsProperties) {
784 this[key] = value;
785 } else {
786 this._optionValues[key] = value;
787 }
788 return this;
789 }
790
791 /**
792 * Store option value and where the value came from.
793 *
794 * @param {string} key
795 * @param {Object} value
796 * @param {string} source - expected values are default/config/env/cli
797 * @return {Command} `this` command for chaining
798 */
799
800 setOptionValueWithSource(key, value, source) {
801 this.setOptionValue(key, value);
802 this._optionValueSources[key] = source;
803 return this;
804 }
805
806 /**
807 * Get source of option value.
808 * Expected values are default | config | env | cli
809 *
810 * @param {string} key
811 * @return {string}
812 */
813
814 getOptionValueSource(key) {
815 return this._optionValueSources[key];
816 }
817
818 /**
819 * Get user arguments from implied or explicit arguments.
820 * Side-effects: set _scriptPath if args included script. Used for default program name, and subcommand searches.
821 *
822 * @api private
823 */
824
825 _prepareUserArgs(argv, parseOptions) {
826 if (argv !== undefined && !Array.isArray(argv)) {
827 throw new Error('first parameter to parse must be array or undefined');
828 }
829 parseOptions = parseOptions || {};
830
831 // Default to using process.argv
832 if (argv === undefined) {
833 argv = process.argv;
834 // @ts-ignore: unknown property
835 if (process.versions && process.versions.electron) {
836 parseOptions.from = 'electron';
837 }
838 }
839 this.rawArgs = argv.slice();
840
841 // make it a little easier for callers by supporting various argv conventions
842 let userArgs;
843 switch (parseOptions.from) {
844 case undefined:
845 case 'node':
846 this._scriptPath = argv[1];
847 userArgs = argv.slice(2);
848 break;
849 case 'electron':
850 // @ts-ignore: unknown property
851 if (process.defaultApp) {
852 this._scriptPath = argv[1];
853 userArgs = argv.slice(2);
854 } else {
855 userArgs = argv.slice(1);
856 }
857 break;
858 case 'user':
859 userArgs = argv.slice(0);
860 break;
861 default:
862 throw new Error(`unexpected parse option { from: '${parseOptions.from}' }`);
863 }
864
865 // Find default name for program from arguments.
866 if (!this._name && this._scriptPath) this.nameFromFilename(this._scriptPath);
867 this._name = this._name || 'program';
868
869 return userArgs;
870 }
871
872 /**
873 * Parse `argv`, setting options and invoking commands when defined.
874 *
875 * The default expectation is that the arguments are from node and have the application as argv[0]
876 * and the script being run in argv[1], with user parameters after that.
877 *
878 * @example
879 * program.parse(process.argv);
880 * program.parse(); // implicitly use process.argv and auto-detect node vs electron conventions
881 * program.parse(my-args, { from: 'user' }); // just user supplied arguments, nothing special about argv[0]
882 *
883 * @param {string[]} [argv] - optional, defaults to process.argv
884 * @param {Object} [parseOptions] - optionally specify style of options with from: node/user/electron
885 * @param {string} [parseOptions.from] - where the args are from: 'node', 'user', 'electron'
886 * @return {Command} `this` command for chaining
887 */
888
889 parse(argv, parseOptions) {
890 const userArgs = this._prepareUserArgs(argv, parseOptions);
891 this._parseCommand([], userArgs);
892
893 return this;
894 }
895
896 /**
897 * Parse `argv`, setting options and invoking commands when defined.
898 *
899 * Use parseAsync instead of parse if any of your action handlers are async. Returns a Promise.
900 *
901 * The default expectation is that the arguments are from node and have the application as argv[0]
902 * and the script being run in argv[1], with user parameters after that.
903 *
904 * @example
905 * await program.parseAsync(process.argv);
906 * await program.parseAsync(); // implicitly use process.argv and auto-detect node vs electron conventions
907 * await program.parseAsync(my-args, { from: 'user' }); // just user supplied arguments, nothing special about argv[0]
908 *
909 * @param {string[]} [argv]
910 * @param {Object} [parseOptions]
911 * @param {string} parseOptions.from - where the args are from: 'node', 'user', 'electron'
912 * @return {Promise}
913 */
914
915 async parseAsync(argv, parseOptions) {
916 const userArgs = this._prepareUserArgs(argv, parseOptions);
917 await this._parseCommand([], userArgs);
918
919 return this;
920 }
921
922 /**
923 * Execute a sub-command executable.
924 *
925 * @api private
926 */
927
928 _executeSubCommand(subcommand, args) {
929 args = args.slice();
930 let launchWithNode = false; // Use node for source targets so do not need to get permissions correct, and on Windows.
931 const sourceExt = ['.js', '.ts', '.tsx', '.mjs', '.cjs'];
932
933 function findFile(baseDir, baseName) {
934 // Look for specified file
935 const localBin = path.resolve(baseDir, baseName);
936 if (fs.existsSync(localBin)) return localBin;
937
938 // Stop looking if candidate already has an expected extension.
939 if (sourceExt.includes(path.extname(baseName))) return undefined;
940
941 // Try all the extensions.
942 const foundExt = sourceExt.find(ext => fs.existsSync(`${localBin}${ext}`));
943 if (foundExt) return `${localBin}${foundExt}`;
944
945 return undefined;
946 }
947
948 // Not checking for help first. Unlikely to have mandatory and executable, and can't robustly test for help flags in external command.
949 this._checkForMissingMandatoryOptions();
950 this._checkForConflictingOptions();
951
952 // executableFile and executableDir might be full path, or just a name
953 let executableFile = subcommand._executableFile || `${this._name}-${subcommand._name}`;
954 let executableDir = this._executableDir || '';
955 if (this._scriptPath) {
956 let resolvedScriptPath; // resolve possible symlink for installed npm binary
957 try {
958 resolvedScriptPath = fs.realpathSync(this._scriptPath);
959 } catch (err) {
960 resolvedScriptPath = this._scriptPath;
961 }
962 executableDir = path.resolve(path.dirname(resolvedScriptPath), executableDir);
963 }
964
965 // Look for a local file in preference to a command in PATH.
966 if (executableDir) {
967 let localFile = findFile(executableDir, executableFile);
968
969 // Legacy search using prefix of script name instead of command name
970 if (!localFile && !subcommand._executableFile && this._scriptPath) {
971 const legacyName = path.basename(this._scriptPath, path.extname(this._scriptPath));
972 if (legacyName !== this._name) {
973 localFile = findFile(executableDir, `${legacyName}-${subcommand._name}`);
974 }
975 }
976 executableFile = localFile || executableFile;
977 }
978
979 launchWithNode = sourceExt.includes(path.extname(executableFile));
980
981 let proc;
982 if (process.platform !== 'win32') {
983 if (launchWithNode) {
984 args.unshift(executableFile);
985 // add executable arguments to spawn
986 args = incrementNodeInspectorPort(process.execArgv).concat(args);
987
988 proc = childProcess.spawn(process.argv[0], args, { stdio: 'inherit' });
989 } else {
990 proc = childProcess.spawn(executableFile, args, { stdio: 'inherit' });
991 }
992 } else {
993 args.unshift(executableFile);
994 // add executable arguments to spawn
995 args = incrementNodeInspectorPort(process.execArgv).concat(args);
996 proc = childProcess.spawn(process.execPath, args, { stdio: 'inherit' });
997 }
998
999 if (!proc.killed) { // testing mainly to avoid leak warnings during unit tests with mocked spawn
1000 const signals = ['SIGUSR1', 'SIGUSR2', 'SIGTERM', 'SIGINT', 'SIGHUP'];
1001 signals.forEach((signal) => {
1002 // @ts-ignore
1003 process.on(signal, () => {
1004 if (proc.killed === false && proc.exitCode === null) {
1005 proc.kill(signal);
1006 }
1007 });
1008 });
1009 }
1010
1011 // By default terminate process when spawned process terminates.
1012 // Suppressing the exit if exitCallback defined is a bit messy and of limited use, but does allow process to stay running!
1013 const exitCallback = this._exitCallback;
1014 if (!exitCallback) {
1015 proc.on('close', process.exit.bind(process));
1016 } else {
1017 proc.on('close', () => {
1018 exitCallback(new CommanderError(process.exitCode || 0, 'commander.executeSubCommandAsync', '(close)'));
1019 });
1020 }
1021 proc.on('error', (err) => {
1022 // @ts-ignore
1023 if (err.code === 'ENOENT') {
1024 const executableDirMessage = executableDir
1025 ? `searched for local subcommand relative to directory '${executableDir}'`
1026 : 'no directory for search for local subcommand, use .executableDir() to supply a custom directory';
1027 const executableMissing = `'${executableFile}' does not exist
1028 - if '${subcommand._name}' is not meant to be an executable command, remove description parameter from '.command()' and use '.description()' instead
1029 - if the default executable name is not suitable, use the executableFile option to supply a custom name or path
1030 - ${executableDirMessage}`;
1031 throw new Error(executableMissing);
1032 // @ts-ignore
1033 } else if (err.code === 'EACCES') {
1034 throw new Error(`'${executableFile}' not executable`);
1035 }
1036 if (!exitCallback) {
1037 process.exit(1);
1038 } else {
1039 const wrappedError = new CommanderError(1, 'commander.executeSubCommandAsync', '(error)');
1040 wrappedError.nestedError = err;
1041 exitCallback(wrappedError);
1042 }
1043 });
1044
1045 // Store the reference to the child process
1046 this.runningCommand = proc;
1047 }
1048
1049 /**
1050 * @api private
1051 */
1052
1053 _dispatchSubcommand(commandName, operands, unknown) {
1054 const subCommand = this._findCommand(commandName);
1055 if (!subCommand) this.help({ error: true });
1056
1057 if (subCommand._executableHandler) {
1058 this._executeSubCommand(subCommand, operands.concat(unknown));
1059 } else {
1060 return subCommand._parseCommand(operands, unknown);
1061 }
1062 }
1063
1064 /**
1065 * Check this.args against expected this._args.
1066 *
1067 * @api private
1068 */
1069
1070 _checkNumberOfArguments() {
1071 // too few
1072 this._args.forEach((arg, i) => {
1073 if (arg.required && this.args[i] == null) {
1074 this.missingArgument(arg.name());
1075 }
1076 });
1077 // too many
1078 if (this._args.length > 0 && this._args[this._args.length - 1].variadic) {
1079 return;
1080 }
1081 if (this.args.length > this._args.length) {
1082 this._excessArguments(this.args);
1083 }
1084 }
1085
1086 /**
1087 * Process this.args using this._args and save as this.processedArgs!
1088 *
1089 * @api private
1090 */
1091
1092 _processArguments() {
1093 const myParseArg = (argument, value, previous) => {
1094 // Extra processing for nice error message on parsing failure.
1095 let parsedValue = value;
1096 if (value !== null && argument.parseArg) {
1097 try {
1098 parsedValue = argument.parseArg(value, previous);
1099 } catch (err) {
1100 if (err.code === 'commander.invalidArgument') {
1101 const message = `error: command-argument value '${value}' is invalid for argument '${argument.name()}'. ${err.message}`;
1102 this.error(message, { exitCode: err.exitCode, code: err.code });
1103 }
1104 throw err;
1105 }
1106 }
1107 return parsedValue;
1108 };
1109
1110 this._checkNumberOfArguments();
1111
1112 const processedArgs = [];
1113 this._args.forEach((declaredArg, index) => {
1114 let value = declaredArg.defaultValue;
1115 if (declaredArg.variadic) {
1116 // Collect together remaining arguments for passing together as an array.
1117 if (index < this.args.length) {
1118 value = this.args.slice(index);
1119 if (declaredArg.parseArg) {
1120 value = value.reduce((processed, v) => {
1121 return myParseArg(declaredArg, v, processed);
1122 }, declaredArg.defaultValue);
1123 }
1124 } else if (value === undefined) {
1125 value = [];
1126 }
1127 } else if (index < this.args.length) {
1128 value = this.args[index];
1129 if (declaredArg.parseArg) {
1130 value = myParseArg(declaredArg, value, declaredArg.defaultValue);
1131 }
1132 }
1133 processedArgs[index] = value;
1134 });
1135 this.processedArgs = processedArgs;
1136 }
1137
1138 /**
1139 * Once we have a promise we chain, but call synchronously until then.
1140 *
1141 * @param {Promise|undefined} promise
1142 * @param {Function} fn
1143 * @return {Promise|undefined}
1144 * @api private
1145 */
1146
1147 _chainOrCall(promise, fn) {
1148 // thenable
1149 if (promise && promise.then && typeof promise.then === 'function') {
1150 // already have a promise, chain callback
1151 return promise.then(() => fn());
1152 }
1153 // callback might return a promise
1154 return fn();
1155 }
1156
1157 /**
1158 *
1159 * @param {Promise|undefined} promise
1160 * @param {string} event
1161 * @return {Promise|undefined}
1162 * @api private
1163 */
1164
1165 _chainOrCallHooks(promise, event) {
1166 let result = promise;
1167 const hooks = [];
1168 getCommandAndParents(this)
1169 .reverse()
1170 .filter(cmd => cmd._lifeCycleHooks[event] !== undefined)
1171 .forEach(hookedCommand => {
1172 hookedCommand._lifeCycleHooks[event].forEach((callback) => {
1173 hooks.push({ hookedCommand, callback });
1174 });
1175 });
1176 if (event === 'postAction') {
1177 hooks.reverse();
1178 }
1179
1180 hooks.forEach((hookDetail) => {
1181 result = this._chainOrCall(result, () => {
1182 return hookDetail.callback(hookDetail.hookedCommand, this);
1183 });
1184 });
1185 return result;
1186 }
1187
1188 /**
1189 * Process arguments in context of this command.
1190 * Returns action result, in case it is a promise.
1191 *
1192 * @api private
1193 */
1194
1195 _parseCommand(operands, unknown) {
1196 const parsed = this.parseOptions(unknown);
1197 this._parseOptionsEnv(); // after cli, so parseArg not called on both cli and env
1198 this._parseOptionsImplied();
1199 operands = operands.concat(parsed.operands);
1200 unknown = parsed.unknown;
1201 this.args = operands.concat(unknown);
1202
1203 if (operands && this._findCommand(operands[0])) {
1204 return this._dispatchSubcommand(operands[0], operands.slice(1), unknown);
1205 }
1206 if (this._hasImplicitHelpCommand() && operands[0] === this._helpCommandName) {
1207 if (operands.length === 1) {
1208 this.help();
1209 }
1210 return this._dispatchSubcommand(operands[1], [], [this._helpLongFlag]);
1211 }
1212 if (this._defaultCommandName) {
1213 outputHelpIfRequested(this, unknown); // Run the help for default command from parent rather than passing to default command
1214 return this._dispatchSubcommand(this._defaultCommandName, operands, unknown);
1215 }
1216 if (this.commands.length && this.args.length === 0 && !this._actionHandler && !this._defaultCommandName) {
1217 // probably missing subcommand and no handler, user needs help (and exit)
1218 this.help({ error: true });
1219 }
1220
1221 outputHelpIfRequested(this, parsed.unknown);
1222 this._checkForMissingMandatoryOptions();
1223 this._checkForConflictingOptions();
1224
1225 // We do not always call this check to avoid masking a "better" error, like unknown command.
1226 const checkForUnknownOptions = () => {
1227 if (parsed.unknown.length > 0) {
1228 this.unknownOption(parsed.unknown[0]);
1229 }
1230 };
1231
1232 const commandEvent = `command:${this.name()}`;
1233 if (this._actionHandler) {
1234 checkForUnknownOptions();
1235 this._processArguments();
1236
1237 let actionResult;
1238 actionResult = this._chainOrCallHooks(actionResult, 'preAction');
1239 actionResult = this._chainOrCall(actionResult, () => this._actionHandler(this.processedArgs));
1240 if (this.parent) {
1241 actionResult = this._chainOrCall(actionResult, () => {
1242 this.parent.emit(commandEvent, operands, unknown); // legacy
1243 });
1244 }
1245 actionResult = this._chainOrCallHooks(actionResult, 'postAction');
1246 return actionResult;
1247 }
1248 if (this.parent && this.parent.listenerCount(commandEvent)) {
1249 checkForUnknownOptions();
1250 this._processArguments();
1251 this.parent.emit(commandEvent, operands, unknown); // legacy
1252 } else if (operands.length) {
1253 if (this._findCommand('*')) { // legacy default command
1254 return this._dispatchSubcommand('*', operands, unknown);
1255 }
1256 if (this.listenerCount('command:*')) {
1257 // skip option check, emit event for possible misspelling suggestion
1258 this.emit('command:*', operands, unknown);
1259 } else if (this.commands.length) {
1260 this.unknownCommand();
1261 } else {
1262 checkForUnknownOptions();
1263 this._processArguments();
1264 }
1265 } else if (this.commands.length) {
1266 checkForUnknownOptions();
1267 // This command has subcommands and nothing hooked up at this level, so display help (and exit).
1268 this.help({ error: true });
1269 } else {
1270 checkForUnknownOptions();
1271 this._processArguments();
1272 // fall through for caller to handle after calling .parse()
1273 }
1274 }
1275
1276 /**
1277 * Find matching command.
1278 *
1279 * @api private
1280 */
1281 _findCommand(name) {
1282 if (!name) return undefined;
1283 return this.commands.find(cmd => cmd._name === name || cmd._aliases.includes(name));
1284 }
1285
1286 /**
1287 * Return an option matching `arg` if any.
1288 *
1289 * @param {string} arg
1290 * @return {Option}
1291 * @api private
1292 */
1293
1294 _findOption(arg) {
1295 return this.options.find(option => option.is(arg));
1296 }
1297
1298 /**
1299 * Display an error message if a mandatory option does not have a value.
1300 * Called after checking for help flags in leaf subcommand.
1301 *
1302 * @api private
1303 */
1304
1305 _checkForMissingMandatoryOptions() {
1306 // Walk up hierarchy so can call in subcommand after checking for displaying help.
1307 for (let cmd = this; cmd; cmd = cmd.parent) {
1308 cmd.options.forEach((anOption) => {
1309 if (anOption.mandatory && (cmd.getOptionValue(anOption.attributeName()) === undefined)) {
1310 cmd.missingMandatoryOptionValue(anOption);
1311 }
1312 });
1313 }
1314 }
1315
1316 /**
1317 * Display an error message if conflicting options are used together in this.
1318 *
1319 * @api private
1320 */
1321 _checkForConflictingLocalOptions() {
1322 const definedNonDefaultOptions = this.options.filter(
1323 (option) => {
1324 const optionKey = option.attributeName();
1325 if (this.getOptionValue(optionKey) === undefined) {
1326 return false;
1327 }
1328 return this.getOptionValueSource(optionKey) !== 'default';
1329 }
1330 );
1331
1332 const optionsWithConflicting = definedNonDefaultOptions.filter(
1333 (option) => option.conflictsWith.length > 0
1334 );
1335
1336 optionsWithConflicting.forEach((option) => {
1337 const conflictingAndDefined = definedNonDefaultOptions.find((defined) =>
1338 option.conflictsWith.includes(defined.attributeName())
1339 );
1340 if (conflictingAndDefined) {
1341 this._conflictingOption(option, conflictingAndDefined);
1342 }
1343 });
1344 }
1345
1346 /**
1347 * Display an error message if conflicting options are used together.
1348 * Called after checking for help flags in leaf subcommand.
1349 *
1350 * @api private
1351 */
1352 _checkForConflictingOptions() {
1353 // Walk up hierarchy so can call in subcommand after checking for displaying help.
1354 for (let cmd = this; cmd; cmd = cmd.parent) {
1355 cmd._checkForConflictingLocalOptions();
1356 }
1357 }
1358
1359 /**
1360 * Parse options from `argv` removing known options,
1361 * and return argv split into operands and unknown arguments.
1362 *
1363 * Examples:
1364 *
1365 * argv => operands, unknown
1366 * --known kkk op => [op], []
1367 * op --known kkk => [op], []
1368 * sub --unknown uuu op => [sub], [--unknown uuu op]
1369 * sub -- --unknown uuu op => [sub --unknown uuu op], []
1370 *
1371 * @param {String[]} argv
1372 * @return {{operands: String[], unknown: String[]}}
1373 */
1374
1375 parseOptions(argv) {
1376 const operands = []; // operands, not options or values
1377 const unknown = []; // first unknown option and remaining unknown args
1378 let dest = operands;
1379 const args = argv.slice();
1380
1381 function maybeOption(arg) {
1382 return arg.length > 1 && arg[0] === '-';
1383 }
1384
1385 // parse options
1386 let activeVariadicOption = null;
1387 while (args.length) {
1388 const arg = args.shift();
1389
1390 // literal
1391 if (arg === '--') {
1392 if (dest === unknown) dest.push(arg);
1393 dest.push(...args);
1394 break;
1395 }
1396
1397 if (activeVariadicOption && !maybeOption(arg)) {
1398 this.emit(`option:${activeVariadicOption.name()}`, arg);
1399 continue;
1400 }
1401 activeVariadicOption = null;
1402
1403 if (maybeOption(arg)) {
1404 const option = this._findOption(arg);
1405 // recognised option, call listener to assign value with possible custom processing
1406 if (option) {
1407 if (option.required) {
1408 const value = args.shift();
1409 if (value === undefined) this.optionMissingArgument(option);
1410 this.emit(`option:${option.name()}`, value);
1411 } else if (option.optional) {
1412 let value = null;
1413 // historical behaviour is optional value is following arg unless an option
1414 if (args.length > 0 && !maybeOption(args[0])) {
1415 value = args.shift();
1416 }
1417 this.emit(`option:${option.name()}`, value);
1418 } else { // boolean flag
1419 this.emit(`option:${option.name()}`);
1420 }
1421 activeVariadicOption = option.variadic ? option : null;
1422 continue;
1423 }
1424 }
1425
1426 // Look for combo options following single dash, eat first one if known.
1427 if (arg.length > 2 && arg[0] === '-' && arg[1] !== '-') {
1428 const option = this._findOption(`-${arg[1]}`);
1429 if (option) {
1430 if (option.required || (option.optional && this._combineFlagAndOptionalValue)) {
1431 // option with value following in same argument
1432 this.emit(`option:${option.name()}`, arg.slice(2));
1433 } else {
1434 // boolean option, emit and put back remainder of arg for further processing
1435 this.emit(`option:${option.name()}`);
1436 args.unshift(`-${arg.slice(2)}`);
1437 }
1438 continue;
1439 }
1440 }
1441
1442 // Look for known long flag with value, like --foo=bar
1443 if (/^--[^=]+=/.test(arg)) {
1444 const index = arg.indexOf('=');
1445 const option = this._findOption(arg.slice(0, index));
1446 if (option && (option.required || option.optional)) {
1447 this.emit(`option:${option.name()}`, arg.slice(index + 1));
1448 continue;
1449 }
1450 }
1451
1452 // Not a recognised option by this command.
1453 // Might be a command-argument, or subcommand option, or unknown option, or help command or option.
1454
1455 // An unknown option means further arguments also classified as unknown so can be reprocessed by subcommands.
1456 if (maybeOption(arg)) {
1457 dest = unknown;
1458 }
1459
1460 // If using positionalOptions, stop processing our options at subcommand.
1461 if ((this._enablePositionalOptions || this._passThroughOptions) && operands.length === 0 && unknown.length === 0) {
1462 if (this._findCommand(arg)) {
1463 operands.push(arg);
1464 if (args.length > 0) unknown.push(...args);
1465 break;
1466 } else if (arg === this._helpCommandName && this._hasImplicitHelpCommand()) {
1467 operands.push(arg);
1468 if (args.length > 0) operands.push(...args);
1469 break;
1470 } else if (this._defaultCommandName) {
1471 unknown.push(arg);
1472 if (args.length > 0) unknown.push(...args);
1473 break;
1474 }
1475 }
1476
1477 // If using passThroughOptions, stop processing options at first command-argument.
1478 if (this._passThroughOptions) {
1479 dest.push(arg);
1480 if (args.length > 0) dest.push(...args);
1481 break;
1482 }
1483
1484 // add arg
1485 dest.push(arg);
1486 }
1487
1488 return { operands, unknown };
1489 }
1490
1491 /**
1492 * Return an object containing local option values as key-value pairs.
1493 *
1494 * @return {Object}
1495 */
1496 opts() {
1497 if (this._storeOptionsAsProperties) {
1498 // Preserve original behaviour so backwards compatible when still using properties
1499 const result = {};
1500 const len = this.options.length;
1501
1502 for (let i = 0; i < len; i++) {
1503 const key = this.options[i].attributeName();
1504 result[key] = key === this._versionOptionName ? this._version : this[key];
1505 }
1506 return result;
1507 }
1508
1509 return this._optionValues;
1510 }
1511
1512 /**
1513 * Return an object containing merged local and global option values as key-value pairs.
1514 *
1515 * @return {Object}
1516 */
1517 optsWithGlobals() {
1518 // globals overwrite locals
1519 return getCommandAndParents(this).reduce(
1520 (combinedOptions, cmd) => Object.assign(combinedOptions, cmd.opts()),
1521 {}
1522 );
1523 }
1524
1525 /**
1526 * Display error message and exit (or call exitOverride).
1527 *
1528 * @param {string} message
1529 * @param {Object} [errorOptions]
1530 * @param {string} [errorOptions.code] - an id string representing the error
1531 * @param {number} [errorOptions.exitCode] - used with process.exit
1532 */
1533 error(message, errorOptions) {
1534 // output handling
1535 this._outputConfiguration.outputError(`${message}\n`, this._outputConfiguration.writeErr);
1536 if (typeof this._showHelpAfterError === 'string') {
1537 this._outputConfiguration.writeErr(`${this._showHelpAfterError}\n`);
1538 } else if (this._showHelpAfterError) {
1539 this._outputConfiguration.writeErr('\n');
1540 this.outputHelp({ error: true });
1541 }
1542
1543 // exit handling
1544 const config = errorOptions || {};
1545 const exitCode = config.exitCode || 1;
1546 const code = config.code || 'commander.error';
1547 this._exit(exitCode, code, message);
1548 }
1549
1550 /**
1551 * Apply any option related environment variables, if option does
1552 * not have a value from cli or client code.
1553 *
1554 * @api private
1555 */
1556 _parseOptionsEnv() {
1557 this.options.forEach((option) => {
1558 if (option.envVar && option.envVar in process.env) {
1559 const optionKey = option.attributeName();
1560 // Priority check. Do not overwrite cli or options from unknown source (client-code).
1561 if (this.getOptionValue(optionKey) === undefined || ['default', 'config', 'env'].includes(this.getOptionValueSource(optionKey))) {
1562 if (option.required || option.optional) { // option can take a value
1563 // keep very simple, optional always takes value
1564 this.emit(`optionEnv:${option.name()}`, process.env[option.envVar]);
1565 } else { // boolean
1566 // keep very simple, only care that envVar defined and not the value
1567 this.emit(`optionEnv:${option.name()}`);
1568 }
1569 }
1570 }
1571 });
1572 }
1573
1574 /**
1575 * Apply any implied option values, if option is undefined or default value.
1576 *
1577 * @api private
1578 */
1579 _parseOptionsImplied() {
1580 const dualHelper = new DualOptions(this.options);
1581 const hasCustomOptionValue = (optionKey) => {
1582 return this.getOptionValue(optionKey) !== undefined && !['default', 'implied'].includes(this.getOptionValueSource(optionKey));
1583 };
1584 this.options
1585 .filter(option => (option.implied !== undefined) &&
1586 hasCustomOptionValue(option.attributeName()) &&
1587 dualHelper.valueFromOption(this.getOptionValue(option.attributeName()), option))
1588 .forEach((option) => {
1589 Object.keys(option.implied)
1590 .filter(impliedKey => !hasCustomOptionValue(impliedKey))
1591 .forEach(impliedKey => {
1592 this.setOptionValueWithSource(impliedKey, option.implied[impliedKey], 'implied');
1593 });
1594 });
1595 }
1596
1597 /**
1598 * Argument `name` is missing.
1599 *
1600 * @param {string} name
1601 * @api private
1602 */
1603
1604 missingArgument(name) {
1605 const message = `error: missing required argument '${name}'`;
1606 this.error(message, { code: 'commander.missingArgument' });
1607 }
1608
1609 /**
1610 * `Option` is missing an argument.
1611 *
1612 * @param {Option} option
1613 * @api private
1614 */
1615
1616 optionMissingArgument(option) {
1617 const message = `error: option '${option.flags}' argument missing`;
1618 this.error(message, { code: 'commander.optionMissingArgument' });
1619 }
1620
1621 /**
1622 * `Option` does not have a value, and is a mandatory option.
1623 *
1624 * @param {Option} option
1625 * @api private
1626 */
1627
1628 missingMandatoryOptionValue(option) {
1629 const message = `error: required option '${option.flags}' not specified`;
1630 this.error(message, { code: 'commander.missingMandatoryOptionValue' });
1631 }
1632
1633 /**
1634 * `Option` conflicts with another option.
1635 *
1636 * @param {Option} option
1637 * @param {Option} conflictingOption
1638 * @api private
1639 */
1640 _conflictingOption(option, conflictingOption) {
1641 // The calling code does not know whether a negated option is the source of the
1642 // value, so do some work to take an educated guess.
1643 const findBestOptionFromValue = (option) => {
1644 const optionKey = option.attributeName();
1645 const optionValue = this.getOptionValue(optionKey);
1646 const negativeOption = this.options.find(target => target.negate && optionKey === target.attributeName());
1647 const positiveOption = this.options.find(target => !target.negate && optionKey === target.attributeName());
1648 if (negativeOption && (
1649 (negativeOption.presetArg === undefined && optionValue === false) ||
1650 (negativeOption.presetArg !== undefined && optionValue === negativeOption.presetArg)
1651 )) {
1652 return negativeOption;
1653 }
1654 return positiveOption || option;
1655 };
1656
1657 const getErrorMessage = (option) => {
1658 const bestOption = findBestOptionFromValue(option);
1659 const optionKey = bestOption.attributeName();
1660 const source = this.getOptionValueSource(optionKey);
1661 if (source === 'env') {
1662 return `environment variable '${bestOption.envVar}'`;
1663 }
1664 return `option '${bestOption.flags}'`;
1665 };
1666
1667 const message = `error: ${getErrorMessage(option)} cannot be used with ${getErrorMessage(conflictingOption)}`;
1668 this.error(message, { code: 'commander.conflictingOption' });
1669 }
1670
1671 /**
1672 * Unknown option `flag`.
1673 *
1674 * @param {string} flag
1675 * @api private
1676 */
1677
1678 unknownOption(flag) {
1679 if (this._allowUnknownOption) return;
1680 let suggestion = '';
1681
1682 if (flag.startsWith('--') && this._showSuggestionAfterError) {
1683 // Looping to pick up the global options too
1684 let candidateFlags = [];
1685 let command = this;
1686 do {
1687 const moreFlags = command.createHelp().visibleOptions(command)
1688 .filter(option => option.long)
1689 .map(option => option.long);
1690 candidateFlags = candidateFlags.concat(moreFlags);
1691 command = command.parent;
1692 } while (command && !command._enablePositionalOptions);
1693 suggestion = suggestSimilar(flag, candidateFlags);
1694 }
1695
1696 const message = `error: unknown option '${flag}'${suggestion}`;
1697 this.error(message, { code: 'commander.unknownOption' });
1698 }
1699
1700 /**
1701 * Excess arguments, more than expected.
1702 *
1703 * @param {string[]} receivedArgs
1704 * @api private
1705 */
1706
1707 _excessArguments(receivedArgs) {
1708 if (this._allowExcessArguments) return;
1709
1710 const expected = this._args.length;
1711 const s = (expected === 1) ? '' : 's';
1712 const forSubcommand = this.parent ? ` for '${this.name()}'` : '';
1713 const message = `error: too many arguments${forSubcommand}. Expected ${expected} argument${s} but got ${receivedArgs.length}.`;
1714 this.error(message, { code: 'commander.excessArguments' });
1715 }
1716
1717 /**
1718 * Unknown command.
1719 *
1720 * @api private
1721 */
1722
1723 unknownCommand() {
1724 const unknownName = this.args[0];
1725 let suggestion = '';
1726
1727 if (this._showSuggestionAfterError) {
1728 const candidateNames = [];
1729 this.createHelp().visibleCommands(this).forEach((command) => {
1730 candidateNames.push(command.name());
1731 // just visible alias
1732 if (command.alias()) candidateNames.push(command.alias());
1733 });
1734 suggestion = suggestSimilar(unknownName, candidateNames);
1735 }
1736
1737 const message = `error: unknown command '${unknownName}'${suggestion}`;
1738 this.error(message, { code: 'commander.unknownCommand' });
1739 }
1740
1741 /**
1742 * Set the program version to `str`.
1743 *
1744 * This method auto-registers the "-V, --version" flag
1745 * which will print the version number when passed.
1746 *
1747 * You can optionally supply the flags and description to override the defaults.
1748 *
1749 * @param {string} str
1750 * @param {string} [flags]
1751 * @param {string} [description]
1752 * @return {this | string} `this` command for chaining, or version string if no arguments
1753 */
1754
1755 version(str, flags, description) {
1756 if (str === undefined) return this._version;
1757 this._version = str;
1758 flags = flags || '-V, --version';
1759 description = description || 'output the version number';
1760 const versionOption = this.createOption(flags, description);
1761 this._versionOptionName = versionOption.attributeName();
1762 this.options.push(versionOption);
1763 this.on('option:' + versionOption.name(), () => {
1764 this._outputConfiguration.writeOut(`${str}\n`);
1765 this._exit(0, 'commander.version', str);
1766 });
1767 return this;
1768 }
1769
1770 /**
1771 * Set the description.
1772 *
1773 * @param {string} [str]
1774 * @param {Object} [argsDescription]
1775 * @return {string|Command}
1776 */
1777 description(str, argsDescription) {
1778 if (str === undefined && argsDescription === undefined) return this._description;
1779 this._description = str;
1780 if (argsDescription) {
1781 this._argsDescription = argsDescription;
1782 }
1783 return this;
1784 }
1785
1786 /**
1787 * Set the summary. Used when listed as subcommand of parent.
1788 *
1789 * @param {string} [str]
1790 * @return {string|Command}
1791 */
1792 summary(str) {
1793 if (str === undefined) return this._summary;
1794 this._summary = str;
1795 return this;
1796 }
1797
1798 /**
1799 * Set an alias for the command.
1800 *
1801 * You may call more than once to add multiple aliases. Only the first alias is shown in the auto-generated help.
1802 *
1803 * @param {string} [alias]
1804 * @return {string|Command}
1805 */
1806
1807 alias(alias) {
1808 if (alias === undefined) return this._aliases[0]; // just return first, for backwards compatibility
1809
1810 /** @type {Command} */
1811 let command = this;
1812 if (this.commands.length !== 0 && this.commands[this.commands.length - 1]._executableHandler) {
1813 // assume adding alias for last added executable subcommand, rather than this
1814 command = this.commands[this.commands.length - 1];
1815 }
1816
1817 if (alias === command._name) throw new Error('Command alias can\'t be the same as its name');
1818
1819 command._aliases.push(alias);
1820 return this;
1821 }
1822
1823 /**
1824 * Set aliases for the command.
1825 *
1826 * Only the first alias is shown in the auto-generated help.
1827 *
1828 * @param {string[]} [aliases]
1829 * @return {string[]|Command}
1830 */
1831
1832 aliases(aliases) {
1833 // Getter for the array of aliases is the main reason for having aliases() in addition to alias().
1834 if (aliases === undefined) return this._aliases;
1835
1836 aliases.forEach((alias) => this.alias(alias));
1837 return this;
1838 }
1839
1840 /**
1841 * Set / get the command usage `str`.
1842 *
1843 * @param {string} [str]
1844 * @return {String|Command}
1845 */
1846
1847 usage(str) {
1848 if (str === undefined) {
1849 if (this._usage) return this._usage;
1850
1851 const args = this._args.map((arg) => {
1852 return humanReadableArgName(arg);
1853 });
1854 return [].concat(
1855 (this.options.length || this._hasHelpOption ? '[options]' : []),
1856 (this.commands.length ? '[command]' : []),
1857 (this._args.length ? args : [])
1858 ).join(' ');
1859 }
1860
1861 this._usage = str;
1862 return this;
1863 }
1864
1865 /**
1866 * Get or set the name of the command.
1867 *
1868 * @param {string} [str]
1869 * @return {string|Command}
1870 */
1871
1872 name(str) {
1873 if (str === undefined) return this._name;
1874 this._name = str;
1875 return this;
1876 }
1877
1878 /**
1879 * Set the name of the command from script filename, such as process.argv[1],
1880 * or require.main.filename, or __filename.
1881 *
1882 * (Used internally and public although not documented in README.)
1883 *
1884 * @example
1885 * program.nameFromFilename(require.main.filename);
1886 *
1887 * @param {string} filename
1888 * @return {Command}
1889 */
1890
1891 nameFromFilename(filename) {
1892 this._name = path.basename(filename, path.extname(filename));
1893
1894 return this;
1895 }
1896
1897 /**
1898 * Get or set the directory for searching for executable subcommands of this command.
1899 *
1900 * @example
1901 * program.executableDir(__dirname);
1902 * // or
1903 * program.executableDir('subcommands');
1904 *
1905 * @param {string} [path]
1906 * @return {string|Command}
1907 */
1908
1909 executableDir(path) {
1910 if (path === undefined) return this._executableDir;
1911 this._executableDir = path;
1912 return this;
1913 }
1914
1915 /**
1916 * Return program help documentation.
1917 *
1918 * @param {{ error: boolean }} [contextOptions] - pass {error:true} to wrap for stderr instead of stdout
1919 * @return {string}
1920 */
1921
1922 helpInformation(contextOptions) {
1923 const helper = this.createHelp();
1924 if (helper.helpWidth === undefined) {
1925 helper.helpWidth = (contextOptions && contextOptions.error) ? this._outputConfiguration.getErrHelpWidth() : this._outputConfiguration.getOutHelpWidth();
1926 }
1927 return helper.formatHelp(this, helper);
1928 }
1929
1930 /**
1931 * @api private
1932 */
1933
1934 _getHelpContext(contextOptions) {
1935 contextOptions = contextOptions || {};
1936 const context = { error: !!contextOptions.error };
1937 let write;
1938 if (context.error) {
1939 write = (arg) => this._outputConfiguration.writeErr(arg);
1940 } else {
1941 write = (arg) => this._outputConfiguration.writeOut(arg);
1942 }
1943 context.write = contextOptions.write || write;
1944 context.command = this;
1945 return context;
1946 }
1947
1948 /**
1949 * Output help information for this command.
1950 *
1951 * Outputs built-in help, and custom text added using `.addHelpText()`.
1952 *
1953 * @param {{ error: boolean } | Function} [contextOptions] - pass {error:true} to write to stderr instead of stdout
1954 */
1955
1956 outputHelp(contextOptions) {
1957 let deprecatedCallback;
1958 if (typeof contextOptions === 'function') {
1959 deprecatedCallback = contextOptions;
1960 contextOptions = undefined;
1961 }
1962 const context = this._getHelpContext(contextOptions);
1963
1964 getCommandAndParents(this).reverse().forEach(command => command.emit('beforeAllHelp', context));
1965 this.emit('beforeHelp', context);
1966
1967 let helpInformation = this.helpInformation(context);
1968 if (deprecatedCallback) {
1969 helpInformation = deprecatedCallback(helpInformation);
1970 if (typeof helpInformation !== 'string' && !Buffer.isBuffer(helpInformation)) {
1971 throw new Error('outputHelp callback must return a string or a Buffer');
1972 }
1973 }
1974 context.write(helpInformation);
1975
1976 this.emit(this._helpLongFlag); // deprecated
1977 this.emit('afterHelp', context);
1978 getCommandAndParents(this).forEach(command => command.emit('afterAllHelp', context));
1979 }
1980
1981 /**
1982 * You can pass in flags and a description to override the help
1983 * flags and help description for your command. Pass in false to
1984 * disable the built-in help option.
1985 *
1986 * @param {string | boolean} [flags]
1987 * @param {string} [description]
1988 * @return {Command} `this` command for chaining
1989 */
1990
1991 helpOption(flags, description) {
1992 if (typeof flags === 'boolean') {
1993 this._hasHelpOption = flags;
1994 return this;
1995 }
1996 this._helpFlags = flags || this._helpFlags;
1997 this._helpDescription = description || this._helpDescription;
1998
1999 const helpFlags = splitOptionFlags(this._helpFlags);
2000 this._helpShortFlag = helpFlags.shortFlag;
2001 this._helpLongFlag = helpFlags.longFlag;
2002
2003 return this;
2004 }
2005
2006 /**
2007 * Output help information and exit.
2008 *
2009 * Outputs built-in help, and custom text added using `.addHelpText()`.
2010 *
2011 * @param {{ error: boolean }} [contextOptions] - pass {error:true} to write to stderr instead of stdout
2012 */
2013
2014 help(contextOptions) {
2015 this.outputHelp(contextOptions);
2016 let exitCode = process.exitCode || 0;
2017 if (exitCode === 0 && contextOptions && typeof contextOptions !== 'function' && contextOptions.error) {
2018 exitCode = 1;
2019 }
2020 // message: do not have all displayed text available so only passing placeholder.
2021 this._exit(exitCode, 'commander.help', '(outputHelp)');
2022 }
2023
2024 /**
2025 * Add additional text to be displayed with the built-in help.
2026 *
2027 * Position is 'before' or 'after' to affect just this command,
2028 * and 'beforeAll' or 'afterAll' to affect this command and all its subcommands.
2029 *
2030 * @param {string} position - before or after built-in help
2031 * @param {string | Function} text - string to add, or a function returning a string
2032 * @return {Command} `this` command for chaining
2033 */
2034 addHelpText(position, text) {
2035 const allowedValues = ['beforeAll', 'before', 'after', 'afterAll'];
2036 if (!allowedValues.includes(position)) {
2037 throw new Error(`Unexpected value for position to addHelpText.
2038Expecting one of '${allowedValues.join("', '")}'`);
2039 }
2040 const helpEvent = `${position}Help`;
2041 this.on(helpEvent, (context) => {
2042 let helpStr;
2043 if (typeof text === 'function') {
2044 helpStr = text({ error: context.error, command: context.command });
2045 } else {
2046 helpStr = text;
2047 }
2048 // Ignore falsy value when nothing to output.
2049 if (helpStr) {
2050 context.write(`${helpStr}\n`);
2051 }
2052 });
2053 return this;
2054 }
2055}
2056
2057/**
2058 * Output help information if help flags specified
2059 *
2060 * @param {Command} cmd - command to output help for
2061 * @param {Array} args - array of options to search for help flags
2062 * @api private
2063 */
2064
2065function outputHelpIfRequested(cmd, args) {
2066 const helpOption = cmd._hasHelpOption && args.find(arg => arg === cmd._helpLongFlag || arg === cmd._helpShortFlag);
2067 if (helpOption) {
2068 cmd.outputHelp();
2069 // (Do not have all displayed text available so only passing placeholder.)
2070 cmd._exit(0, 'commander.helpDisplayed', '(outputHelp)');
2071 }
2072}
2073
2074/**
2075 * Scan arguments and increment port number for inspect calls (to avoid conflicts when spawning new command).
2076 *
2077 * @param {string[]} args - array of arguments from node.execArgv
2078 * @returns {string[]}
2079 * @api private
2080 */
2081
2082function incrementNodeInspectorPort(args) {
2083 // Testing for these options:
2084 // --inspect[=[host:]port]
2085 // --inspect-brk[=[host:]port]
2086 // --inspect-port=[host:]port
2087 return args.map((arg) => {
2088 if (!arg.startsWith('--inspect')) {
2089 return arg;
2090 }
2091 let debugOption;
2092 let debugHost = '127.0.0.1';
2093 let debugPort = '9229';
2094 let match;
2095 if ((match = arg.match(/^(--inspect(-brk)?)$/)) !== null) {
2096 // e.g. --inspect
2097 debugOption = match[1];
2098 } else if ((match = arg.match(/^(--inspect(-brk|-port)?)=([^:]+)$/)) !== null) {
2099 debugOption = match[1];
2100 if (/^\d+$/.test(match[3])) {
2101 // e.g. --inspect=1234
2102 debugPort = match[3];
2103 } else {
2104 // e.g. --inspect=localhost
2105 debugHost = match[3];
2106 }
2107 } else if ((match = arg.match(/^(--inspect(-brk|-port)?)=([^:]+):(\d+)$/)) !== null) {
2108 // e.g. --inspect=localhost:1234
2109 debugOption = match[1];
2110 debugHost = match[3];
2111 debugPort = match[4];
2112 }
2113
2114 if (debugOption && debugPort !== '0') {
2115 return `${debugOption}=${debugHost}:${parseInt(debugPort) + 1}`;
2116 }
2117 return arg;
2118 });
2119}
2120
2121/**
2122 * @param {Command} startCommand
2123 * @returns {Command[]}
2124 * @api private
2125 */
2126
2127function getCommandAndParents(startCommand) {
2128 const result = [];
2129 for (let command = startCommand; command; command = command.parent) {
2130 result.push(command);
2131 }
2132 return result;
2133}
2134
2135exports.Command = Command;