Skip to content

Commit ab1fa4b

Browse files
committed
feat: rethink how options are inherited by commands (#766)
BREAKING CHANGE: by default options, and many of yargs' parsing helpers will now default to being applied globally; such that they are no-longer reset before being passed into commands.
1 parent 8308efa commit ab1fa4b

File tree

9 files changed

+553
-229
lines changed

9 files changed

+553
-229
lines changed

README.md

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -408,7 +408,7 @@ explicitly set.
408408

409409
If `key` is an array, interpret all the elements as booleans.
410410

411-
.check(fn)
411+
.check(fn, [global=true])
412412
----------
413413

414414
Check that certain conditions are met in the provided arguments.
@@ -418,6 +418,9 @@ Check that certain conditions are met in the provided arguments.
418418
If `fn` throws or returns a non-truthy value, show the thrown error, usage information, and
419419
exit.
420420

421+
`global` indicates whether `check()` should be enabled both
422+
at the top-level and for each sub-command.
423+
421424
<a name="choices"></a>.choices(key, choices)
422425
----------------------
423426

@@ -551,14 +554,6 @@ yargs
551554
.argv
552555
```
553556

554-
Note that commands will not automatically inherit configuration _or_ options
555-
of their parent context. This means you'll have to re-apply configuration
556-
if necessary, and make options global manually using the [global](#global) method.
557-
558-
Additionally, the [`help`](#help) and [`version`](#version)
559-
options (if used) **always** apply globally, just like the
560-
[`.wrap()`](#wrap) configuration.
561-
562557
`builder` can also be a function. This function is executed
563558
with a `yargs` instance, and can be used to provide _advanced_ command specific help:
564559

@@ -678,7 +673,7 @@ require('yargs')
678673
console.log(`setting ${argv.key} to ${argv.value}`)
679674
}
680675
})
681-
.demandCommand(1)
676+
.demandCommand()
682677
.help()
683678
.wrap(72)
684679
.argv
@@ -824,7 +819,7 @@ cli.js:
824819
#!/usr/bin/env node
825820
require('yargs')
826821
.commandDir('cmds')
827-
.demandCommand(1)
822+
.demandCommand()
828823
.help()
829824
.argv
830825
```
@@ -1112,9 +1107,9 @@ Options:
11121107
Missing required arguments: run, path
11131108
```
11141109

1115-
<a name="demandCommand"></a>.demandCommand(min, [minMsg])
1110+
<a name="demandCommand"></a>.demandCommand([min=1], [minMsg])
11161111
------------------------------
1117-
.demandCommand(min, [max], [minMsg], [maxMsg])
1112+
.demandCommand([min=1], [max], [minMsg], [maxMsg])
11181113
------------------------------
11191114

11201115
Demand in context of commands. You can demand a minimum and a maximum number a user can have within your program, as well as provide corresponding error messages if either of the demands is not met.
@@ -1134,7 +1129,9 @@ require('yargs')
11341129
.help()
11351130
.argv
11361131
```
1132+
11371133
which will provide the following output:
1134+
11381135
```bash
11391136
Commands:
11401137
configure <key> [value] Set a config variable [aliases: config, cfg]
@@ -1293,7 +1290,7 @@ require('yargs')
12931290

12941291
Outputs the same completion choices as `./test.js --foo`<kbd>TAB</kbd>: `--foobar` and `--foobaz`
12951292

1296-
<a name="global"></a>.global(globals)
1293+
<a name="global"></a>.global(globals, [global=true])
12971294
------------
12981295

12991296
Indicate that an option (or group of options) should not be reset when a command
@@ -1303,11 +1300,13 @@ is executed, as an example:
13031300
var argv = require('yargs')
13041301
.option('a', {
13051302
alias: 'all',
1306-
default: true
1303+
default: true,
1304+
global: false
13071305
})
13081306
.option('n', {
13091307
alias: 'none',
1310-
default: true
1308+
default: true,
1309+
global: false
13111310
})
13121311
.command('foo', 'foo command', function (yargs) {
13131312
return yargs.option('b', {
@@ -1322,7 +1321,7 @@ var argv = require('yargs')
13221321
If the `foo` command is executed the `all` option will remain, but the `none`
13231322
option will have been eliminated.
13241323

1325-
`help`, `version`, and `completion` options default to being global.
1324+
Options default to being global.
13261325

13271326
<a name="group"></a>.group(key(s), groupName)
13281327
--------------------
@@ -1778,12 +1777,15 @@ Specify --help for available options
17781777
Specifies either a single option key (string), or an array of options.
17791778
If any of the options is present, yargs validation is skipped.
17801779

1781-
.strict()
1780+
.strict([global=true])
17821781
---------
17831782

17841783
Any command-line argument given that is not demanded, or does not have a
17851784
corresponding description, will be reported as an error.
17861785

1786+
`global` indicates whether `strict()` should be enabled both
1787+
at the top-level and for each sub-command.
1788+
17871789
<a name="string"></a>.string(key)
17881790
------------
17891791

lib/command.js

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -131,47 +131,62 @@ module.exports = function (yargs, usage, validation) {
131131
}
132132

133133
self.runCommand = function (command, yargs, parsed) {
134-
var argv = parsed.argv
134+
var aliases = parsed.aliases
135135
var commandHandler = handlers[command] || handlers[aliasMap[command]]
136-
var innerArgv = argv
137136
var currentContext = yargs.getContext()
138137
var numFiles = currentContext.files.length
139138
var parentCommands = currentContext.commands.slice()
139+
140+
// what does yargs look like after the buidler is run?
141+
var innerArgv = parsed.argv
142+
var innerYargs = null
143+
140144
currentContext.commands.push(command)
141145
if (typeof commandHandler.builder === 'function') {
142146
// a function can be provided, which builds
143147
// up a yargs chain and possibly returns it.
144-
innerArgv = commandHandler.builder(yargs.reset(parsed.aliases))
148+
innerYargs = commandHandler.builder(yargs.reset(parsed.aliases))
145149
// if the builder function did not yet parse argv with reset yargs
146150
// and did not explicitly set a usage() string, then apply the
147151
// original command string as usage() for consistent behavior with
148-
// options object below
152+
// options object below.
149153
if (yargs.parsed === false) {
150154
if (typeof yargs.getUsageInstance().getUsage() === 'undefined') {
151155
yargs.usage('$0 ' + (parentCommands.length ? parentCommands.join(' ') + ' ' : '') + commandHandler.original)
152156
}
153-
innerArgv = innerArgv ? innerArgv.argv : yargs.argv
157+
innerArgv = innerYargs ? innerYargs._parseArgs(null, null, true) : yargs._parseArgs(null, null, true)
154158
} else {
155159
innerArgv = yargs.parsed.argv
156160
}
161+
162+
if (innerYargs && yargs.parsed === false) aliases = innerYargs.parsed.aliases
163+
else aliases = yargs.parsed.aliases
157164
} else if (typeof commandHandler.builder === 'object') {
158165
// as a short hand, an object can instead be provided, specifying
159166
// the options that a command takes.
160-
innerArgv = yargs.reset(parsed.aliases)
161-
innerArgv.usage('$0 ' + (parentCommands.length ? parentCommands.join(' ') + ' ' : '') + commandHandler.original)
167+
innerYargs = yargs.reset(parsed.aliases)
168+
innerYargs.usage('$0 ' + (parentCommands.length ? parentCommands.join(' ') + ' ' : '') + commandHandler.original)
162169
Object.keys(commandHandler.builder).forEach(function (key) {
163-
innerArgv.option(key, commandHandler.builder[key])
170+
innerYargs.option(key, commandHandler.builder[key])
164171
})
165-
innerArgv = innerArgv.argv
172+
innerArgv = innerYargs._parseArgs(null, null, true)
173+
aliases = innerYargs.parsed.aliases
166174
}
175+
167176
if (!yargs._hasOutput()) populatePositionals(commandHandler, innerArgv, currentContext, yargs)
168177

169178
if (commandHandler.handler && !yargs._hasOutput()) {
170179
commandHandler.handler(innerArgv)
171180
}
181+
182+
// we apply validation post-hoc, so that custom
183+
// checks get passed populated positional arguments.
184+
yargs._runValidation(innerArgv, aliases)
185+
172186
currentContext.commands.pop()
173187
numFiles = currentContext.files.length - numFiles
174188
if (numFiles > 0) currentContext.files.splice(numFiles * -1, numFiles)
189+
175190
return innerArgv
176191
}
177192

lib/usage.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -436,7 +436,7 @@ module.exports = function (yargs, y18n) {
436436
else logger.log(version)
437437
}
438438

439-
self.reset = function (globalLookup) {
439+
self.reset = function (localLookup) {
440440
// do not reset wrap here
441441
// do not reset fails here
442442
failMessage = null
@@ -446,7 +446,7 @@ module.exports = function (yargs, y18n) {
446446
examples = []
447447
commands = []
448448
descriptions = objFilter(descriptions, function (k, v) {
449-
return globalLookup[k]
449+
return !localLookup[k]
450450
})
451451
return self
452452
}

lib/validation.js

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -194,22 +194,26 @@ module.exports = function (yargs, usage, y18n) {
194194

195195
// custom checks, added using the `check` option on yargs.
196196
var checks = []
197-
self.check = function (f) {
198-
checks.push(f)
197+
self.check = function (f, global) {
198+
checks.push({
199+
func: f,
200+
global: global
201+
})
199202
}
200203

201204
self.customChecks = function (argv, aliases) {
202205
for (var i = 0, f; (f = checks[i]) !== undefined; i++) {
206+
var func = f.func
203207
var result = null
204208
try {
205-
result = f(argv, aliases)
209+
result = func(argv, aliases)
206210
} catch (err) {
207211
usage.fail(err.message ? err.message : err, err)
208212
continue
209213
}
210214

211215
if (!result) {
212-
usage.fail(__('Argument check failed: %s', f.toString()))
216+
usage.fail(__('Argument check failed: %s', func.toString()))
213217
} else if (typeof result === 'string' || result instanceof Error) {
214218
usage.fail(result.toString(), result)
215219
}
@@ -224,6 +228,7 @@ module.exports = function (yargs, usage, y18n) {
224228
self.implies(k, key[k])
225229
})
226230
} else {
231+
yargs.global(key)
227232
implied[key] = value
228233
}
229234
}
@@ -290,6 +295,7 @@ module.exports = function (yargs, usage, y18n) {
290295
self.conflicts(k, key[k])
291296
})
292297
} else {
298+
yargs.global(key)
293299
conflicting[key] = value
294300
}
295301
}
@@ -324,12 +330,16 @@ module.exports = function (yargs, usage, y18n) {
324330
if (recommended) usage.fail(__('Did you mean %s?', recommended))
325331
}
326332

327-
self.reset = function (globalLookup) {
333+
self.reset = function (localLookup) {
328334
implied = objFilter(implied, function (k, v) {
329-
return globalLookup[k]
335+
return !localLookup[k]
336+
})
337+
conflicting = objFilter(conflicting, function (k, v) {
338+
return !localLookup[k]
339+
})
340+
checks = checks.filter(function (c) {
341+
return c.global
330342
})
331-
checks = []
332-
conflicting = {}
333343
return self
334344
}
335345

0 commit comments

Comments
 (0)