Repository: yargs/yargs
Pull request #1001 (2017-11-01 15:50:23)

Async command handlers
Addresses issue #510 ~~"Allow command handler to accept a promise"~~ "Support async (i.e. promise-returning) command handlers"

> we'd just need to update the argument validation and invocation of a command handler to understand a promise

This PR handles "update [...] invocation of a command handler to understand a promise". Nothing is done if the promise resolves, but if it rejects, it is handled as ["failure"](https://github.com/zenflow/yargs/blob/master/docs/api.md#failfn) with an `err` but no `msg`.

@bcoe Not sure what needs to be done about "update the argument validation"... It seems like there's no change needed; arguments to command handlers, and validation of said arguments, aren't affected by this feature. This feature deals only with what is *returned* by the command handler (a promise, potentially). Am I missing something?

This PR also makes a slight change to the default failure handler, so that if there's no `msg` but there is an `err`, `err` will be logged instead, which will display the full stack trace for debugging.

Thank you for your work on this project, and taking the time to review this change :)

zcei: Looks really promising! 👏  (pun intended 😉 )

> Not sure what needs to be done about "update the argument validation"... It seems like there's no change needed

This is a whole different topic, as async argument validation would turn whole `yargs` library into getting async, as the `.argv` cannot be available before the async validation succeeded.

As a workaround, you could throw a specific error (e.g. `UnmatchedAsyncConstraint`) and handle this in the `.fail` method (i.e. show the usage help text), together with your generic failure handling.

So getting this enhancement in without async argument validation would be awesome!
zenflow: @zcei It's promising, and also thenabling 😉 (Jokes, How long have you been a dad btw? My kids are 8 and 10, Jokes)

> This is a whole different topic, as async argument validation would turn whole yargs library into getting async, as the .argv cannot be available before the async validation succeeded.

Ah, that explains it. Thanks. (D'uh)

Yeah changing yargs to do validation asynchronously seems like it have to have some significant breaking changes, since (correct me if I'm getting this wrong) it must be either always async or always sync, or [Zalgo](http://blog.izs.me/post/59142742143/designing-apis-for-asynchrony) get's released.

Maybe we should open a separate issue for that?
bcoe: @zenflow think you'll have a chance to see this over the finish line? otherwise hopefully I'll have some time over the holiday.
zenflow: @bcoe Sorry for all the delay - I'm finally free from work for a while now, after a few mad weeks of finishing up, documenting, handing off, etc.

I'll definitely have time to finish this off over the next week :)

Patch:
diff --git a/README.md b/README.md
index 6f25e99..d16b82d 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,5 @@
 # Yargs
 
-_Yargs is developed on evenings and weekends by
-volunteers. Why not grab them dinner or a drink?_
-
 [![Build Status][travis-image]][travis-url]
 [![Coverage Status][coveralls-image]][coveralls-url]
 [![NPM version][npm-image]][npm-url]
diff --git a/docs/api.md b/docs/api.md
index 28f9c55..193056b 100644
--- a/docs/api.md
+++ b/docs/api.md
@@ -664,10 +664,10 @@ Examples will be printed out as part of the help message.
 <a name="exitprocess"></a>.exitProcess(enable)
 ----------------------------------
 
-By default, yargs exits the process when the user passes a help flag, uses the
-`.version` functionality, or when validation fails. Calling
-`.exitProcess(false)` disables this behavior, enabling further actions after
-yargs have been validated.
+By default, yargs exits the process when the user passes a help flag, the user
+uses the `.version` functionality, validation fails, or the command handler
+fails. Calling `.exitProcess(false)` disables this behavior, enabling further
+actions after yargs have been validated.
 
 <a name="fail"></a>.fail(fn)
 ---------
diff --git a/lib/command.js b/lib/command.js
index 395c7c6..65322db 100644
--- a/lib/command.js
+++ b/lib/command.js
@@ -232,7 +232,13 @@ module.exports = function command (yargs, usage, validation) {
         }, {})
         Object.assign(innerArgv, middlewareArgs)
       }
-      commandHandler.handler(innerArgv)
+      const handlerResult = commandHandler.handler(innerArgv)
+      if (handlerResult && typeof handlerResult.then === 'function') {
+        handlerResult.then(
+          null,
+          (error) => yargs.getUsageInstance().fail(null, error)
+        )
+      }
     }
 
     if (command) {
diff --git a/lib/usage.js b/lib/usage.js
index cf79b10..bd0906a 100644
--- a/lib/usage.js
+++ b/lib/usage.js
@@ -46,9 +46,9 @@ module.exports = function usage (yargs, y18n) {
       if (!failureOutput) {
         failureOutput = true
         if (showHelpOnFail) yargs.showHelp('error')
-        if (msg) logger.error(msg)
+        if (msg || err) logger.error(msg || err)
         if (failMessage) {
-          if (msg) logger.error('')
+          if (msg || err) logger.error('')
           logger.error(failMessage)
         }
       }
diff --git a/locales/nn.json b/locales/nn.json
new file mode 100644
index 0000000..5e03c50
--- /dev/null
+++ b/locales/nn.json
@@ -0,0 +1,39 @@
+{
+  "Commands:": "Kommandoar:",
+  "Options:": "Alternativ:",
+  "Examples:": "Døme:",
+  "boolean": "boolsk",
+  "count": "mengd",
+  "string": "streng",
+  "number": "nummer",
+  "array": "matrise",
+  "required": "obligatorisk",
+  "default:": "standard:",
+  "choices:": "val:",
+  "generated-value": "generert-verdi",
+  "Not enough non-option arguments: got %s, need at least %s":
+    "Ikkje nok ikkje-alternativ argument: fekk %s, treng minst %s",
+  "Too many non-option arguments: got %s, maximum of %s":
+    "For mange ikkje-alternativ argument: fekk %s, maksimum %s",
+  "Missing argument value: %s": {
+    "one": "Manglar argumentverdi: %s",
+    "other": "Manglar argumentverdiar: %s"
+  },
+  "Missing required argument: %s": {
+    "one": "Manglar obligatorisk argument: %s",
+    "other": "Manglar obligatoriske argument: %s"
+  },
+  "Unknown argument: %s": {
+    "one": "Ukjent argument: %s",
+    "other": "Ukjende argument: %s"
+  },
+  "Invalid values:": "Ugyldige verdiar:",
+  "Argument: %s, Given: %s, Choices: %s": "Argument: %s, Gjeve: %s, Val: %s",
+  "Argument check failed: %s": "Argument sjekk mislukkast: %s",
+  "Implications failed:": "Konsekvensane mislukkast:",
+  "Not enough arguments following: %s": "Ikkje nok fylgande argument: %s",
+  "Invalid JSON config file: %s": "Ugyldig JSON konfigurasjonsfil: %s",
+  "Path to JSON config file": "Bane til JSON konfigurasjonsfil",
+  "Show help": "Vis hjelp",
+  "Show version number": "Vis versjonsnummer"
+}


Test patch:
diff --git a/test/command.js b/test/command.js
index 86eb2b3..cadde7b 100644
--- a/test/command.js
+++ b/test/command.js
@@ -1402,4 +1402,17 @@ describe('Command', () => {
       }).to.throw(/.*\.usage\(\) description must start with \$0.*/)
     })
   })
+
+  // addresses https://github.com/yargs/yargs/issues/510
+  it('fails when the promise returned by the command handler rejects', (done) => {
+    const error = new Error()
+    yargs('foo')
+      .command('foo', 'foo command', noop, (yargs) => Promise.reject(error))
+      .fail((msg, err) => {
+        expect(msg).to.equal(null)
+        expect(err).to.equal(error)
+        done()
+      })
+      .argv
+  })
 })
diff --git a/test/yargs.js b/test/yargs.js
index 361dbec..c3365e2 100644
--- a/test/yargs.js
+++ b/test/yargs.js
@@ -2113,5 +2113,23 @@ describe('yargs dsl tests', () => {
           })
       }).to.throw(/\.positional\(\) can only be called/)
     })
+
+    // see: https://github.com/yargs/yargs-parser/pull/110
+    it('does not parse large scientific notation values, when type string', (done) => {
+      yargs('cmd')
+        .command('cmd deploy <version>', 'a command', (yargs) => {
+          yargs
+            .version(false)
+            .positional('version', {
+              type: 'string'
+            })
+        }).parse('cmd deploy 123e123', (err, argv) => {
+          if (err) {
+            return done(err)
+          }
+          argv.version.should.eql('123e123')
+          return done()
+        })
+    })
   })
 })