Repository: yargs/yargs
Pull request #857 (2017-04-15 21:35:29)

fix: commands are now applied in order, from left to right
BREAKING CHANGE: commands will now not be executed if they are proceeded by another positional argument

fixes: #853 

CC: @gajus


Patch:
diff --git a/lib/command.js b/lib/command.js
index c49555a..d66d288 100644
--- a/lib/command.js
+++ b/lib/command.js
@@ -174,7 +174,7 @@ module.exports = function (yargs, usage, validation) {
     return !!defaultCommand
   }
 
-  self.runCommand = function (command, yargs, parsed) {
+  self.runCommand = function (command, yargs, parsed, commandIndex) {
     var aliases = parsed.aliases
     var commandHandler = handlers[command] || handlers[aliasMap[command]] || defaultCommand
     var currentContext = yargs.getContext()
@@ -199,7 +199,7 @@ module.exports = function (yargs, usage, validation) {
         if (typeof yargs.getUsageInstance().getUsage() === 'undefined') {
           yargs.usage('$0 ' + (parentCommands.length ? parentCommands.join(' ') + ' ' : '') + commandHandler.original)
         }
-        innerArgv = innerYargs ? innerYargs._parseArgs(null, null, true) : yargs._parseArgs(null, null, true)
+        innerArgv = innerYargs ? innerYargs._parseArgs(null, null, true, commandIndex) : yargs._parseArgs(null, null, true, commandIndex)
       } else {
         innerArgv = yargs.parsed.argv
       }
@@ -214,7 +214,7 @@ module.exports = function (yargs, usage, validation) {
       Object.keys(commandHandler.builder).forEach(function (key) {
         innerYargs.option(key, commandHandler.builder[key])
       })
-      innerArgv = innerYargs._parseArgs(null, null, true)
+      innerArgv = innerYargs._parseArgs(null, null, true, commandIndex)
       aliases = innerYargs.parsed.aliases
     }
 
diff --git a/yargs.js b/yargs.js
index 92eb38f..9e05e41 100644
--- a/yargs.js
+++ b/yargs.js
@@ -932,7 +932,7 @@ function Yargs (processArgs, cwd, parentRequire) {
     enumerable: true
   })
 
-  self._parseArgs = function (args, shortCircuit, _skipValidation) {
+  self._parseArgs = function (args, shortCircuit, _skipValidation, commandIndex) {
     var skipValidation = !!_skipValidation
     args = args || processArgs
 
@@ -983,13 +983,17 @@ function Yargs (processArgs, cwd, parentRequire) {
         var handlerKeys = command.getCommands()
         if (handlerKeys.length) {
           var firstUnknownCommand
-          for (var i = 0, cmd; argv._[i] !== undefined; i++) {
+          for (var i = (commandIndex || 0), cmd; argv._[i] !== undefined; i++) {
             cmd = String(argv._[i])
             if (~handlerKeys.indexOf(cmd) && cmd !== completionCommand) {
               setPlaceholderKeys(argv)
-              return command.runCommand(cmd, self, parsed)
+              // commands are executed using a recursive algorithm that executes
+              // the deepest command first; we keep track of the position in the
+              // argv._ array that is currently being executed.
+              return command.runCommand(cmd, self, parsed, i + 1)
             } else if (!firstUnknownCommand && cmd !== completionCommand) {
               firstUnknownCommand = cmd
+              break
             }
           }
 


Test patch:
diff --git a/test/command.js b/test/command.js
index 4d50af7..fb7e449 100644
--- a/test/command.js
+++ b/test/command.js
@@ -1327,4 +1327,16 @@ describe('Command', function () {
       .argv
     output.should.include('1')
   })
+
+  // see: https://github.com/yargs/yargs/issues/853
+  it('should not execute command if it is proceeded by another positional argument', function () {
+    var commandCalled = false
+    yargs()
+      .command('foo', 'foo command', function () {}, function () { commandCalled = true })
+      .parse('bar foo', function (err, argv) {
+        expect(err).to.equal(null)
+        commandCalled.should.equal(false)
+        argv._.should.eql(['bar', 'foo'])
+      })
+  })
 })