diff --git a/docs/api.md b/docs/api.md
index e6659dd79..9f55c0788 100644
--- a/docs/api.md
+++ b/docs/api.md
@@ -1401,6 +1401,13 @@ corresponding description, will be reported as an error.
Unrecognized commands will also be reported as errors.
+.strictCommands([enabled=true])
+---------
+
+Similar to `.strict()`, except that it only applies to unrecognized commands. A
+user can still provide arbitrary options, but unknown positional commands
+will raise an error.
+
.string(key)
------------
diff --git a/lib/validation.js b/lib/validation.js
index 35659a356..5af0c724b 100644
--- a/lib/validation.js
+++ b/lib/validation.js
@@ -120,6 +120,32 @@ module.exports = function validation (yargs, usage, y18n) {
}
}
+ self.unknownCommands = function unknownCommands (argv, aliases, positionalMap) {
+ const commandKeys = yargs.getCommandInstance().getCommands()
+ const unknown = []
+ const currentContext = yargs.getContext()
+
+ if ((currentContext.commands.length > 0) || (commandKeys.length > 0)) {
+ argv._.slice(currentContext.commands.length).forEach((key) => {
+ if (commandKeys.indexOf(key) === -1) {
+ unknown.push(key)
+ }
+ })
+ }
+
+ if (unknown.length > 0) {
+ usage.fail(__n(
+ 'Unknown command: %s',
+ 'Unknown commands: %s',
+ unknown.length,
+ unknown.join(', ')
+ ))
+ return true
+ } else {
+ return false
+ }
+ }
+
// check for a key that is not an alias, or for which every alias is new,
// implying that it was invented by the parser, e.g., during camelization
self.isValidAndSomeAliasIsNotNew = function isValidAndSomeAliasIsNotNew (key, aliases) {
diff --git a/test/validation.js b/test/validation.js
index e9dc0d65a..ebd7d7331 100644
--- a/test/validation.js
+++ b/test/validation.js
@@ -939,4 +939,91 @@ describe('validation tests', () => {
.parse()
})
})
+
+ describe('strictCommands', () => {
+ it('succeeds in parse if command is known', () => {
+ const parsed = yargs('foo -a 10')
+ .strictCommands()
+ .command('foo', 'foo command')
+ .parse()
+ parsed.a.should.equal(10)
+ parsed._.should.eql(['foo'])
+ })
+
+ it('succeeds in parse if top level and inner command are known', () => {
+ const parsed = yargs('foo bar --cool beans')
+ .strictCommands()
+ .command('foo', 'foo command', (yargs) => {
+ yargs.command('bar')
+ })
+ .parse()
+ parsed.cool.should.equal('beans')
+ parsed._.should.eql(['foo', 'bar'])
+ })
+
+ it('fails with error if command is unknown', (done) => {
+ yargs('blerg -a 10')
+ .strictCommands()
+ .command('foo', 'foo command')
+ .fail((msg) => {
+ msg.should.equal('Unknown command: blerg')
+ return done()
+ })
+ .parse()
+ })
+
+ it('fails with error if inner command is unknown', (done) => {
+ yargs('foo blarg --cool beans')
+ .strictCommands()
+ .command('foo', 'foo command', (yargs) => {
+ yargs.command('bar')
+ })
+ .fail((msg) => {
+ msg.should.equal('Unknown command: blarg')
+ return done()
+ })
+ .parse()
+ })
+
+ // TODO(bcoe): consider implementing this behvaior in the next major version of yargs:
+ //
+ // // for the special case of yargs.demandCommand() and yargs.demandCommand(1), if
+ // // yargs has been configured with commands, we automatically enable strictCommands.
+ // if (commandKeys.length && demandedCommands._ && demandedCommands._.min === 1 && demandedCommands._.max === Infinity) {
+ // yargs.strictCommands()
+ // }
+ // it('enables strict commands if commands used in conjunction with demandCommand', (done) => {
+ // yargs('blerg -a 10')
+ // .demandCommand()
+ // .command('foo', 'foo command')
+ // .fail((msg) => {
+ // msg.should.equal('Unknown command: blerg')
+ // return done()
+ // })
+ // .parse()
+ // })
+
+ it('does not apply implicit strictCommands to inner commands', () => {
+ const parse = yargs('foo blarg --cool beans')
+ .demandCommand()
+ .command('foo', 'foo command', (yargs) => {
+ yargs.command('bar')
+ })
+ .parse()
+ parse.cool.should.equal('beans')
+ parse._.should.eql(['foo', 'blarg'])
+ })
+
+ it('allows strictCommands to be applied to inner commands', (done) => {
+ yargs('foo blarg')
+ .command('foo', 'foo command', (yargs) => {
+ yargs.command('bar').strictCommands()
+ })
+ .fail((msg) => {
+ msg.should.equal('Unknown command: blarg')
+ return done()
+ })
+ .parse()
+ })
+ })
})
diff --git a/yargs.js b/yargs.js
index d9a30ac5b..238900f58 100644
--- a/yargs.js
+++ b/yargs.js
@@ -167,6 +167,7 @@ function Yargs (processArgs, cwd, parentRequire) {
validation.freeze()
command.freeze()
frozen.strict = strict
+ frozen.strictCommands = strictCommands
frozen.completionCommand = completionCommand
frozen.output = output
frozen.exitError = exitError
@@ -190,6 +191,7 @@ function Yargs (processArgs, cwd, parentRequire) {
validation.unfreeze()
command.unfreeze()
strict = frozen.strict
+ strictCommands = frozen.strictCommands
completionCommand = frozen.completionCommand
parseFn = frozen.parseFn
parseContext = frozen.parseContext
@@ -795,6 +797,14 @@ function Yargs (processArgs, cwd, parentRequire) {
}
self.getStrict = () => strict
+ let strictCommands = false
+ self.strictCommands = function (enabled) {
+ argsert('[boolean]', [enabled], arguments.length)
+ strictCommands = enabled !== false
+ return self
+ }
+ self.getStrictCommands = () => strictCommands
+
let parserConfig = {}
self.parserConfiguration = function parserConfiguration (config) {
argsert('