UNPKG

55.3 kBJavaScriptView Raw
1/**
2 * Module dependencies.
3 */
4
5const EventEmitter = require('events').EventEmitter;
6const spawn = require('child_process').spawn;
7const path = require('path');
8const fs = require('fs');
9
10// @ts-check
11
12class Option {
13 /**
14 * Initialize a new `Option` with the given `flags` and `description`.
15 *
16 * @param {string} flags
17 * @param {string} description
18 * @api public
19 */
20
21 constructor(flags, description) {
22 this.flags = flags;
23 this.required = flags.includes('<'); // A value must be supplied when the option is specified.
24 this.optional = flags.includes('['); // A value is optional when the option is specified.
25 // variadic test ignores <value,...> et al which might be used to describe custom splitting of single argument
26 this.variadic = /\w\.\.\.[>\]]$/.test(flags); // The option can take multiple values.
27 this.mandatory = false; // The option must have a value after parsing, which usually means it must be specified on command line.
28 const optionFlags = _parseOptionFlags(flags);
29 this.short = optionFlags.shortFlag;
30 this.long = optionFlags.longFlag;
31 this.negate = false;
32 if (this.long) {
33 this.negate = this.long.startsWith('--no-');
34 }
35 this.description = description || '';
36 this.defaultValue = undefined;
37 }
38
39 /**
40 * Return option name.
41 *
42 * @return {string}
43 * @api private
44 */
45
46 name() {
47 if (this.long) {
48 return this.long.replace(/^--/, '');
49 }
50 return this.short.replace(/^-/, '');
51 };
52
53 /**
54 * Return option name, in a camelcase format that can be used
55 * as a object attribute key.
56 *
57 * @return {string}
58 * @api private
59 */
60
61 attributeName() {
62 return camelcase(this.name().replace(/^no-/, ''));
63 };
64
65 /**
66 * Check if `arg` matches the short or long flag.
67 *
68 * @param {string} arg
69 * @return {boolean}
70 * @api private
71 */
72
73 is(arg) {
74 return this.short === arg || this.long === arg;
75 };
76}
77
78/**
79 * CommanderError class
80 * @class
81 */
82class CommanderError extends Error {
83 /**
84 * Constructs the CommanderError class
85 * @param {number} exitCode suggested exit code which could be used with process.exit
86 * @param {string} code an id string representing the error
87 * @param {string} message human-readable description of the error
88 * @constructor
89 */
90 constructor(exitCode, code, message) {
91 super(message);
92 // properly capture stack trace in Node.js
93 Error.captureStackTrace(this, this.constructor);
94 this.name = this.constructor.name;
95 this.code = code;
96 this.exitCode = exitCode;
97 this.nestedError = undefined;
98 }
99}
100
101class Command extends EventEmitter {
102 /**
103 * Initialize a new `Command`.
104 *
105 * @param {string} [name]
106 * @api public
107 */
108
109 constructor(name) {
110 super();
111 this.commands = [];
112 this.options = [];
113 this.parent = null;
114 this._allowUnknownOption = false;
115 this._args = [];
116 this.rawArgs = null;
117 this._scriptPath = null;
118 this._name = name || '';
119 this._optionValues = {};
120 this._storeOptionsAsProperties = true; // backwards compatible by default
121 this._storeOptionsAsPropertiesCalled = false;
122 this._passCommandToAction = true; // backwards compatible by default
123 this._actionResults = [];
124 this._actionHandler = null;
125 this._executableHandler = false;
126 this._executableFile = null; // custom name for executable
127 this._defaultCommandName = null;
128 this._exitCallback = null;
129 this._aliases = [];
130 this._combineFlagAndOptionalValue = true;
131
132 this._hidden = false;
133 this._hasHelpOption = true;
134 this._helpFlags = '-h, --help';
135 this._helpDescription = 'display help for command';
136 this._helpShortFlag = '-h';
137 this._helpLongFlag = '--help';
138 this._hasImplicitHelpCommand = undefined; // Deliberately undefined, not decided whether true or false
139 this._helpCommandName = 'help';
140 this._helpCommandnameAndArgs = 'help [command]';
141 this._helpCommandDescription = 'display help for command';
142 }
143
144 /**
145 * Define a command.
146 *
147 * There are two styles of command: pay attention to where to put the description.
148 *
149 * Examples:
150 *
151 * // Command implemented using action handler (description is supplied separately to `.command`)
152 * program
153 * .command('clone <source> [destination]')
154 * .description('clone a repository into a newly created directory')
155 * .action((source, destination) => {
156 * console.log('clone command called');
157 * });
158 *
159 * // Command implemented using separate executable file (description is second parameter to `.command`)
160 * program
161 * .command('start <service>', 'start named service')
162 * .command('stop [service]', 'stop named service, or all if no name supplied');
163 *
164 * @param {string} nameAndArgs - command name and arguments, args are `<required>` or `[optional]` and last may also be `variadic...`
165 * @param {Object|string} [actionOptsOrExecDesc] - configuration options (for action), or description (for executable)
166 * @param {Object} [execOpts] - configuration options (for executable)
167 * @return {Command} returns new command for action handler, or `this` for executable command
168 * @api public
169 */
170
171 command(nameAndArgs, actionOptsOrExecDesc, execOpts) {
172 let desc = actionOptsOrExecDesc;
173 let opts = execOpts;
174 if (typeof desc === 'object' && desc !== null) {
175 opts = desc;
176 desc = null;
177 }
178 opts = opts || {};
179 const args = nameAndArgs.split(/ +/);
180 const cmd = this.createCommand(args.shift());
181
182 if (desc) {
183 cmd.description(desc);
184 cmd._executableHandler = true;
185 }
186 if (opts.isDefault) this._defaultCommandName = cmd._name;
187
188 cmd._hidden = !!(opts.noHelp || opts.hidden);
189 cmd._hasHelpOption = this._hasHelpOption;
190 cmd._helpFlags = this._helpFlags;
191 cmd._helpDescription = this._helpDescription;
192 cmd._helpShortFlag = this._helpShortFlag;
193 cmd._helpLongFlag = this._helpLongFlag;
194 cmd._helpCommandName = this._helpCommandName;
195 cmd._helpCommandnameAndArgs = this._helpCommandnameAndArgs;
196 cmd._helpCommandDescription = this._helpCommandDescription;
197 cmd._exitCallback = this._exitCallback;
198 cmd._storeOptionsAsProperties = this._storeOptionsAsProperties;
199 cmd._passCommandToAction = this._passCommandToAction;
200 cmd._combineFlagAndOptionalValue = this._combineFlagAndOptionalValue;
201
202 cmd._executableFile = opts.executableFile || null; // Custom name for executable file, set missing to null to match constructor
203 this.commands.push(cmd);
204 cmd._parseExpectedArgs(args);
205 cmd.parent = this;
206
207 if (desc) return this;
208 return cmd;
209 };
210
211 /**
212 * Factory routine to create a new unattached command.
213 *
214 * See .command() for creating an attached subcommand, which uses this routine to
215 * create the command. You can override createCommand to customise subcommands.
216 *
217 * @param {string} [name]
218 * @return {Command} new command
219 * @api public
220 */
221
222 createCommand(name) {
223 return new Command(name);
224 };
225
226 /**
227 * Add a prepared subcommand.
228 *
229 * See .command() for creating an attached subcommand which inherits settings from its parent.
230 *
231 * @param {Command} cmd - new subcommand
232 * @param {Object} [opts] - configuration options
233 * @return {Command} `this` command for chaining
234 * @api public
235 */
236
237 addCommand(cmd, opts) {
238 if (!cmd._name) throw new Error('Command passed to .addCommand() must have a name');
239
240 // To keep things simple, block automatic name generation for deeply nested executables.
241 // Fail fast and detect when adding rather than later when parsing.
242 function checkExplicitNames(commandArray) {
243 commandArray.forEach((cmd) => {
244 if (cmd._executableHandler && !cmd._executableFile) {
245 throw new Error(`Must specify executableFile for deeply nested executable: ${cmd.name()}`);
246 }
247 checkExplicitNames(cmd.commands);
248 });
249 }
250 checkExplicitNames(cmd.commands);
251
252 opts = opts || {};
253 if (opts.isDefault) this._defaultCommandName = cmd._name;
254 if (opts.noHelp || opts.hidden) cmd._hidden = true; // modifying passed command due to existing implementation
255
256 this.commands.push(cmd);
257 cmd.parent = this;
258 return this;
259 };
260
261 /**
262 * Define argument syntax for the command.
263 *
264 * @api public
265 */
266
267 arguments(desc) {
268 return this._parseExpectedArgs(desc.split(/ +/));
269 };
270
271 /**
272 * Override default decision whether to add implicit help command.
273 *
274 * addHelpCommand() // force on
275 * addHelpCommand(false); // force off
276 * addHelpCommand('help [cmd]', 'display help for [cmd]'); // force on with custom details
277 *
278 * @return {Command} `this` command for chaining
279 * @api public
280 */
281
282 addHelpCommand(enableOrNameAndArgs, description) {
283 if (enableOrNameAndArgs === false) {
284 this._hasImplicitHelpCommand = false;
285 } else {
286 this._hasImplicitHelpCommand = true;
287 if (typeof enableOrNameAndArgs === 'string') {
288 this._helpCommandName = enableOrNameAndArgs.split(' ')[0];
289 this._helpCommandnameAndArgs = enableOrNameAndArgs;
290 }
291 this._helpCommandDescription = description || this._helpCommandDescription;
292 }
293 return this;
294 };
295
296 /**
297 * @return {boolean}
298 * @api private
299 */
300
301 _lazyHasImplicitHelpCommand() {
302 if (this._hasImplicitHelpCommand === undefined) {
303 this._hasImplicitHelpCommand = this.commands.length && !this._actionHandler && !this._findCommand('help');
304 }
305 return this._hasImplicitHelpCommand;
306 };
307
308 /**
309 * Parse expected `args`.
310 *
311 * For example `["[type]"]` becomes `[{ required: false, name: 'type' }]`.
312 *
313 * @param {Array} args
314 * @return {Command} `this` command for chaining
315 * @api private
316 */
317
318 _parseExpectedArgs(args) {
319 if (!args.length) return;
320 args.forEach((arg) => {
321 const argDetails = {
322 required: false,
323 name: '',
324 variadic: false
325 };
326
327 switch (arg[0]) {
328 case '<':
329 argDetails.required = true;
330 argDetails.name = arg.slice(1, -1);
331 break;
332 case '[':
333 argDetails.name = arg.slice(1, -1);
334 break;
335 }
336
337 if (argDetails.name.length > 3 && argDetails.name.slice(-3) === '...') {
338 argDetails.variadic = true;
339 argDetails.name = argDetails.name.slice(0, -3);
340 }
341 if (argDetails.name) {
342 this._args.push(argDetails);
343 }
344 });
345 this._args.forEach((arg, i) => {
346 if (arg.variadic && i < this._args.length - 1) {
347 throw new Error(`only the last argument can be variadic '${arg.name}'`);
348 }
349 });
350 return this;
351 };
352
353 /**
354 * Register callback to use as replacement for calling process.exit.
355 *
356 * @param {Function} [fn] optional callback which will be passed a CommanderError, defaults to throwing
357 * @return {Command} `this` command for chaining
358 * @api public
359 */
360
361 exitOverride(fn) {
362 if (fn) {
363 this._exitCallback = fn;
364 } else {
365 this._exitCallback = (err) => {
366 if (err.code !== 'commander.executeSubCommandAsync') {
367 throw err;
368 } else {
369 // Async callback from spawn events, not useful to throw.
370 }
371 };
372 }
373 return this;
374 };
375
376 /**
377 * Call process.exit, and _exitCallback if defined.
378 *
379 * @param {number} exitCode exit code for using with process.exit
380 * @param {string} code an id string representing the error
381 * @param {string} message human-readable description of the error
382 * @return never
383 * @api private
384 */
385
386 _exit(exitCode, code, message) {
387 if (this._exitCallback) {
388 this._exitCallback(new CommanderError(exitCode, code, message));
389 // Expecting this line is not reached.
390 }
391 process.exit(exitCode);
392 };
393
394 /**
395 * Register callback `fn` for the command.
396 *
397 * Examples:
398 *
399 * program
400 * .command('help')
401 * .description('display verbose help')
402 * .action(function() {
403 * // output help here
404 * });
405 *
406 * @param {Function} fn
407 * @return {Command} `this` command for chaining
408 * @api public
409 */
410
411 action(fn) {
412 const listener = (args) => {
413 // The .action callback takes an extra parameter which is the command or options.
414 const expectedArgsCount = this._args.length;
415 const actionArgs = args.slice(0, expectedArgsCount);
416 if (this._passCommandToAction) {
417 actionArgs[expectedArgsCount] = this;
418 } else {
419 actionArgs[expectedArgsCount] = this.opts();
420 }
421 // Add the extra arguments so available too.
422 if (args.length > expectedArgsCount) {
423 actionArgs.push(args.slice(expectedArgsCount));
424 }
425
426 const actionResult = fn.apply(this, actionArgs);
427 // Remember result in case it is async. Assume parseAsync getting called on root.
428 let rootCommand = this;
429 while (rootCommand.parent) {
430 rootCommand = rootCommand.parent;
431 }
432 rootCommand._actionResults.push(actionResult);
433 };
434 this._actionHandler = listener;
435 return this;
436 };
437
438 /**
439 * Internal routine to check whether there is a clash storing option value with a Command property.
440 *
441 * @param {Option} option
442 * @api private
443 */
444
445 _checkForOptionNameClash(option) {
446 if (!this._storeOptionsAsProperties || this._storeOptionsAsPropertiesCalled) {
447 // Storing options safely, or user has been explicit and up to them.
448 return;
449 }
450 // User may override help, and hard to tell if worth warning.
451 if (option.name() === 'help') {
452 return;
453 }
454
455 const commandProperty = this._getOptionValue(option.attributeName());
456 if (commandProperty === undefined) {
457 // no clash
458 return;
459 }
460
461 let foundClash = true;
462 if (option.negate) {
463 // It is ok if define foo before --no-foo.
464 const positiveLongFlag = option.long.replace(/^--no-/, '--');
465 foundClash = !this._findOption(positiveLongFlag);
466 } else if (option.long) {
467 const negativeLongFlag = option.long.replace(/^--/, '--no-');
468 foundClash = !this._findOption(negativeLongFlag);
469 }
470
471 if (foundClash) {
472 throw new Error(`option '${option.name()}' clashes with existing property '${option.attributeName()}' on Command
473- call storeOptionsAsProperties(false) to store option values safely,
474- or call storeOptionsAsProperties(true) to suppress this check,
475- or change option name
476
477Read more on https://git.io/JJc0W`);
478 }
479 };
480
481 /**
482 * Internal implementation shared by .option() and .requiredOption()
483 *
484 * @param {Object} config
485 * @param {string} flags
486 * @param {string} description
487 * @param {Function|*} [fn] - custom option processing function or default value
488 * @param {*} [defaultValue]
489 * @return {Command} `this` command for chaining
490 * @api private
491 */
492
493 _optionEx(config, flags, description, fn, defaultValue) {
494 const option = new Option(flags, description);
495 const oname = option.name();
496 const name = option.attributeName();
497 option.mandatory = !!config.mandatory;
498
499 this._checkForOptionNameClash(option);
500
501 // default as 3rd arg
502 if (typeof fn !== 'function') {
503 if (fn instanceof RegExp) {
504 // This is a bit simplistic (especially no error messages), and probably better handled by caller using custom option processing.
505 // No longer documented in README, but still present for backwards compatibility.
506 const regex = fn;
507 fn = (val, def) => {
508 const m = regex.exec(val);
509 return m ? m[0] : def;
510 };
511 } else {
512 defaultValue = fn;
513 fn = null;
514 }
515 }
516
517 // preassign default value for --no-*, [optional], <required>, or plain flag if boolean value
518 if (option.negate || option.optional || option.required || typeof defaultValue === 'boolean') {
519 // when --no-foo we make sure default is true, unless a --foo option is already defined
520 if (option.negate) {
521 const positiveLongFlag = option.long.replace(/^--no-/, '--');
522 defaultValue = this._findOption(positiveLongFlag) ? this._getOptionValue(name) : true;
523 }
524 // preassign only if we have a default
525 if (defaultValue !== undefined) {
526 this._setOptionValue(name, defaultValue);
527 option.defaultValue = defaultValue;
528 }
529 }
530
531 // register the option
532 this.options.push(option);
533
534 // when it's passed assign the value
535 // and conditionally invoke the callback
536 this.on('option:' + oname, (val) => {
537 const oldValue = this._getOptionValue(name);
538
539 // custom processing
540 if (val !== null && fn) {
541 val = fn(val, oldValue === undefined ? defaultValue : oldValue);
542 } else if (val !== null && option.variadic) {
543 if (oldValue === defaultValue || !Array.isArray(oldValue)) {
544 val = [val];
545 } else {
546 val = oldValue.concat(val);
547 }
548 }
549
550 // unassigned or boolean value
551 if (typeof oldValue === 'boolean' || typeof oldValue === 'undefined') {
552 // if no value, negate false, and we have a default, then use it!
553 if (val == null) {
554 this._setOptionValue(name, option.negate
555 ? false
556 : defaultValue || true);
557 } else {
558 this._setOptionValue(name, val);
559 }
560 } else if (val !== null) {
561 // reassign
562 this._setOptionValue(name, option.negate ? false : val);
563 }
564 });
565
566 return this;
567 };
568
569 /**
570 * Define option with `flags`, `description` and optional
571 * coercion `fn`.
572 *
573 * The `flags` string should contain both the short and long flags,
574 * separated by comma, a pipe or space. The following are all valid
575 * all will output this way when `--help` is used.
576 *
577 * "-p, --pepper"
578 * "-p|--pepper"
579 * "-p --pepper"
580 *
581 * Examples:
582 *
583 * // simple boolean defaulting to undefined
584 * program.option('-p, --pepper', 'add pepper');
585 *
586 * program.pepper
587 * // => undefined
588 *
589 * --pepper
590 * program.pepper
591 * // => true
592 *
593 * // simple boolean defaulting to true (unless non-negated option is also defined)
594 * program.option('-C, --no-cheese', 'remove cheese');
595 *
596 * program.cheese
597 * // => true
598 *
599 * --no-cheese
600 * program.cheese
601 * // => false
602 *
603 * // required argument
604 * program.option('-C, --chdir <path>', 'change the working directory');
605 *
606 * --chdir /tmp
607 * program.chdir
608 * // => "/tmp"
609 *
610 * // optional argument
611 * program.option('-c, --cheese [type]', 'add cheese [marble]');
612 *
613 * @param {string} flags
614 * @param {string} description
615 * @param {Function|*} [fn] - custom option processing function or default value
616 * @param {*} [defaultValue]
617 * @return {Command} `this` command for chaining
618 * @api public
619 */
620
621 option(flags, description, fn, defaultValue) {
622 return this._optionEx({}, flags, description, fn, defaultValue);
623 };
624
625 /**
626 * Add a required option which must have a value after parsing. This usually means
627 * the option must be specified on the command line. (Otherwise the same as .option().)
628 *
629 * The `flags` string should contain both the short and long flags, separated by comma, a pipe or space.
630 *
631 * @param {string} flags
632 * @param {string} description
633 * @param {Function|*} [fn] - custom option processing function or default value
634 * @param {*} [defaultValue]
635 * @return {Command} `this` command for chaining
636 * @api public
637 */
638
639 requiredOption(flags, description, fn, defaultValue) {
640 return this._optionEx({ mandatory: true }, flags, description, fn, defaultValue);
641 };
642
643 /**
644 * Alter parsing of short flags with optional values.
645 *
646 * Examples:
647 *
648 * // for `.option('-f,--flag [value]'):
649 * .combineFlagAndOptionalValue(true) // `-f80` is treated like `--flag=80`, this is the default behaviour
650 * .combineFlagAndOptionalValue(false) // `-fb` is treated like `-f -b`
651 *
652 * @param {Boolean} [arg] - if `true` or omitted, an optional value can be specified directly after the flag.
653 * @api public
654 */
655 combineFlagAndOptionalValue(arg) {
656 this._combineFlagAndOptionalValue = (arg === undefined) || arg;
657 return this;
658 };
659
660 /**
661 * Allow unknown options on the command line.
662 *
663 * @param {Boolean} [arg] - if `true` or omitted, no error will be thrown
664 * for unknown options.
665 * @api public
666 */
667 allowUnknownOption(arg) {
668 this._allowUnknownOption = (arg === undefined) || arg;
669 return this;
670 };
671
672 /**
673 * Whether to store option values as properties on command object,
674 * or store separately (specify false). In both cases the option values can be accessed using .opts().
675 *
676 * @param {boolean} value
677 * @return {Command} `this` command for chaining
678 * @api public
679 */
680
681 storeOptionsAsProperties(value) {
682 this._storeOptionsAsPropertiesCalled = true;
683 this._storeOptionsAsProperties = (value === undefined) || value;
684 if (this.options.length) {
685 throw new Error('call .storeOptionsAsProperties() before adding options');
686 }
687 return this;
688 };
689
690 /**
691 * Whether to pass command to action handler,
692 * or just the options (specify false).
693 *
694 * @param {boolean} value
695 * @return {Command} `this` command for chaining
696 * @api public
697 */
698
699 passCommandToAction(value) {
700 this._passCommandToAction = (value === undefined) || value;
701 return this;
702 };
703
704 /**
705 * Store option value
706 *
707 * @param {string} key
708 * @param {Object} value
709 * @api private
710 */
711
712 _setOptionValue(key, value) {
713 if (this._storeOptionsAsProperties) {
714 this[key] = value;
715 } else {
716 this._optionValues[key] = value;
717 }
718 };
719
720 /**
721 * Retrieve option value
722 *
723 * @param {string} key
724 * @return {Object} value
725 * @api private
726 */
727
728 _getOptionValue(key) {
729 if (this._storeOptionsAsProperties) {
730 return this[key];
731 }
732 return this._optionValues[key];
733 };
734
735 /**
736 * Parse `argv`, setting options and invoking commands when defined.
737 *
738 * The default expectation is that the arguments are from node and have the application as argv[0]
739 * and the script being run in argv[1], with user parameters after that.
740 *
741 * Examples:
742 *
743 * program.parse(process.argv);
744 * program.parse(); // implicitly use process.argv and auto-detect node vs electron conventions
745 * program.parse(my-args, { from: 'user' }); // just user supplied arguments, nothing special about argv[0]
746 *
747 * @param {string[]} [argv] - optional, defaults to process.argv
748 * @param {Object} [parseOptions] - optionally specify style of options with from: node/user/electron
749 * @param {string} [parseOptions.from] - where the args are from: 'node', 'user', 'electron'
750 * @return {Command} `this` command for chaining
751 * @api public
752 */
753
754 parse(argv, parseOptions) {
755 if (argv !== undefined && !Array.isArray(argv)) {
756 throw new Error('first parameter to parse must be array or undefined');
757 }
758 parseOptions = parseOptions || {};
759
760 // Default to using process.argv
761 if (argv === undefined) {
762 argv = process.argv;
763 // @ts-ignore
764 if (process.versions && process.versions.electron) {
765 parseOptions.from = 'electron';
766 }
767 }
768 this.rawArgs = argv.slice();
769
770 // make it a little easier for callers by supporting various argv conventions
771 let userArgs;
772 switch (parseOptions.from) {
773 case undefined:
774 case 'node':
775 this._scriptPath = argv[1];
776 userArgs = argv.slice(2);
777 break;
778 case 'electron':
779 // @ts-ignore
780 if (process.defaultApp) {
781 this._scriptPath = argv[1];
782 userArgs = argv.slice(2);
783 } else {
784 userArgs = argv.slice(1);
785 }
786 break;
787 case 'user':
788 userArgs = argv.slice(0);
789 break;
790 default:
791 throw new Error(`unexpected parse option { from: '${parseOptions.from}' }`);
792 }
793 if (!this._scriptPath && process.mainModule) {
794 this._scriptPath = process.mainModule.filename;
795 }
796
797 // Guess name, used in usage in help.
798 this._name = this._name || (this._scriptPath && path.basename(this._scriptPath, path.extname(this._scriptPath)));
799
800 // Let's go!
801 this._parseCommand([], userArgs);
802
803 return this;
804 };
805
806 /**
807 * Parse `argv`, setting options and invoking commands when defined.
808 *
809 * Use parseAsync instead of parse if any of your action handlers are async. Returns a Promise.
810 *
811 * The default expectation is that the arguments are from node and have the application as argv[0]
812 * and the script being run in argv[1], with user parameters after that.
813 *
814 * Examples:
815 *
816 * program.parseAsync(process.argv);
817 * program.parseAsync(); // implicitly use process.argv and auto-detect node vs electron conventions
818 * program.parseAsync(my-args, { from: 'user' }); // just user supplied arguments, nothing special about argv[0]
819 *
820 * @param {string[]} [argv]
821 * @param {Object} [parseOptions]
822 * @param {string} parseOptions.from - where the args are from: 'node', 'user', 'electron'
823 * @return {Promise}
824 * @api public
825 */
826
827 parseAsync(argv, parseOptions) {
828 this.parse(argv, parseOptions);
829 return Promise.all(this._actionResults).then(() => this);
830 };
831
832 /**
833 * Execute a sub-command executable.
834 *
835 * @api private
836 */
837
838 _executeSubCommand(subcommand, args) {
839 args = args.slice();
840 let launchWithNode = false; // Use node for source targets so do not need to get permissions correct, and on Windows.
841 const sourceExt = ['.js', '.ts', '.tsx', '.mjs'];
842
843 // Not checking for help first. Unlikely to have mandatory and executable, and can't robustly test for help flags in external command.
844 this._checkForMissingMandatoryOptions();
845
846 // Want the entry script as the reference for command name and directory for searching for other files.
847 let scriptPath = this._scriptPath;
848 // Fallback in case not set, due to how Command created or called.
849 if (!scriptPath && process.mainModule) {
850 scriptPath = process.mainModule.filename;
851 }
852
853 let baseDir;
854 try {
855 const resolvedLink = fs.realpathSync(scriptPath);
856 baseDir = path.dirname(resolvedLink);
857 } catch (e) {
858 baseDir = '.'; // dummy, probably not going to find executable!
859 }
860
861 // name of the subcommand, like `pm-install`
862 let bin = path.basename(scriptPath, path.extname(scriptPath)) + '-' + subcommand._name;
863 if (subcommand._executableFile) {
864 bin = subcommand._executableFile;
865 }
866
867 const localBin = path.join(baseDir, bin);
868 if (fs.existsSync(localBin)) {
869 // prefer local `./<bin>` to bin in the $PATH
870 bin = localBin;
871 } else {
872 // Look for source files.
873 sourceExt.forEach((ext) => {
874 if (fs.existsSync(`${localBin}${ext}`)) {
875 bin = `${localBin}${ext}`;
876 }
877 });
878 }
879 launchWithNode = sourceExt.includes(path.extname(bin));
880
881 let proc;
882 if (process.platform !== 'win32') {
883 if (launchWithNode) {
884 args.unshift(bin);
885 // add executable arguments to spawn
886 args = incrementNodeInspectorPort(process.execArgv).concat(args);
887
888 proc = spawn(process.argv[0], args, { stdio: 'inherit' });
889 } else {
890 proc = spawn(bin, args, { stdio: 'inherit' });
891 }
892 } else {
893 args.unshift(bin);
894 // add executable arguments to spawn
895 args = incrementNodeInspectorPort(process.execArgv).concat(args);
896 proc = spawn(process.execPath, args, { stdio: 'inherit' });
897 }
898
899 const signals = ['SIGUSR1', 'SIGUSR2', 'SIGTERM', 'SIGINT', 'SIGHUP'];
900 signals.forEach((signal) => {
901 // @ts-ignore
902 process.on(signal, () => {
903 if (proc.killed === false && proc.exitCode === null) {
904 proc.kill(signal);
905 }
906 });
907 });
908
909 // By default terminate process when spawned process terminates.
910 // Suppressing the exit if exitCallback defined is a bit messy and of limited use, but does allow process to stay running!
911 const exitCallback = this._exitCallback;
912 if (!exitCallback) {
913 proc.on('close', process.exit.bind(process));
914 } else {
915 proc.on('close', () => {
916 exitCallback(new CommanderError(process.exitCode || 0, 'commander.executeSubCommandAsync', '(close)'));
917 });
918 }
919 proc.on('error', (err) => {
920 // @ts-ignore
921 if (err.code === 'ENOENT') {
922 const executableMissing = `'${bin}' does not exist
923 - if '${subcommand._name}' is not meant to be an executable command, remove description parameter from '.command()' and use '.description()' instead
924 - if the default executable name is not suitable, use the executableFile option to supply a custom name`;
925 throw new Error(executableMissing);
926 // @ts-ignore
927 } else if (err.code === 'EACCES') {
928 throw new Error(`'${bin}' not executable`);
929 }
930 if (!exitCallback) {
931 process.exit(1);
932 } else {
933 const wrappedError = new CommanderError(1, 'commander.executeSubCommandAsync', '(error)');
934 wrappedError.nestedError = err;
935 exitCallback(wrappedError);
936 }
937 });
938
939 // Store the reference to the child process
940 this.runningCommand = proc;
941 };
942
943 /**
944 * @api private
945 */
946 _dispatchSubcommand(commandName, operands, unknown) {
947 const subCommand = this._findCommand(commandName);
948 if (!subCommand) this._helpAndError();
949
950 if (subCommand._executableHandler) {
951 this._executeSubCommand(subCommand, operands.concat(unknown));
952 } else {
953 subCommand._parseCommand(operands, unknown);
954 }
955 };
956
957 /**
958 * Process arguments in context of this command.
959 *
960 * @api private
961 */
962
963 _parseCommand(operands, unknown) {
964 const parsed = this.parseOptions(unknown);
965 operands = operands.concat(parsed.operands);
966 unknown = parsed.unknown;
967 this.args = operands.concat(unknown);
968
969 if (operands && this._findCommand(operands[0])) {
970 this._dispatchSubcommand(operands[0], operands.slice(1), unknown);
971 } else if (this._lazyHasImplicitHelpCommand() && operands[0] === this._helpCommandName) {
972 if (operands.length === 1) {
973 this.help();
974 } else {
975 this._dispatchSubcommand(operands[1], [], [this._helpLongFlag]);
976 }
977 } else if (this._defaultCommandName) {
978 outputHelpIfRequested(this, unknown); // Run the help for default command from parent rather than passing to default command
979 this._dispatchSubcommand(this._defaultCommandName, operands, unknown);
980 } else {
981 if (this.commands.length && this.args.length === 0 && !this._actionHandler && !this._defaultCommandName) {
982 // probably missing subcommand and no handler, user needs help
983 this._helpAndError();
984 }
985
986 outputHelpIfRequested(this, parsed.unknown);
987 this._checkForMissingMandatoryOptions();
988 if (parsed.unknown.length > 0) {
989 this.unknownOption(parsed.unknown[0]);
990 }
991
992 if (this._actionHandler) {
993 const args = this.args.slice();
994 this._args.forEach((arg, i) => {
995 if (arg.required && args[i] == null) {
996 this.missingArgument(arg.name);
997 } else if (arg.variadic) {
998 args[i] = args.splice(i);
999 }
1000 });
1001
1002 this._actionHandler(args);
1003 this.emit('command:' + this.name(), operands, unknown);
1004 } else if (operands.length) {
1005 if (this._findCommand('*')) {
1006 this._dispatchSubcommand('*', operands, unknown);
1007 } else if (this.listenerCount('command:*')) {
1008 this.emit('command:*', operands, unknown);
1009 } else if (this.commands.length) {
1010 this.unknownCommand();
1011 }
1012 } else if (this.commands.length) {
1013 // This command has subcommands and nothing hooked up at this level, so display help.
1014 this._helpAndError();
1015 } else {
1016 // fall through for caller to handle after calling .parse()
1017 }
1018 }
1019 };
1020
1021 /**
1022 * Find matching command.
1023 *
1024 * @api private
1025 */
1026 _findCommand(name) {
1027 if (!name) return undefined;
1028 return this.commands.find(cmd => cmd._name === name || cmd._aliases.includes(name));
1029 };
1030
1031 /**
1032 * Return an option matching `arg` if any.
1033 *
1034 * @param {string} arg
1035 * @return {Option}
1036 * @api private
1037 */
1038
1039 _findOption(arg) {
1040 return this.options.find(option => option.is(arg));
1041 };
1042
1043 /**
1044 * Display an error message if a mandatory option does not have a value.
1045 * Lazy calling after checking for help flags from leaf subcommand.
1046 *
1047 * @api private
1048 */
1049
1050 _checkForMissingMandatoryOptions() {
1051 // Walk up hierarchy so can call in subcommand after checking for displaying help.
1052 for (let cmd = this; cmd; cmd = cmd.parent) {
1053 cmd.options.forEach((anOption) => {
1054 if (anOption.mandatory && (cmd._getOptionValue(anOption.attributeName()) === undefined)) {
1055 cmd.missingMandatoryOptionValue(anOption);
1056 }
1057 });
1058 }
1059 };
1060
1061 /**
1062 * Parse options from `argv` removing known options,
1063 * and return argv split into operands and unknown arguments.
1064 *
1065 * Examples:
1066 *
1067 * argv => operands, unknown
1068 * --known kkk op => [op], []
1069 * op --known kkk => [op], []
1070 * sub --unknown uuu op => [sub], [--unknown uuu op]
1071 * sub -- --unknown uuu op => [sub --unknown uuu op], []
1072 *
1073 * @param {String[]} argv
1074 * @return {{operands: String[], unknown: String[]}}
1075 * @api public
1076 */
1077
1078 parseOptions(argv) {
1079 const operands = []; // operands, not options or values
1080 const unknown = []; // first unknown option and remaining unknown args
1081 let dest = operands;
1082 const args = argv.slice();
1083
1084 function maybeOption(arg) {
1085 return arg.length > 1 && arg[0] === '-';
1086 }
1087
1088 // parse options
1089 let activeVariadicOption = null;
1090 while (args.length) {
1091 const arg = args.shift();
1092
1093 // literal
1094 if (arg === '--') {
1095 if (dest === unknown) dest.push(arg);
1096 dest.push(...args);
1097 break;
1098 }
1099
1100 if (activeVariadicOption && !maybeOption(arg)) {
1101 this.emit(`option:${activeVariadicOption.name()}`, arg);
1102 continue;
1103 }
1104 activeVariadicOption = null;
1105
1106 if (maybeOption(arg)) {
1107 const option = this._findOption(arg);
1108 // recognised option, call listener to assign value with possible custom processing
1109 if (option) {
1110 if (option.required) {
1111 const value = args.shift();
1112 if (value === undefined) this.optionMissingArgument(option);
1113 this.emit(`option:${option.name()}`, value);
1114 } else if (option.optional) {
1115 let value = null;
1116 // historical behaviour is optional value is following arg unless an option
1117 if (args.length > 0 && !maybeOption(args[0])) {
1118 value = args.shift();
1119 }
1120 this.emit(`option:${option.name()}`, value);
1121 } else { // boolean flag
1122 this.emit(`option:${option.name()}`);
1123 }
1124 activeVariadicOption = option.variadic ? option : null;
1125 continue;
1126 }
1127 }
1128
1129 // Look for combo options following single dash, eat first one if known.
1130 if (arg.length > 2 && arg[0] === '-' && arg[1] !== '-') {
1131 const option = this._findOption(`-${arg[1]}`);
1132 if (option) {
1133 if (option.required || (option.optional && this._combineFlagAndOptionalValue)) {
1134 // option with value following in same argument
1135 this.emit(`option:${option.name()}`, arg.slice(2));
1136 } else {
1137 // boolean option, emit and put back remainder of arg for further processing
1138 this.emit(`option:${option.name()}`);
1139 args.unshift(`-${arg.slice(2)}`);
1140 }
1141 continue;
1142 }
1143 }
1144
1145 // Look for known long flag with value, like --foo=bar
1146 if (/^--[^=]+=/.test(arg)) {
1147 const index = arg.indexOf('=');
1148 const option = this._findOption(arg.slice(0, index));
1149 if (option && (option.required || option.optional)) {
1150 this.emit(`option:${option.name()}`, arg.slice(index + 1));
1151 continue;
1152 }
1153 }
1154
1155 // looks like an option but unknown, unknowns from here
1156 if (arg.length > 1 && arg[0] === '-') {
1157 dest = unknown;
1158 }
1159
1160 // add arg
1161 dest.push(arg);
1162 }
1163
1164 return { operands, unknown };
1165 };
1166
1167 /**
1168 * Return an object containing options as key-value pairs
1169 *
1170 * @return {Object}
1171 * @api public
1172 */
1173 opts() {
1174 if (this._storeOptionsAsProperties) {
1175 // Preserve original behaviour so backwards compatible when still using properties
1176 const result = {};
1177 const len = this.options.length;
1178
1179 for (let i = 0; i < len; i++) {
1180 const key = this.options[i].attributeName();
1181 result[key] = key === this._versionOptionName ? this._version : this[key];
1182 }
1183 return result;
1184 }
1185
1186 return this._optionValues;
1187 };
1188
1189 /**
1190 * Argument `name` is missing.
1191 *
1192 * @param {string} name
1193 * @api private
1194 */
1195
1196 missingArgument(name) {
1197 const message = `error: missing required argument '${name}'`;
1198 console.error(message);
1199 this._exit(1, 'commander.missingArgument', message);
1200 };
1201
1202 /**
1203 * `Option` is missing an argument, but received `flag` or nothing.
1204 *
1205 * @param {Option} option
1206 * @param {string} [flag]
1207 * @api private
1208 */
1209
1210 optionMissingArgument(option, flag) {
1211 let message;
1212 if (flag) {
1213 message = `error: option '${option.flags}' argument missing, got '${flag}'`;
1214 } else {
1215 message = `error: option '${option.flags}' argument missing`;
1216 }
1217 console.error(message);
1218 this._exit(1, 'commander.optionMissingArgument', message);
1219 };
1220
1221 /**
1222 * `Option` does not have a value, and is a mandatory option.
1223 *
1224 * @param {Option} option
1225 * @api private
1226 */
1227
1228 missingMandatoryOptionValue(option) {
1229 const message = `error: required option '${option.flags}' not specified`;
1230 console.error(message);
1231 this._exit(1, 'commander.missingMandatoryOptionValue', message);
1232 };
1233
1234 /**
1235 * Unknown option `flag`.
1236 *
1237 * @param {string} flag
1238 * @api private
1239 */
1240
1241 unknownOption(flag) {
1242 if (this._allowUnknownOption) return;
1243 const message = `error: unknown option '${flag}'`;
1244 console.error(message);
1245 this._exit(1, 'commander.unknownOption', message);
1246 };
1247
1248 /**
1249 * Unknown command.
1250 *
1251 * @api private
1252 */
1253
1254 unknownCommand() {
1255 const partCommands = [this.name()];
1256 for (let parentCmd = this.parent; parentCmd; parentCmd = parentCmd.parent) {
1257 partCommands.unshift(parentCmd.name());
1258 }
1259 const fullCommand = partCommands.join(' ');
1260 const message = `error: unknown command '${this.args[0]}'.` +
1261 (this._hasHelpOption ? ` See '${fullCommand} ${this._helpLongFlag}'.` : '');
1262 console.error(message);
1263 this._exit(1, 'commander.unknownCommand', message);
1264 };
1265
1266 /**
1267 * Set the program version to `str`.
1268 *
1269 * This method auto-registers the "-V, --version" flag
1270 * which will print the version number when passed.
1271 *
1272 * You can optionally supply the flags and description to override the defaults.
1273 *
1274 * @param {string} str
1275 * @param {string} [flags]
1276 * @param {string} [description]
1277 * @return {this | string} `this` command for chaining, or version string if no arguments
1278 * @api public
1279 */
1280
1281 version(str, flags, description) {
1282 if (str === undefined) return this._version;
1283 this._version = str;
1284 flags = flags || '-V, --version';
1285 description = description || 'output the version number';
1286 const versionOption = new Option(flags, description);
1287 this._versionOptionName = versionOption.attributeName();
1288 this.options.push(versionOption);
1289 this.on('option:' + versionOption.name(), () => {
1290 process.stdout.write(str + '\n');
1291 this._exit(0, 'commander.version', str);
1292 });
1293 return this;
1294 };
1295
1296 /**
1297 * Set the description to `str`.
1298 *
1299 * @param {string} str
1300 * @param {Object} [argsDescription]
1301 * @return {string|Command}
1302 * @api public
1303 */
1304
1305 description(str, argsDescription) {
1306 if (str === undefined && argsDescription === undefined) return this._description;
1307 this._description = str;
1308 this._argsDescription = argsDescription;
1309 return this;
1310 };
1311
1312 /**
1313 * Set an alias for the command.
1314 *
1315 * You may call more than once to add multiple aliases. Only the first alias is shown in the auto-generated help.
1316 *
1317 * @param {string} [alias]
1318 * @return {string|Command}
1319 * @api public
1320 */
1321
1322 alias(alias) {
1323 if (alias === undefined) return this._aliases[0]; // just return first, for backwards compatibility
1324
1325 let command = this;
1326 if (this.commands.length !== 0 && this.commands[this.commands.length - 1]._executableHandler) {
1327 // assume adding alias for last added executable subcommand, rather than this
1328 command = this.commands[this.commands.length - 1];
1329 }
1330
1331 if (alias === command._name) throw new Error('Command alias can\'t be the same as its name');
1332
1333 command._aliases.push(alias);
1334 return this;
1335 };
1336
1337 /**
1338 * Set aliases for the command.
1339 *
1340 * Only the first alias is shown in the auto-generated help.
1341 *
1342 * @param {string[]} [aliases]
1343 * @return {string[]|Command}
1344 * @api public
1345 */
1346
1347 aliases(aliases) {
1348 // Getter for the array of aliases is the main reason for having aliases() in addition to alias().
1349 if (aliases === undefined) return this._aliases;
1350
1351 aliases.forEach((alias) => this.alias(alias));
1352 return this;
1353 };
1354
1355 /**
1356 * Set / get the command usage `str`.
1357 *
1358 * @param {string} [str]
1359 * @return {String|Command}
1360 * @api public
1361 */
1362
1363 usage(str) {
1364 if (str === undefined) {
1365 if (this._usage) return this._usage;
1366
1367 const args = this._args.map((arg) => {
1368 return humanReadableArgName(arg);
1369 });
1370 return [].concat(
1371 (this.options.length || this._hasHelpOption ? '[options]' : []),
1372 (this.commands.length ? '[command]' : []),
1373 (this._args.length ? args : [])
1374 ).join(' ');
1375 }
1376
1377 this._usage = str;
1378 return this;
1379 };
1380
1381 /**
1382 * Get or set the name of the command
1383 *
1384 * @param {string} [str]
1385 * @return {String|Command}
1386 * @api public
1387 */
1388
1389 name(str) {
1390 if (str === undefined) return this._name;
1391 this._name = str;
1392 return this;
1393 };
1394
1395 /**
1396 * Return prepared commands.
1397 *
1398 * @return {Array}
1399 * @api private
1400 */
1401
1402 prepareCommands() {
1403 const commandDetails = this.commands.filter((cmd) => {
1404 return !cmd._hidden;
1405 }).map((cmd) => {
1406 const args = cmd._args.map((arg) => {
1407 return humanReadableArgName(arg);
1408 }).join(' ');
1409
1410 return [
1411 cmd._name +
1412 (cmd._aliases[0] ? '|' + cmd._aliases[0] : '') +
1413 (cmd.options.length ? ' [options]' : '') +
1414 (args ? ' ' + args : ''),
1415 cmd._description
1416 ];
1417 });
1418
1419 if (this._lazyHasImplicitHelpCommand()) {
1420 commandDetails.push([this._helpCommandnameAndArgs, this._helpCommandDescription]);
1421 }
1422 return commandDetails;
1423 };
1424
1425 /**
1426 * Return the largest command length.
1427 *
1428 * @return {number}
1429 * @api private
1430 */
1431
1432 largestCommandLength() {
1433 const commands = this.prepareCommands();
1434 return commands.reduce((max, command) => {
1435 return Math.max(max, command[0].length);
1436 }, 0);
1437 };
1438
1439 /**
1440 * Return the largest option length.
1441 *
1442 * @return {number}
1443 * @api private
1444 */
1445
1446 largestOptionLength() {
1447 const options = [].slice.call(this.options);
1448 options.push({
1449 flags: this._helpFlags
1450 });
1451
1452 return options.reduce((max, option) => {
1453 return Math.max(max, option.flags.length);
1454 }, 0);
1455 };
1456
1457 /**
1458 * Return the largest arg length.
1459 *
1460 * @return {number}
1461 * @api private
1462 */
1463
1464 largestArgLength() {
1465 return this._args.reduce((max, arg) => {
1466 return Math.max(max, arg.name.length);
1467 }, 0);
1468 };
1469
1470 /**
1471 * Return the pad width.
1472 *
1473 * @return {number}
1474 * @api private
1475 */
1476
1477 padWidth() {
1478 let width = this.largestOptionLength();
1479 if (this._argsDescription && this._args.length) {
1480 if (this.largestArgLength() > width) {
1481 width = this.largestArgLength();
1482 }
1483 }
1484
1485 if (this.commands && this.commands.length) {
1486 if (this.largestCommandLength() > width) {
1487 width = this.largestCommandLength();
1488 }
1489 }
1490
1491 return width;
1492 };
1493
1494 /**
1495 * Return help for options.
1496 *
1497 * @return {string}
1498 * @api private
1499 */
1500
1501 optionHelp() {
1502 const width = this.padWidth();
1503 const columns = process.stdout.columns || 80;
1504 const descriptionWidth = columns - width - 4;
1505 function padOptionDetails(flags, description) {
1506 return pad(flags, width) + ' ' + optionalWrap(description, descriptionWidth, width + 2);
1507 };
1508
1509 // Explicit options (including version)
1510 const help = this.options.map((option) => {
1511 const fullDesc = option.description +
1512 ((!option.negate && option.defaultValue !== undefined) ? ' (default: ' + JSON.stringify(option.defaultValue) + ')' : '');
1513 return padOptionDetails(option.flags, fullDesc);
1514 });
1515
1516 // Implicit help
1517 const showShortHelpFlag = this._hasHelpOption && this._helpShortFlag && !this._findOption(this._helpShortFlag);
1518 const showLongHelpFlag = this._hasHelpOption && !this._findOption(this._helpLongFlag);
1519 if (showShortHelpFlag || showLongHelpFlag) {
1520 let helpFlags = this._helpFlags;
1521 if (!showShortHelpFlag) {
1522 helpFlags = this._helpLongFlag;
1523 } else if (!showLongHelpFlag) {
1524 helpFlags = this._helpShortFlag;
1525 }
1526 help.push(padOptionDetails(helpFlags, this._helpDescription));
1527 }
1528
1529 return help.join('\n');
1530 };
1531
1532 /**
1533 * Return command help documentation.
1534 *
1535 * @return {string}
1536 * @api private
1537 */
1538
1539 commandHelp() {
1540 if (!this.commands.length && !this._lazyHasImplicitHelpCommand()) return '';
1541
1542 const commands = this.prepareCommands();
1543 const width = this.padWidth();
1544
1545 const columns = process.stdout.columns || 80;
1546 const descriptionWidth = columns - width - 4;
1547
1548 return [
1549 'Commands:',
1550 commands.map((cmd) => {
1551 const desc = cmd[1] ? ' ' + cmd[1] : '';
1552 return (desc ? pad(cmd[0], width) : cmd[0]) + optionalWrap(desc, descriptionWidth, width + 2);
1553 }).join('\n').replace(/^/gm, ' '),
1554 ''
1555 ].join('\n');
1556 };
1557
1558 /**
1559 * Return program help documentation.
1560 *
1561 * @return {string}
1562 * @api public
1563 */
1564
1565 helpInformation() {
1566 let desc = [];
1567 if (this._description) {
1568 desc = [
1569 this._description,
1570 ''
1571 ];
1572
1573 const argsDescription = this._argsDescription;
1574 if (argsDescription && this._args.length) {
1575 const width = this.padWidth();
1576 const columns = process.stdout.columns || 80;
1577 const descriptionWidth = columns - width - 5;
1578 desc.push('Arguments:');
1579 this._args.forEach((arg) => {
1580 desc.push(' ' + pad(arg.name, width) + ' ' + wrap(argsDescription[arg.name] || '', descriptionWidth, width + 4));
1581 });
1582 desc.push('');
1583 }
1584 }
1585
1586 let cmdName = this._name;
1587 if (this._aliases[0]) {
1588 cmdName = cmdName + '|' + this._aliases[0];
1589 }
1590 let parentCmdNames = '';
1591 for (let parentCmd = this.parent; parentCmd; parentCmd = parentCmd.parent) {
1592 parentCmdNames = parentCmd.name() + ' ' + parentCmdNames;
1593 }
1594 const usage = [
1595 'Usage: ' + parentCmdNames + cmdName + ' ' + this.usage(),
1596 ''
1597 ];
1598
1599 let cmds = [];
1600 const commandHelp = this.commandHelp();
1601 if (commandHelp) cmds = [commandHelp];
1602
1603 let options = [];
1604 if (this._hasHelpOption || this.options.length > 0) {
1605 options = [
1606 'Options:',
1607 '' + this.optionHelp().replace(/^/gm, ' '),
1608 ''
1609 ];
1610 }
1611
1612 return usage
1613 .concat(desc)
1614 .concat(options)
1615 .concat(cmds)
1616 .join('\n');
1617 };
1618
1619 /**
1620 * Output help information for this command.
1621 *
1622 * When listener(s) are available for the helpLongFlag
1623 * those callbacks are invoked.
1624 *
1625 * @api public
1626 */
1627
1628 outputHelp(cb) {
1629 if (!cb) {
1630 cb = (passthru) => {
1631 return passthru;
1632 };
1633 }
1634 const cbOutput = cb(this.helpInformation());
1635 if (typeof cbOutput !== 'string' && !Buffer.isBuffer(cbOutput)) {
1636 throw new Error('outputHelp callback must return a string or a Buffer');
1637 }
1638 process.stdout.write(cbOutput);
1639 this.emit(this._helpLongFlag);
1640 };
1641
1642 /**
1643 * You can pass in flags and a description to override the help
1644 * flags and help description for your command. Pass in false to
1645 * disable the built-in help option.
1646 *
1647 * @param {string | boolean} [flags]
1648 * @param {string} [description]
1649 * @return {Command} `this` command for chaining
1650 * @api public
1651 */
1652
1653 helpOption(flags, description) {
1654 if (typeof flags === 'boolean') {
1655 this._hasHelpOption = flags;
1656 return this;
1657 }
1658 this._helpFlags = flags || this._helpFlags;
1659 this._helpDescription = description || this._helpDescription;
1660
1661 const helpFlags = _parseOptionFlags(this._helpFlags);
1662 this._helpShortFlag = helpFlags.shortFlag;
1663 this._helpLongFlag = helpFlags.longFlag;
1664
1665 return this;
1666 };
1667
1668 /**
1669 * Output help information and exit.
1670 *
1671 * @param {Function} [cb]
1672 * @api public
1673 */
1674
1675 help(cb) {
1676 this.outputHelp(cb);
1677 // exitCode: preserving original behaviour which was calling process.exit()
1678 // message: do not have all displayed text available so only passing placeholder.
1679 this._exit(process.exitCode || 0, 'commander.help', '(outputHelp)');
1680 };
1681
1682 /**
1683 * Output help information and exit. Display for error situations.
1684 *
1685 * @api private
1686 */
1687
1688 _helpAndError() {
1689 this.outputHelp();
1690 // message: do not have all displayed text available so only passing placeholder.
1691 this._exit(1, 'commander.help', '(outputHelp)');
1692 };
1693};
1694
1695/**
1696 * Expose the root command.
1697 */
1698
1699exports = module.exports = new Command();
1700exports.program = exports; // More explicit access to global command.
1701
1702/**
1703 * Expose classes
1704 */
1705
1706exports.Command = Command;
1707exports.Option = Option;
1708exports.CommanderError = CommanderError;
1709
1710/**
1711 * Camel-case the given `flag`
1712 *
1713 * @param {string} flag
1714 * @return {string}
1715 * @api private
1716 */
1717
1718function camelcase(flag) {
1719 return flag.split('-').reduce((str, word) => {
1720 return str + word[0].toUpperCase() + word.slice(1);
1721 });
1722}
1723
1724/**
1725 * Pad `str` to `width`.
1726 *
1727 * @param {string} str
1728 * @param {number} width
1729 * @return {string}
1730 * @api private
1731 */
1732
1733function pad(str, width) {
1734 const len = Math.max(0, width - str.length);
1735 return str + Array(len + 1).join(' ');
1736}
1737
1738/**
1739 * Wraps the given string with line breaks at the specified width while breaking
1740 * words and indenting every but the first line on the left.
1741 *
1742 * @param {string} str
1743 * @param {number} width
1744 * @param {number} indent
1745 * @return {string}
1746 * @api private
1747 */
1748function wrap(str, width, indent) {
1749 const regex = new RegExp('.{1,' + (width - 1) + '}([\\s\u200B]|$)|[^\\s\u200B]+?([\\s\u200B]|$)', 'g');
1750 const lines = str.match(regex) || [];
1751 return lines.map((line, i) => {
1752 if (line.slice(-1) === '\n') {
1753 line = line.slice(0, line.length - 1);
1754 }
1755 return ((i > 0 && indent) ? Array(indent + 1).join(' ') : '') + line.trimRight();
1756 }).join('\n');
1757}
1758
1759/**
1760 * Optionally wrap the given str to a max width of width characters per line
1761 * while indenting with indent spaces. Do not wrap if insufficient width or
1762 * string is manually formatted.
1763 *
1764 * @param {string} str
1765 * @param {number} width
1766 * @param {number} indent
1767 * @return {string}
1768 * @api private
1769 */
1770function optionalWrap(str, width, indent) {
1771 // Detect manually wrapped and indented strings by searching for line breaks
1772 // followed by multiple spaces/tabs.
1773 if (str.match(/[\n]\s+/)) return str;
1774 // Do not wrap to narrow columns (or can end up with a word per line).
1775 const minWidth = 40;
1776 if (width < minWidth) return str;
1777
1778 return wrap(str, width, indent);
1779}
1780
1781/**
1782 * Output help information if help flags specified
1783 *
1784 * @param {Command} cmd - command to output help for
1785 * @param {Array} args - array of options to search for help flags
1786 * @api private
1787 */
1788
1789function outputHelpIfRequested(cmd, args) {
1790 const helpOption = cmd._hasHelpOption && args.find(arg => arg === cmd._helpLongFlag || arg === cmd._helpShortFlag);
1791 if (helpOption) {
1792 cmd.outputHelp();
1793 // (Do not have all displayed text available so only passing placeholder.)
1794 cmd._exit(0, 'commander.helpDisplayed', '(outputHelp)');
1795 }
1796}
1797
1798/**
1799 * Takes an argument and returns its human readable equivalent for help usage.
1800 *
1801 * @param {Object} arg
1802 * @return {string}
1803 * @api private
1804 */
1805
1806function humanReadableArgName(arg) {
1807 const nameOutput = arg.name + (arg.variadic === true ? '...' : '');
1808
1809 return arg.required
1810 ? '<' + nameOutput + '>'
1811 : '[' + nameOutput + ']';
1812}
1813
1814/**
1815 * Parse the short and long flag out of something like '-m,--mixed <value>'
1816 *
1817 * @api private
1818 */
1819
1820function _parseOptionFlags(flags) {
1821 let shortFlag;
1822 let longFlag;
1823 // Use original very loose parsing to maintain backwards compatibility for now,
1824 // which allowed for example unintended `-sw, --short-word` [sic].
1825 const flagParts = flags.split(/[ |,]+/);
1826 if (flagParts.length > 1 && !/^[[<]/.test(flagParts[1])) shortFlag = flagParts.shift();
1827 longFlag = flagParts.shift();
1828 // Add support for lone short flag without significantly changing parsing!
1829 if (!shortFlag && /^-[^-]$/.test(longFlag)) {
1830 shortFlag = longFlag;
1831 longFlag = undefined;
1832 }
1833 return { shortFlag, longFlag };
1834}
1835
1836/**
1837 * Scan arguments and increment port number for inspect calls (to avoid conflicts when spawning new command).
1838 *
1839 * @param {string[]} args - array of arguments from node.execArgv
1840 * @returns {string[]}
1841 * @api private
1842 */
1843
1844function incrementNodeInspectorPort(args) {
1845 // Testing for these options:
1846 // --inspect[=[host:]port]
1847 // --inspect-brk[=[host:]port]
1848 // --inspect-port=[host:]port
1849 return args.map((arg) => {
1850 if (!arg.startsWith('--inspect')) {
1851 return arg;
1852 }
1853 let debugOption;
1854 let debugHost = '127.0.0.1';
1855 let debugPort = '9229';
1856 let match;
1857 if ((match = arg.match(/^(--inspect(-brk)?)$/)) !== null) {
1858 // e.g. --inspect
1859 debugOption = match[1];
1860 } else if ((match = arg.match(/^(--inspect(-brk|-port)?)=([^:]+)$/)) !== null) {
1861 debugOption = match[1];
1862 if (/^\d+$/.test(match[3])) {
1863 // e.g. --inspect=1234
1864 debugPort = match[3];
1865 } else {
1866 // e.g. --inspect=localhost
1867 debugHost = match[3];
1868 }
1869 } else if ((match = arg.match(/^(--inspect(-brk|-port)?)=([^:]+):(\d+)$/)) !== null) {
1870 // e.g. --inspect=localhost:1234
1871 debugOption = match[1];
1872 debugHost = match[3];
1873 debugPort = match[4];
1874 }
1875
1876 if (debugOption && debugPort !== '0') {
1877 return `${debugOption}=${debugHost}:${parseInt(debugPort) + 1}`;
1878 }
1879 return arg;
1880 });
1881}