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