UNPKG

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