Repository: yargs/yargs
Pull request #678 (2016-10-18 00:56:02)

fix: context must be reset, or nested sub-commands fail on second invocation
This addresses an issue @ceejbot ran into using headless `.parse()` in conjunction with nested sub-commands.

CC: @nexdrew 

nexdrew: Hmm, the whole point of the generic `context` is to keep track of things that _should not_ be reset. I honestly don't know how subcommands would work at all if we're resetting this, since it's used to determine which level of command is currently being executed and which files (for `.commandDir()`) have been visited (to account for potential cyclic directory refs).

I really need to test this thoroughly and would argue for a different solution.
bcoe: @nexdrew keep in mind that the only time we reset the context object is in-between distinct runs of 

`.parse(rawArgs, context, parseFn)`

The nested command handlers do invoke `.parse()` but they invoke it without `parseFn`; so `unfreeze` will not be executed until the entire stack has popped. 
nexdrew: Ok, resetting the "context" makes more sense in the `unfreeze()` function (as opposed to the `reset()` method), but, at the risk of sounding silly, I must admit this has me baffled; I don't see the connection between the `parseContext` var (set from the 2nd arg to `.parse()` and applied to `argv` for the command handler) and the `context` var (private state used for subcommand scoping, never applied to `argv`) at all.

At the risk of sounding silly, I must admit this is comole
bcoe: @nexdrew did a bit of digging, this is the underlying issue here:

```js
      if (visited) {
        // check for cyclic reference
        // each command file path should only be seen once per execution
        if (~context.files.indexOf(joined)) return visited
        // keep track of visited files in context.files
        context.files.push(joined)
        self.addHandler(visited)
      }
```

Because `files` is left in the context object, on the next run of the parser we don't re-add handlers.
nexdrew: Ok, wow, good find. I didn't realize the problem is specific to a command that uses `.commandDir()` in its `builder` function. This makes sense now.

(Also, I apologize for my presumptive knee-jerk reaction! 😔 I should have taken more time to understand/diagnose the problem before responding. Thanks for keeping a level head and finding the root cause for us, which you have now had to fix twice! 😳)

Let me do a bit of experimenting to see if I can make the `context` management more robust. Otherwise, we could get away with simply resetting the `context.files` array.
nexdrew: @bcoe See #680 (which is a PR against the `nested-command-fix` branch) for my preferred solution to this problem. If you're ok with it, we can merge 680 first and then merge 678.

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 129dd2d..319646d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -102,7 +102,6 @@ All notable changes to this project will be documented in this file. See [standa
 * adds recommendCommands() for command suggestions ([#580](https://github.com/yargs/yargs/issues/580)) ([59474dc](https://github.com/yargs/yargs/commit/59474dc))
 * apply .env() globally ([#553](https://github.com/yargs/yargs/issues/553)) ([be65728](https://github.com/yargs/yargs/commit/be65728))
 * apply default builder to command() and apply fail() handlers globally ([#583](https://github.com/yargs/yargs/issues/583)) ([0aaa68b](https://github.com/yargs/yargs/commit/0aaa68b))
-* interpret demand() numbers as relative to executing command ([#582](https://github.com/yargs/yargs/issues/582)) ([927810c](https://github.com/yargs/yargs/commit/927810c))
 * update yargs-parser to version 3.1.0 ([#581](https://github.com/yargs/yargs/issues/581)) ([882a127](https://github.com/yargs/yargs/commit/882a127))
 
 
@@ -118,6 +117,7 @@ All notable changes to this project will be documented in this file. See [standa
 * yargs-parser now better handles negative integer values, at the cost of handling numeric option names, e.g., -1 hello
 * default: removed undocumented `defaults` alias for `default`.
 * introduces a default `help` command which outputs help, as an alternative to a help flag.
+* interpret demand() numbers as relative to executing command ([#582](https://github.com/yargs/yargs/issues/582)) ([927810c](https://github.com/yargs/yargs/commit/927810c))
 
 
 
diff --git a/README.md b/README.md
index 6451af3..e5aa460 100644
--- a/README.md
+++ b/README.md
@@ -1352,6 +1352,7 @@ Locales currently supported:
 * **pt_BR:** Brazilian Portuguese.
 * **tr:** Turkish.
 * **zh:** Chinese.
+* **th:** Thai.
 
 To submit a new translation for yargs:
 
diff --git a/lib/command.js b/lib/command.js
index 128b750..e73340d 100644
--- a/lib/command.js
+++ b/lib/command.js
@@ -134,6 +134,7 @@ module.exports = function (yargs, usage, validation) {
     var commandHandler = handlers[command] || handlers[aliasMap[command]]
     var innerArgv = argv
     var currentContext = yargs.getContext()
+    var numFiles = currentContext.files.length
     var parentCommands = currentContext.commands.slice()
     currentContext.commands.push(command)
     if (typeof commandHandler.builder === 'function') {
@@ -168,6 +169,8 @@ module.exports = function (yargs, usage, validation) {
       commandHandler.handler(innerArgv)
     }
     currentContext.commands.pop()
+    numFiles = currentContext.files.length - numFiles
+    if (numFiles > 0) currentContext.files.splice(numFiles * -1, numFiles)
     return innerArgv
   }
 
diff --git a/locales/th.json b/locales/th.json
new file mode 100644
index 0000000..3f08dcd
--- /dev/null
+++ b/locales/th.json
@@ -0,0 +1,39 @@
+{
+  "Commands:": "คอมมาน",
+  "Options:": "ออฟชั่น",
+  "Examples:": "ตัวอย่าง",
+  "boolean": "บูลีน",
+  "count": "นับ",
+  "string": "สตริง",
+  "number": "ตัวเลข",
+  "array": "อาเรย์",
+  "required": "จำเป็น",
+  "default:": "ค่าเริ่มต้น",
+  "choices:": "ตัวเลือก",
+  "aliases:": "เอเลียส",
+  "generated-value": "ค่าที่ถูกสร้างขึ้น",
+  "Not enough non-option arguments: got %s, need at least %s": "ใส่อาร์กิวเมนต์ไม่ครบตามจำนวนที่กำหนด: ใส่ค่ามาจำนวน %s ค่า, แต่ต้องการอย่างน้อย %s ค่า",
+  "Too many non-option arguments: got %s, maximum of %s": "ใส่อาร์กิวเมนต์เกินจำนวนที่กำหนด: ใส่ค่ามาจำนวน %s ค่า, แต่ต้องการมากที่สุด %s ค่า",
+  "Missing argument value: %s": {
+    "one": "ค่าอาร์กิวเมนต์ที่ขาดไป: %s",
+    "other": "ค่าอาร์กิวเมนต์ที่ขาดไป: %s"
+  },
+  "Missing required argument: %s": {
+    "one": "อาร์กิวเมนต์จำเป็นที่ขาดไป: %s",
+    "other": "อาร์กิวเมนต์จำเป็นที่ขาดไป: %s"
+  },
+  "Unknown argument: %s": {
+    "one": "อาร์กิวเมนต์ที่ไม่รู้จัก: %s",
+    "other": "อาร์กิวเมนต์ที่ไม่รู้จัก: %s"
+  },
+  "Invalid values:": "ค่าไม่ถูกต้อง:",
+  "Argument: %s, Given: %s, Choices: %s": "อาร์กิวเมนต์: %s, ได้รับ: %s, ตัวเลือก: %s",
+  "Argument check failed: %s": "ตรวจสอบพบอาร์กิวเมนต์ที่ไม่ถูกต้อง: %s",
+  "Implications failed:": "Implications ไม่สำเร็จ:",
+  "Not enough arguments following: %s": "ใส่อาร์กิวเมนต์ไม่ครบ: %s",
+  "Invalid JSON config file: %s": "ไฟล์คอนฟิค JSON ไม่ถูกต้อง: %s",
+  "Path to JSON config file": "พาทไฟล์คอนฟิค JSON",
+  "Show help": "ขอความช่วยเหลือ",
+  "Show version number": "แสดงตัวเลขเวอร์ชั่น",
+  "Did you mean %s?": "คุณหมายถึง %s?"
+}


Test patch:
diff --git a/test/fixtures/cmddir/deep/within-a-dream.js b/test/fixtures/cmddir/deep/within-a-dream.js
index af9e7f7..b6d2f19 100644
--- a/test/fixtures/cmddir/deep/within-a-dream.js
+++ b/test/fixtures/cmddir/deep/within-a-dream.js
@@ -12,6 +12,7 @@ module.exports = {
   },
   handler: function (argv) {
     var factor = 7
+    if (argv.context) argv.context.counter++ // keep track of how many times we've invoked this handler.
     if (argv.extract) {
       if (!argv.withKick) factor -= 2
       if (!chancesLevel2(factor)) throw new Error('Something went wrong at level 2! Check your options for increased chance of success.')
diff --git a/test/yargs.js b/test/yargs.js
index a28fcac..f2339b7 100644
--- a/test/yargs.js
+++ b/test/yargs.js
@@ -954,6 +954,21 @@ describe('yargs dsl tests', function () {
         argv.what.should.equal(true)
       })
 
+      it('allows nested sub-commands to be invoked multiple times', function () {
+        var context = {counter: 0}
+
+        checkOutput(function () {
+          var parser = yargs()
+            .commandDir('fixtures/cmddir')
+
+          parser.parse('dream within-a-dream --what', {context: context}, function (_err, argv, _output) {})
+          parser.parse('dream within-a-dream --what', {context: context}, function (_err, argv, _output) {})
+          parser.parse('dream within-a-dream --what', {context: context}, function (_err, argv, _output) {})
+        })
+
+        context.counter.should.equal(3)
+      })
+
       it('overwrites the prior context object, when parse is called multiple times', function () {
         var argv = null
         var parser = yargs()