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} 1757