1'use strict' 2const argsert = require('./lib/argsert') 3const fs = require('fs') 4const Command = require('./lib/command') 5const Completion = require('./lib/completion') 6const Parser = require('yargs-parser') 7const path = require('path') 8const Usage = require('./lib/usage') 9const Validation = require('./lib/validation') 10const Y18n = require('y18n') 11const objFilter = require('./lib/obj-filter') 12const setBlocking = require('set-blocking') 13const applyExtends = require('./lib/apply-extends') 14const { globalMiddlewareFactory } = require('./lib/middleware') 15const YError = require('./lib/yerror') 16 17exports = module.exports = Yargs 18function Yargs (processArgs, cwd, parentRequire) { 19 processArgs = processArgs || [] // handle calling yargs(). 20 21 const self = {} 22 let command = null 23 let completion = null 24 let groups = {} 25 let globalMiddleware = [] 26 let output = '' 27 let preservedGroups = {} 28 let usage = null 29 let validation = null 30 31 const y18n = Y18n({ 32 directory: path.resolve(__dirname, './locales'), 33 updateFiles: false 34 }) 35 36 self.middleware = globalMiddlewareFactory(globalMiddleware, self) 37 38 if (!cwd) cwd = process.cwd() 39 40 self.scriptName = function (scriptName) { 41 self.customScriptName = true 42 self.$0 = scriptName 43 return self 44 } 45 46 // ignore the node bin, specify this in your 47 // bin file with #!/usr/bin/env node 48 if (/\b(node|iojs|electron)(\.exe)?$/.test(process.argv[0])) { 49 self.$0 = process.argv.slice(1, 2) 50 } else { 51 self.$0 = process.argv.slice(0, 1) 52 } 53 54 self.$0 = self.$0 55 .map((x, i) => { 56 const b = rebase(cwd, x) 57 return x.match(/^(\/|([a-zA-Z]:)?\\)/) && b.length < x.length ? b : x 58 }) 59 .join(' ').trim() 60 61 if (process.env._ !== undefined && process.argv[1] === process.env._) { 62 self.$0 = process.env._.replace( 63 `${path.dirname(process.execPath)}/`, '' 64 ) 65 } 66 67 // use context object to keep track of resets, subcommand execution, etc 68 // submodules should modify and check the state of context as necessary 69 const context = { resets: -1, commands: [], fullCommands: [], files: [] } 70 self.getContext = () => context 71 72 // puts yargs back into an initial state. any keys 73 // that have been set to "global" will not be reset 74 // by this action. 75 let options 76 self.resetOptions = self.reset = function resetOptions (aliases) { 77 context.resets++ 78 aliases = aliases || {} 79 options = options || {} 80 // put yargs back into an initial state, this 81 // logic is used to build a nested command 82 // hierarchy. 83 const tmpOptions = {} 84 tmpOptions.local = options.local ? options.local : [] 85 tmpOptions.configObjects = options.configObjects ? options.configObjects : [] 86 87 // if a key has been explicitly set as local, 88 // we should reset it before passing options to command. 89 const localLookup = {} 90 tmpOptions.local.forEach((l) => { 91 localLookup[l] = true 92 ;(aliases[l] || []).forEach((a) => { 93 localLookup[a] = true 94 }) 95 }) 96 97 // add all groups not set to local to preserved groups 98 Object.assign( 99 preservedGroups, 100 Object.keys(groups).reduce((acc, groupName) => { 101 const keys = groups[groupName].filter(key => !(key in localLookup)) 102 if (keys.length > 0) { 103 acc[groupName] = keys 104 } 105 return acc 106 }, {}) 107 ) 108 // groups can now be reset 109 groups = {} 110 111 const arrayOptions = [ 112 'array', 'boolean', 'string', 'skipValidation', 113 'count', 'normalize', 'number', 114 'hiddenOptions' 115 ] 116 117 const objectOptions = [ 118 'narg', 'key', 'alias', 'default', 'defaultDescription', 119 'config', 'choices', 'demandedOptions', 'demandedCommands', 'coerce' 120 ] 121 122 arrayOptions.forEach((k) => { 123 tmpOptions[k] = (options[k] || []).filter(k => !localLookup[k]) 124 }) 125 126 objectOptions.forEach((k) => { 127 tmpOptions[k] = objFilter(options[k], (k, v) => !localLookup[k]) 128 }) 129 130 tmpOptions.envPrefix = options.envPrefix 131 options = tmpOptions 132 133 // if this is the first time being executed, create 134 // instances of all our helpers -- otherwise just reset. 135 usage = usage ? usage.reset(localLookup) : Usage(self, y18n) 136 validation = validation ? validation.reset(localLookup) : Validation(self, usage, y18n) 137 command = command ? command.reset() : Command(self, usage, validation, globalMiddleware) 138 if (!completion) completion = Completion(self, usage, command) 139 140 completionCommand = null 141 output = '' 142 exitError = null 143 hasOutput = false 144 self.parsed = false 145 146 return self 147 } 148 self.resetOptions() 149 150 // temporary hack: allow "freezing" of reset-able state for parse(msg, cb) 151 let frozens = [] 152 function freeze () { 153 let frozen = {} 154 frozens.push(frozen) 155 frozen.options = options 156 frozen.configObjects = options.configObjects.slice(0) 157 frozen.exitProcess = exitProcess 158 frozen.groups = groups 159 usage.freeze() 160 validation.freeze() 161 command.freeze() 162 frozen.strict = strict 163 frozen.completionCommand = completionCommand 164 frozen.output = output 165 frozen.exitError = exitError 166 frozen.hasOutput = hasOutput 167 frozen.parsed = self.parsed 168 frozen.parseFn = parseFn 169 frozen.parseContext = parseContext 170 } 171 function unfreeze () { 172 let frozen = frozens.pop() 173 options = frozen.options 174 options.configObjects = frozen.configObjects 175 exitProcess = frozen.exitProcess 176 groups = frozen.groups 177 output = frozen.output 178 exitError = frozen.exitError 179 hasOutput = frozen.hasOutput 180 self.parsed = frozen.parsed 181 usage.unfreeze() 182 validation.unfreeze() 183 command.unfreeze() 184 strict = frozen.strict 185 completionCommand = frozen.completionCommand 186 parseFn = frozen.parseFn 187 parseContext = frozen.parseContext 188 } 189 190 self.boolean = function (keys) { 191 argsert('<array|string>', [keys], arguments.length) 192 populateParserHintArray('boolean', keys) 193 return self 194 } 195 196 self.array = function (keys) { 197 argsert('<array|string>', [keys], arguments.length) 198 populateParserHintArray('array', keys) 199 return self 200 } 201 202 self.number = function (keys) { 203 argsert('<array|string>', [keys], arguments.length) 204 populateParserHintArray('number', keys) 205 return self 206 } 207 208 self.normalize = function (keys) { 209 argsert('<array|string>', [keys], arguments.length) 210 populateParserHintArray('normalize', keys) 211 return self 212 } 213 214 self.count = function (keys) { 215 argsert('<array|string>', [keys], arguments.length) 216 populateParserHintArray('count', keys) 217 return self 218 } 219 220 self.string = function (keys) { 221 argsert('<array|string>', [keys], arguments.length) 222 populateParserHintArray('string', keys) 223 return self 224 } 225 226 self.requiresArg = function (keys) { 227 argsert('<array|string>', [keys], arguments.length) 228 populateParserHintObject(self.nargs, false, 'narg', keys, 1) 229 return self 230 } 231 232 self.skipValidation = function (keys) { 233 argsert('<array|string>', [keys], arguments.length) 234 populateParserHintArray('skipValidation', keys) 235 return self 236 } 237 238 function populateParserHintArray (type, keys, value) { 239 keys = [].concat(keys) 240 keys.forEach((key) => { 241 key = sanitizeKey(key) 242 options[type].push(key) 243 }) 244 } 245 246 self.nargs = function (key, value) { 247 argsert('<string|object|array> [number]', [key, value], arguments.length) 248 populateParserHintObject(self.nargs, false, 'narg', key, value) 249 return self 250 } 251 252 self.choices = function (key, value) { 253 argsert('<object|string|array> [string|array]', [key, value], arguments.length) 254 populateParserHintObject(self.choices, true, 'choices', key, value) 255 return self 256 } 257 258 self.alias = function (key, value) { 259 argsert('<object|string|array> [string|array]', [key, value], arguments.length) 260 populateParserHintObject(self.alias, true, 'alias', key, value) 261 return self 262 } 263 264 // TODO: actually deprecate self.defaults. 265 self.default = self.defaults = function (key, value, defaultDescription) { 266 argsert('<object|string|array> [*] [string]', [key, value, defaultDescription], arguments.length) 267 if (defaultDescription) options.defaultDescription[key] = defaultDescription 268 if (typeof value === 'function') { 269 if (!options.defaultDescription[key]) options.defaultDescription[key] = usage.functionDescription(value) 270 value = value.call() 271 } 272 populateParserHintObject(self.default, false, 'default', key, value) 273 return self 274 } 275 276 self.describe = function (key, desc) { 277 argsert('<object|string|array> [string]', [key, desc], arguments.length) 278 populateParserHintObject(self.describe, false, 'key', key, true) 279 usage.describe(key, desc) 280 return self 281 } 282 283 self.demandOption = function (keys, msg) { 284 argsert('<object|string|array> [string]', [keys, msg], arguments.length) 285 populateParserHintObject(self.demandOption, false, 'demandedOptions', keys, msg) 286 return self 287 } 288 289 self.coerce = function (keys, value) { 290 argsert('<object|string|array> [function]', [keys, value], arguments.length) 291 populateParserHintObject(self.coerce, false, 'coerce', keys, value) 292 return self 293 } 294 295 function populateParserHintObject (builder, isArray, type, key, value) { 296 if (Array.isArray(key)) { 297 const temp = Object.create(null) 298 // an array of keys with one value ['x', 'y', 'z'], function parse () {} 299 key.forEach((k) => { 300 temp[k] = value 301 }) 302 builder(temp) 303 } else if (typeof key === 'object') { 304 // an object of key value pairs: {'x': parse () {}, 'y': parse() {}} 305 Object.keys(key).forEach((k) => { 306 builder(k, key[k]) 307 }) 308 } else { 309 key = sanitizeKey(key) 310 // a single key value pair 'x', parse() {} 311 if (isArray) { 312 options[type][key] = (options[type][key] || []).concat(value) 313 } else { 314 options[type][key] = value 315 } 316 } 317 } 318 319 // TODO(bcoe): in future major versions move more objects towards 320 // Object.create(null): 321 function sanitizeKey (key) { 322 if (key === '__proto__') return '___proto___' 323 return key 324 } 325 326 function deleteFromParserHintObject (optionKey) { 327 // delete from all parsing hints: 328 // boolean, array, key, alias, etc. 329 Object.keys(options).forEach((hintKey) => { 330 const hint = options[hintKey] 331 if (Array.isArray(hint)) { 332 if (~hint.indexOf(optionKey)) hint.splice(hint.indexOf(optionKey), 1) 333 } else if (typeof hint === 'object') { 334 delete hint[optionKey] 335 } 336 }) 337 // now delete the description from usage.js. 338 delete usage.getDescriptions()[optionKey] 339 } 340 341 self.config = function config (key, msg, parseFn) { 342 argsert('[object|string] [string|function] [function]', [key, msg, parseFn], arguments.length) 343 // allow a config object to be provided directly. 344 if (typeof key === 'object') { 345 key = applyExtends(key, cwd, self.getParserConfiguration()['deep-merge-config']) 346 options.configObjects = (options.configObjects || []).concat(key) 347 return self 348 } 349 350 // allow for a custom parsing function. 351 if (typeof msg === 'function') { 352 parseFn = msg 353 msg = null 354 } 355 356 key = key || 'config' 357 self.describe(key, msg || usage.deferY18nLookup('Path to JSON config file')) 358 ;(Array.isArray(key) ? key : [key]).forEach((k) => { 359 options.config[k] = parseFn || true 360 }) 361 362 return self 363 } 364 365 self.example = function (cmd, description) { 366 argsert('<string> [string]', [cmd, description], arguments.length) 367 usage.example(cmd, description) 368 return self 369 } 370 371 self.command = function (cmd, description, builder, handler, middlewares) { 372 argsert('<string|array|object> [string|boolean] [function|object] [function] [array]', [cmd, description, builder, handler, middlewares], arguments.length) 373 command.addHandler(cmd, description, builder, handler, middlewares) 374 return self 375 } 376 377 self.commandDir = function (dir, opts) { 378 argsert('<string> [object]', [dir, opts], arguments.length) 379 const req = parentRequire || require 380 command.addDirectory(dir, self.getContext(), req, require('get-caller-file')(), opts) 381 return self 382 } 383 384 // TODO: deprecate self.demand in favor of 385 // .demandCommand() .demandOption(). 386 self.demand = self.required = self.require = function demand (keys, max, msg) { 387 // you can optionally provide a 'max' key, 388 // which will raise an exception if too many '_' 389 // options are provided. 390 if (Array.isArray(max)) { 391 max.forEach((key) => { 392 self.demandOption(key, msg) 393 }) 394 max = Infinity 395 } else if (typeof max !== 'number') { 396 msg = max 397 max = Infinity 398 } 399 400 if (typeof keys === 'number') { 401 self.demandCommand(keys, max, msg, msg) 402 } else if (Array.isArray(keys)) { 403 keys.forEach((key) => { 404 self.demandOption(key, msg) 405 }) 406 } else { 407 if (typeof msg === 'string') { 408 self.demandOption(keys, msg) 409 } else if (msg === true || typeof msg === 'undefined') { 410 self.demandOption(keys) 411 } 412 } 413 414 return self 415 } 416 417 self.demandCommand = function demandCommand (min, max, minMsg, maxMsg) { 418 argsert('[number] [number|string] [string|null|undefined] [string|null|undefined]', [min, max, minMsg, maxMsg], arguments.length) 419 420 if (typeof min === 'undefined') min = 1 421 422 if (typeof max !== 'number') { 423 minMsg = max 424 max = Infinity 425 } 426 427 self.global('_', false) 428 429 options.demandedCommands._ = { 430 min, 431 max, 432 minMsg, 433 maxMsg 434 } 435 436 return self 437 } 438 439 self.getDemandedOptions = () => { 440 argsert([], 0) 441 return options.demandedOptions 442 } 443 444 self.getDemandedCommands = () => { 445 argsert([], 0) 446 return options.demandedCommands 447 } 448 449 self.implies = function (key, value) { 450 argsert('<string|object> [number|string|array]', [key, value], arguments.length) 451 validation.implies(key, value) 452 return self 453 } 454 455 self.conflicts = function (key1, key2) { 456 argsert('<string|object> [string|array]', [key1, key2], arguments.length) 457 validation.conflicts(key1, key2) 458 return self 459 } 460 461 self.usage = function (msg, description, builder, handler) { 462 argsert('<string|null|undefined> [string|boolean] [function|object] [function]', [msg, description, builder, handler], arguments.length) 463 464 if (description !== undefined) { 465 // .usage() can be used as an alias for defining 466 // a default command. 467 if ((msg || '').match(/^\$0( |$)/)) { 468 return self.command(msg, description, builder, handler) 469 } else { 470 throw new YError('.usage() description must start with $0 if being used as alias for .command()') 471 } 472 } else { 473 usage.usage(msg) 474 return self 475 } 476 } 477 478 self.epilogue = self.epilog = function (msg) { 479 argsert('<string>', [msg], arguments.length) 480 usage.epilog(msg) 481 return self 482 } 483 484 self.fail = function (f) { 485 argsert('<function>', [f], arguments.length) 486 usage.failFn(f) 487 return self 488 } 489 490 self.check = function (f, _global) { 491 argsert('<function> [boolean]', [f, _global], arguments.length) 492 validation.check(f, _global !== false) 493 return self 494 } 495 496 self.global = function global (globals, global) { 497 argsert('<string|array> [boolean]', [globals, global], arguments.length) 498 globals = [].concat(globals) 499 if (global !== false) { 500 options.local = options.local.filter(l => globals.indexOf(l) === -1) 501 } else { 502 globals.forEach((g) => { 503 if (options.local.indexOf(g) === -1) options.local.push(g) 504 }) 505 } 506 return self 507 } 508 509 self.pkgConf = function pkgConf (key, rootPath) { 510 argsert('<string> [string]', [key, rootPath], arguments.length) 511 let conf = null 512 // prefer cwd to require-main-filename in this method 513 // since we're looking for e.g. "nyc" config in nyc consumer 514 // rather than "yargs" config in nyc (where nyc is the main filename) 515 const obj = pkgUp(rootPath || cwd) 516 517 // If an object exists in the key, add it to options.configObjects 518 if (obj[key] && typeof obj[key] === 'object') { 519 conf = applyExtends(obj[key], rootPath || cwd, self.getParserConfiguration()['deep-merge-config']) 520 options.configObjects = (options.configObjects || []).concat(conf) 521 } 522 523 return self 524 } 525 526 const pkgs = {} 527 function pkgUp (rootPath) { 528 const npath = rootPath || '*' 529 if (pkgs[npath]) return pkgs[npath] 530 const findUp = require('find-up') 531 532 let obj = {} 533 try { 534 let startDir = rootPath || require('require-main-filename')(parentRequire || require) 535 536 // When called in an environment that lacks require.main.filename, such as a jest test runner, 537 // startDir is already process.cwd(), and should not be shortened. 538 // Whether or not it is _actually_ a directory (e.g., extensionless bin) is irrelevant, find-up handles it. 539 if (!rootPath && path.extname(startDir)) { 540 startDir = path.dirname(startDir) 541 } 542 543 const pkgJsonPath = findUp.sync('package.json', { 544 cwd: startDir 545 }) 546 obj = JSON.parse(fs.readFileSync(pkgJsonPath)) 547 } catch (noop) {} 548 549 pkgs[npath] = obj || {} 550 return pkgs[npath] 551 } 552 553 let parseFn = null 554 let parseContext = null 555 self.parse = function parse (args, shortCircuit, _parseFn) { 556 argsert('[string|array] [function|boolean|object] [function]', [args, shortCircuit, _parseFn], arguments.length) 557 freeze() 558 if (typeof args === 'undefined') { 559 const argv = self._parseArgs(processArgs) 560 const tmpParsed = self.parsed 561 unfreeze() 562 // TODO: remove this compatibility hack when we release yargs@15.x: 563 self.parsed = tmpParsed 564 return argv 565 } 566 567 // a context object can optionally be provided, this allows 568 // additional information to be passed to a command handler. 569 if (typeof shortCircuit === 'object') { 570 parseContext = shortCircuit 571 shortCircuit = _parseFn 572 } 573 574 // by providing a function as a second argument to 575 // parse you can capture output that would otherwise 576 // default to printing to stdout/stderr. 577 if (typeof shortCircuit === 'function') { 578 parseFn = shortCircuit 579 shortCircuit = null 580 } 581 // completion short-circuits the parsing process, 582 // skipping validation, etc. 583 if (!shortCircuit) processArgs = args 584 585 if (parseFn) exitProcess = false 586 587 const parsed = self._parseArgs(args, shortCircuit) 588 if (parseFn) parseFn(exitError, parsed, output) 589 unfreeze() 590 591 return parsed 592 } 593 594 self._getParseContext = () => parseContext || {} 595 596 self._hasParseCallback = () => !!parseFn 597 598 self.option = self.options = function option (key, opt) { 599 argsert('<string|object> [object]', [key, opt], arguments.length) 600 if (typeof key === 'object') { 601 Object.keys(key).forEach((k) => { 602 self.options(k, key[k]) 603 }) 604 } else { 605 if (typeof opt !== 'object') { 606 opt = {} 607 } 608 609 options.key[key] = true // track manually set keys. 610 611 if (opt.alias) self.alias(key, opt.alias) 612 613 const demand = opt.demand || opt.required || opt.require 614 615 // deprecated, use 'demandOption' instead 616 if (demand) { 617 self.demand(key, demand) 618 } 619 620 if (opt.demandOption) { 621 self.demandOption(key, typeof opt.demandOption === 'string' ? opt.demandOption : undefined) 622 } 623 624 if ('conflicts' in opt) { 625 self.conflicts(key, opt.conflicts) 626 } 627 628 if ('default' in opt) { 629 self.default(key, opt.default) 630 } 631 632 if ('implies' in opt) { 633 self.implies(key, opt.implies) 634 } 635 636 if ('nargs' in opt) { 637 self.nargs(key, opt.nargs) 638 } 639 640 if (opt.config) { 641 self.config(key, opt.configParser) 642 } 643 644 if (opt.normalize) { 645 self.normalize(key) 646 } 647 648 if ('choices' in opt) { 649 self.choices(key, opt.choices) 650 } 651 652 if ('coerce' in opt) { 653 self.coerce(key, opt.coerce) 654 } 655 656 if ('group' in opt) { 657 self.group(key, opt.group) 658 } 659 660 if (opt.boolean || opt.type === 'boolean') { 661 self.boolean(key) 662 if (opt.alias) self.boolean(opt.alias) 663 } 664 665 if (opt.array || opt.type === 'array') { 666 self.array(key) 667 if (opt.alias) self.array(opt.alias) 668 } 669 670 if (opt.number || opt.type === 'number') { 671 self.number(key) 672 if (opt.alias) self.number(opt.alias) 673 } 674 675 if (opt.string || opt.type === 'string') { 676 self.string(key) 677 if (opt.alias) self.string(opt.alias) 678 } 679 680 if (opt.count || opt.type === 'count') { 681 self.count(key) 682 } 683 684 if (typeof opt.global === 'boolean') { 685 self.global(key, opt.global) 686 } 687 688 if (opt.defaultDescription) { 689 options.defaultDescription[key] = opt.defaultDescription 690 } 691 692 if (opt.skipValidation) { 693 self.skipValidation(key) 694 } 695 696 const desc = opt.describe || opt.description || opt.desc 697 self.describe(key, desc) 698 if (opt.hidden) { 699 self.hide(key) 700 } 701 702 if (opt.requiresArg) { 703 self.requiresArg(key) 704 } 705 } 706 707 return self 708 } 709 self.getOptions = () => options 710 711 self.positional = function (key, opts) { 712 argsert('<string> <object>', [key, opts], arguments.length) 713 if (context.resets === 0) { 714 throw new YError(".positional() can only be called in a command's builder function") 715 } 716 717 // .positional() only supports a subset of the configuration 718 // options available to .option(). 719 const supportedOpts = ['default', 'defaultDescription', 'implies', 'normalize', 720 'choices', 'conflicts', 'coerce', 'type', 'describe', 721 'desc', 'description', 'alias'] 722 opts = objFilter(opts, (k, v) => { 723 let accept = supportedOpts.indexOf(k) !== -1 724 // type can be one of string|number|boolean. 725 if (k === 'type' && ['string', 'number', 'boolean'].indexOf(v) === -1) accept = false 726 return accept 727 }) 728 729 // copy over any settings that can be inferred from the command string. 730 const fullCommand = context.fullCommands[context.fullCommands.length - 1] 731 const parseOptions = fullCommand ? command.cmdToParseOptions(fullCommand) : { 732 array: [], 733 alias: {}, 734 default: {}, 735 demand: {} 736 } 737 Object.keys(parseOptions).forEach((pk) => { 738 if (Array.isArray(parseOptions[pk])) { 739 if (parseOptions[pk].indexOf(key) !== -1) opts[pk] = true 740 } else { 741 if (parseOptions[pk][key] && !(pk in opts)) opts[pk] = parseOptions[pk][key] 742 } 743 }) 744 self.group(key, usage.getPositionalGroupName()) 745 return self.option(key, opts) 746 } 747 748 self.group = function group (opts, groupName) { 749 argsert('<string|array> <string>', [opts, groupName], arguments.length) 750 const existing = preservedGroups[groupName] || groups[groupName] 751 if (preservedGroups[groupName]) { 752 // we now only need to track this group name in groups. 753 delete preservedGroups[groupName] 754 } 755 756 const seen = {} 757 groups[groupName] = (existing || []).concat(opts).filter((key) => { 758 if (seen[key]) return false 759 return (seen[key] = true) 760 }) 761 return self 762 } 763 // combine explicit and preserved groups. explicit groups should be first 764 self.getGroups = () => Object.assign({}, groups, preservedGroups) 765 766 // as long as options.envPrefix is not undefined, 767 // parser will apply env vars matching prefix to argv 768 self.env = function (prefix) { 769 argsert('[string|boolean]', [prefix], arguments.length) 770 if (prefix === false) options.envPrefix = undefined 771 else options.envPrefix = prefix || '' 772 return self 773 } 774 775 self.wrap = function (cols) { 776 argsert('<number|null|undefined>', [cols], arguments.length) 777 usage.wrap(cols) 778 return self 779 } 780 781 let strict = false 782 self.strict = function (enabled) { 783 argsert('[boolean]', [enabled], arguments.length) 784 strict = enabled !== false 785 return self 786 } 787 self.getStrict = () => strict 788 789 let parserConfig = {} 790 self.parserConfiguration = function parserConfiguration (config) { 791 argsert('<object>', [config], arguments.length) 792 parserConfig = config 793 return self 794 } 795 self.getParserConfiguration = () => parserConfig 796 797 self.showHelp = function (level) { 798 argsert('[string|function]', [level], arguments.length) 799 if (!self.parsed) self._parseArgs(processArgs) // run parser, if it has not already been executed. 800 if (command.hasDefaultCommand()) { 801 context.resets++ // override the restriction on top-level positoinals. 802 command.runDefaultBuilderOn(self, true) 803 } 804 usage.showHelp(level) 805 return self 806 } 807 808 let versionOpt = null 809 self.version = function version (opt, msg, ver) { 810 const defaultVersionOpt = 'version' 811 argsert('[boolean|string] [string] [string]', [opt, msg, ver], arguments.length) 812 813 // nuke the key previously configured 814 // to return version #. 815 if (versionOpt) { 816 deleteFromParserHintObject(versionOpt) 817 usage.version(undefined) 818 versionOpt = null 819 } 820 821 if (arguments.length === 0) { 822 ver = guessVersion() 823 opt = defaultVersionOpt 824 } else if (arguments.length === 1) { 825 if (opt === false) { // disable default 'version' key. 826 return self 827 } 828 ver = opt 829 opt = defaultVersionOpt 830 } else if (arguments.length === 2) { 831 ver = msg 832 msg = null 833 } 834 835 versionOpt = typeof opt === 'string' ? opt : defaultVersionOpt 836 msg = msg || usage.deferY18nLookup('Show version number') 837 838 usage.version(ver || undefined) 839 self.boolean(versionOpt) 840 self.describe(versionOpt, msg) 841 return self 842 } 843 844 function guessVersion () { 845 const obj = pkgUp() 846 847 return obj.version || 'unknown' 848 } 849 850 let helpOpt = null 851 self.addHelpOpt = self.help = function addHelpOpt (opt, msg) { 852 const defaultHelpOpt = 'help' 853 argsert('[string|boolean] [string]', [opt, msg], arguments.length) 854 855 // nuke the key previously configured 856 // to return help. 857 if (helpOpt) { 858 deleteFromParserHintObject(helpOpt) 859 helpOpt = null 860 } 861 862 if (arguments.length === 1) { 863 if (opt === false) return self 864 } 865 866 // use arguments, fallback to defaults for opt and msg 867 helpOpt = typeof opt === 'string' ? opt : defaultHelpOpt 868 self.boolean(helpOpt) 869 self.describe(helpOpt, msg || usage.deferY18nLookup('Show help')) 870 return self 871 } 872 873 const defaultShowHiddenOpt = 'show-hidden' 874 options.showHiddenOpt = defaultShowHiddenOpt 875 self.addShowHiddenOpt = self.showHidden = function addShowHiddenOpt (opt, msg) { 876 argsert('[string|boolean] [string]', [opt, msg], arguments.length) 877 878 if (arguments.length === 1) { 879 if (opt === false) return self 880 } 881 882 const showHiddenOpt = typeof opt === 'string' ? opt : defaultShowHiddenOpt 883 self.boolean(showHiddenOpt) 884 self.describe(showHiddenOpt, msg || usage.deferY18nLookup('Show hidden options')) 885 options.showHiddenOpt = showHiddenOpt 886 return self 887 } 888 889 self.hide = function hide (key) { 890 argsert('<string|object>', [key], arguments.length) 891 options.hiddenOptions.push(key) 892 return self 893 } 894 895 self.showHelpOnFail = function showHelpOnFail (enabled, message) { 896 argsert('[boolean|string] [string]', [enabled, message], arguments.length) 897 usage.showHelpOnFail(enabled, message) 898 return self 899 } 900 901 var exitProcess = true 902 self.exitProcess = function (enabled) { 903 argsert('[boolean]', [enabled], arguments.length) 904 if (typeof enabled !== 'boolean') { 905 enabled = true 906 } 907 exitProcess = enabled 908 return self 909 } 910 self.getExitProcess = () => exitProcess 911 912 var completionCommand = null 913 self.completion = function (cmd, desc, fn) { 914 argsert('[string] [string|boolean|function] [function]', [cmd, desc, fn], arguments.length) 915 916 // a function to execute when generating 917 // completions can be provided as the second 918 // or third argument to completion. 919 if (typeof desc === 'function') { 920 fn = desc 921 desc = null 922 } 923 924 // register the completion command. 925 completionCommand = cmd || completionCommand || 'completion' 926 if (!desc && desc !== false) { 927 desc = 'generate completion script' 928 } 929 self.command(completionCommand, desc) 930 931 // a function can be provided 932 if (fn) completion.registerFunction(fn) 933 934 return self 935 } 936 937 self.showCompletionScript = function ($0, cmd) { 938 argsert('[string] [string]', [$0, cmd], arguments.length) 939 $0 = $0 || self.$0 940 _logger.log(completion.generateCompletionScript($0, cmd || completionCommand || 'completion')) 941 return self 942 } 943 944 self.getCompletion = function (args, done) { 945 argsert('<array> <function>', [args, done], arguments.length) 946 completion.getCompletion(args, done) 947 } 948 949 self.locale = function (locale) { 950 argsert('[string]', [locale], arguments.length) 951 if (arguments.length === 0) { 952 guessLocale() 953 return y18n.getLocale() 954 } 955 detectLocale = false 956 y18n.setLocale(locale) 957 return self 958 } 959 960 self.updateStrings = self.updateLocale = function (obj) { 961 argsert('<object>', [obj], arguments.length) 962 detectLocale = false 963 y18n.updateLocale(obj) 964 return self 965 } 966 967 let detectLocale = true 968 self.detectLocale = function (detect) { 969 argsert('<boolean>', [detect], arguments.length) 970 detectLocale = detect 971 return self 972 } 973 self.getDetectLocale = () => detectLocale 974 975 var hasOutput = false 976 var exitError = null 977 // maybe exit, always capture 978 // context about why we wanted to exit. 979 self.exit = (code, err) => { 980 hasOutput = true 981 exitError = err 982 if (exitProcess) process.exit(code) 983 } 984 985 // we use a custom logger that buffers output, 986 // so that we can print to non-CLIs, e.g., chat-bots. 987 const _logger = { 988 log () { 989 const args = [] 990 for (let i = 0; i < arguments.length; i++) args.push(arguments[i]) 991 if (!self._hasParseCallback()) console.log.apply(console, args) 992 hasOutput = true 993 if (output.length) output += '\n' 994 output += args.join(' ') 995 }, 996 error () { 997 const args = [] 998 for (let i = 0; i < arguments.length; i++) args.push(arguments[i]) 999 if (!self._hasParseCallback()) console.error.apply(console, args) 1000 hasOutput = true 1001 if (output.length) output += '\n' 1002 output += args.join(' ') 1003 } 1004 } 1005 self._getLoggerInstance = () => _logger 1006 // has yargs output an error our help 1007 // message in the current execution context. 1008 self._hasOutput = () => hasOutput 1009 1010 self._setHasOutput = () => { 1011 hasOutput = true 1012 } 1013 1014 let recommendCommands 1015 self.recommendCommands = function (recommend) { 1016 argsert('[boolean]', [recommend], arguments.length) 1017 recommendCommands = typeof recommend === 'boolean' ? recommend : true 1018 return self 1019 } 1020 1021 self.getUsageInstance = () => usage 1022 1023 self.getValidationInstance = () => validation 1024 1025 self.getCommandInstance = () => command 1026 1027 self.terminalWidth = () => { 1028 argsert([], 0) 1029 return typeof process.stdout.columns !== 'undefined' ? process.stdout.columns : null 1030 } 1031 1032 Object.defineProperty(self, 'argv', { 1033 get: () => self._parseArgs(processArgs), 1034 enumerable: true 1035 }) 1036 1037 self._parseArgs = function parseArgs (args, shortCircuit, _calledFromCommand, commandIndex) { 1038 let skipValidation = !!_calledFromCommand 1039 args = args || processArgs 1040 1041 options.__ = y18n.__ 1042 options.configuration = self.getParserConfiguration() 1043 // Deprecated 1044 let pkgConfig = pkgUp()['yargs'] 1045 if (pkgConfig) { 1046 console.warn('Configuring yargs through package.json is deprecated and will be removed in a future major release, please use the JS API instead.') 1047 options.configuration = Object.assign({}, pkgConfig, options.configuration) 1048 } 1049 1050 const populateDoubleDash = !!options.configuration['populate--'] 1051 const config = Object.assign({}, options.configuration, { 1052 'populate--': true 1053 }) 1054 const parsed = Parser.detailed(args, Object.assign({}, options, { 1055 configuration: config 1056 })) 1057 1058 let argv = parsed.argv 1059 if (parseContext) argv = Object.assign({}, argv, parseContext) 1060 const aliases = parsed.aliases 1061 1062 argv.$0 = self.$0 1063 self.parsed = parsed 1064 1065 try { 1066 guessLocale() // guess locale lazily, so that it can be turned off in chain. 1067 1068 // while building up the argv object, there 1069 // are two passes through the parser. If completion 1070 // is being performed short-circuit on the first pass. 1071 if (shortCircuit) { 1072 return (populateDoubleDash || _calledFromCommand) ? argv : self._copyDoubleDash(argv) 1073 } 1074 1075 // if there's a handler associated with a 1076 // command defer processing to it. 1077 if (helpOpt) { 1078 // consider any multi-char helpOpt alias as a valid help command 1079 // unless all helpOpt aliases are single-char 1080 // note that parsed.aliases is a normalized bidirectional map :) 1081 const helpCmds = [helpOpt] 1082 .concat(aliases[helpOpt] || []) 1083 .filter(k => k.length > 1) 1084 // check if help should trigger and strip it from _. 1085 if (~helpCmds.indexOf(argv._[argv._.length - 1])) { 1086 argv._.pop() 1087 argv[helpOpt] = true 1088 } 1089 } 1090 1091 const handlerKeys = command.getCommands() 1092 const requestCompletions = completion.completionKey in argv 1093 const skipRecommendation = argv[helpOpt] || requestCompletions 1094 const skipDefaultCommand = skipRecommendation && (handlerKeys.length > 1 || handlerKeys[0] !== '$0') 1095 1096 if (argv._.length) { 1097 if (handlerKeys.length) { 1098 let firstUnknownCommand 1099 for (let i = (commandIndex || 0), cmd; argv._[i] !== undefined; i++) { 1100 cmd = String(argv._[i]) 1101 if (~handlerKeys.indexOf(cmd) && cmd !== completionCommand) { 1102 // commands are executed using a recursive algorithm that executes 1103 // the deepest command first; we keep track of the position in the 1104 // argv._ array that is currently being executed. 1105 const innerArgv = command.runCommand(cmd, self, parsed, i + 1) 1106 return populateDoubleDash ? innerArgv : self._copyDoubleDash(innerArgv) 1107 } else if (!firstUnknownCommand && cmd !== completionCommand) { 1108 firstUnknownCommand = cmd 1109 break 1110 } 1111 } 1112 1113 // run the default command, if defined 1114 if (command.hasDefaultCommand() && !skipDefaultCommand) { 1115 const innerArgv = command.runCommand(null, self, parsed) 1116 return populateDoubleDash ? innerArgv : self._copyDoubleDash(innerArgv) 1117 } 1118 1119 // recommend a command if recommendCommands() has 1120 // been enabled, and no commands were found to execute 1121 if (recommendCommands && firstUnknownCommand && !skipRecommendation) { 1122 validation.recommendCommands(firstUnknownCommand, handlerKeys) 1123 } 1124 } 1125 1126 // generate a completion script for adding to ~/.bashrc. 1127 if (completionCommand && ~argv._.indexOf(completionCommand) && !requestCompletions) { 1128 if (exitProcess) setBlocking(true) 1129 self.showCompletionScript() 1130 self.exit(0) 1131 } 1132 } else if (command.hasDefaultCommand() && !skipDefaultCommand) { 1133 const innerArgv = command.runCommand(null, self, parsed) 1134 return populateDoubleDash ? innerArgv : self._copyDoubleDash(innerArgv) 1135 } 1136 1137 // we must run completions first, a user might 1138 // want to complete the --help or --version option. 1139 if (requestCompletions) { 1140 if (exitProcess) setBlocking(true) 1141 1142 // we allow for asynchronous completions, 1143 // e.g., loading in a list of commands from an API. 1144 const completionArgs = args.slice(args.indexOf(`--${completion.completionKey}`) + 1) 1145 completion.getCompletion(completionArgs, (completions) => { 1146 ;(completions || []).forEach((completion) => { 1147 _logger.log(completion) 1148 }) 1149 1150 self.exit(0) 1151 }) 1152 return (populateDoubleDash || _calledFromCommand) ? argv : self._copyDoubleDash(argv) 1153 } 1154 1155 // Handle 'help' and 'version' options 1156 // if we haven't already output help! 1157 if (!hasOutput) { 1158 Object.keys(argv).forEach((key) => { 1159 if (key === helpOpt && argv[key]) { 1160 if (exitProcess) setBlocking(true) 1161 1162 skipValidation = true 1163 self.showHelp('log') 1164 self.exit(0) 1165 } else if (key === versionOpt && argv[key]) { 1166 if (exitProcess) setBlocking(true) 1167 1168 skipValidation = true 1169 usage.showVersion() 1170 self.exit(0) 1171 } 1172 }) 1173 } 1174 1175 // Check if any of the options to skip validation were provided 1176 if (!skipValidation && options.skipValidation.length > 0) { 1177 skipValidation = Object.keys(argv).some(key => options.skipValidation.indexOf(key) >= 0 && argv[key] === true) 1178 } 1179 1180 // If the help or version options where used and exitProcess is false, 1181 // or if explicitly skipped, we won't run validations. 1182 if (!skipValidation) { 1183 if (parsed.error) throw new YError(parsed.error.message) 1184 1185 // if we're executed via bash completion, don't 1186 // bother with validation. 1187 if (!requestCompletions) { 1188 self._runValidation(argv, aliases, {}, parsed.error) 1189 } 1190 } 1191 } catch (err) { 1192 if (err instanceof YError) usage.fail(err.message, err) 1193 else throw err 1194 } 1195 1196 return (populateDoubleDash || _calledFromCommand) ? argv : self._copyDoubleDash(argv) 1197 } 1198 1199 // to simplify the parsing of positionals in commands, 1200 // we temporarily populate '--' rather than _, with arguments 1201 // after the '--' directive. After the parse, we copy these back. 1202 self._copyDoubleDash = function (argv) { 1203 if (!argv._ || !argv['--']) return argv 1204 argv._.push.apply(argv._, argv['--']) 1205 1206 // TODO(bcoe): refactor command parsing such that this delete is not 1207 // necessary: https://github.com/yargs/yargs/issues/1482 1208 try { 1209 delete argv['--'] 1210 } catch (_err) {} 1211 1212 return argv 1213 } 1214 1215 self._runValidation = function runValidation (argv, aliases, positionalMap, parseErrors) { 1216 if (parseErrors) throw new YError(parseErrors.message || parseErrors) 1217 validation.nonOptionCount(argv) 1218 validation.requiredArguments(argv) 1219 if (strict) validation.unknownArguments(argv, aliases, positionalMap) 1220 validation.customChecks(argv, aliases) 1221 validation.limitedChoices(argv) 1222 validation.implications(argv) 1223 validation.conflicting(argv) 1224 } 1225 1226 function guessLocale () { 1227 if (!detectLocale) return 1228 1229 try { 1230 const { env } = process 1231 const locale = env.LC_ALL || env.LC_MESSAGES || env.LANG || env.LANGUAGE || 'en_US' 1232 self.locale(locale.replace(/[.:].*/, '')) 1233 } catch (err) { 1234 // if we explode looking up locale just noop 1235 // we'll keep using the default language 'en'. 1236 } 1237 } 1238 1239 // an app should almost always have --version and --help, 1240 // if you *really* want to disable this use .help(false)/.version(false). 1241 self.help() 1242 self.version() 1243 1244 return self 1245} 1246 1247// rebase an absolute path to a relative one with respect to a base directory 1248// exported for tests 1249exports.rebase = rebase 1250function rebase (base, dir) { 1251 return path.relative(base, dir) 1252} 1253