Repository: bcoe/yargs (js)

Issue:
How to get stack trace in case of an error?
```js
#! /usr/bin/env node

import yargs from 'yargs';

yargs
    .fail((...args) => {
        console.log('args', args);
    })
    .command('lint', 'Report code format errors.', () => {
        throw new Error('test');
    })
    .argv;

```

Executing this code fails with `test` output. I am expecting `.fail()` to be called, but it is not. Is there a way to disable `yargs` error handler?

Pull request:
Pass the original error as the second argument of fail() handler. (fixes #373)
A follow up to https://github.com/bcoe/yargs/pull/374

Patch:
diff --git a/index.js b/index.js
index 15a7a77..b8655bb 100644
--- a/index.js
+++ b/index.js
@@ -535,7 +535,7 @@ function Argv (processArgs, cwd) {
       try {
         args = parseArgs(processArgs)
       } catch (err) {
-        usage.fail(err.message)
+        usage.fail(err.message, err)
       }
 
       return args
diff --git a/lib/usage.js b/lib/usage.js
index 588117f..0571db0 100644
--- a/lib/usage.js
+++ b/lib/usage.js
@@ -31,10 +31,10 @@ module.exports = function (yargs, y18n) {
   }
 
   var failureOutput = false
-  self.fail = function (msg) {
+  self.fail = function (msg, err) {
     if (fails.length) {
       fails.forEach(function (f) {
-        f(msg)
+        f(msg, err)
       })
     } else {
       // don't output failure message more than once
@@ -50,7 +50,7 @@ module.exports = function (yargs, y18n) {
       if (yargs.getExitProcess()) {
         process.exit(1)
       } else {
-        throw new Error(msg)
+        throw err || new Error(msg)
       }
     }
   }
diff --git a/lib/validation.js b/lib/validation.js
index b7de682..b1bbfdf 100644
--- a/lib/validation.js
+++ b/lib/validation.js
@@ -192,7 +192,7 @@ module.exports = function (yargs, usage, y18n) {
           usage.fail(result)
         }
       } catch (err) {
-        usage.fail(err.message ? err.message : err)
+        usage.fail(err.message ? err.message : err, err)
       }
     })
   }


Test patch:
diff --git a/test/usage.js b/test/usage.js
index af98b8d..1f7c658 100644
--- a/test/usage.js
+++ b/test/usage.js
@@ -417,29 +417,103 @@ describe('usage tests', function () {
     )
   })
 
-  describe('when exitProcess is false and check fails with a thrown exception', function () {
-    it('should display missing arguments once', function () {
-      var r = checkUsage(function () {
-        try {
-          return yargs('-x 10 -z 20')
-            .usage('Usage: $0 -x NUM -y NUM')
-            .exitProcess(false)
-            .wrap(null)
-            .check(function (argv) {
-              if (!('x' in argv)) throw Error('You forgot about -x')
-              if (!('y' in argv)) throw Error('You forgot about -y')
-            })
-            .argv
-        } catch (err) {
-          // ignore the error, we only test the output here
-        }
+  describe('when exitProcess is false', function () {
+    describe('when check fails with a thrown exception', function () {
+      it('should display missing arguments once', function () {
+        var r = checkUsage(function () {
+          try {
+            return yargs('-x 10 -z 20')
+              .usage('Usage: $0 -x NUM -y NUM')
+              .exitProcess(false)
+              .wrap(null)
+              .check(function (argv) {
+                if (!('x' in argv)) throw Error('You forgot about -x')
+                if (!('y' in argv)) throw Error('You forgot about -y')
+              })
+              .argv
+          } catch (err) {
+            // ignore the error, we only test the output here
+          }
+        })
+        r.errors.join('\n').split(/\n+/).should.deep.equal([
+          'Usage: ./usage -x NUM -y NUM',
+          'You forgot about -y'
+        ])
+        r.should.have.property('logs').with.length(0)
+        r.should.have.property('exit').and.be.false
+      })
+    })
+    describe('fail()', function () {
+      it('is called with the original error message as the first parameter', function () {
+        var r = checkUsage(function () {
+          try {
+            return yargs()
+              .fail(function (message) {
+                console.log(message)
+              })
+              .exitProcess(false)
+              .wrap(null)
+              .check(function (argv) {
+                throw new Error('foo')
+              })
+              .argv
+          } catch (error) {
+
+          }
+        })
+        r.logs.should.deep.equal(['foo'])
+        r.should.have.property('exit').and.be.false
+      })
+      describe('when check() throws error', function () {
+        it('fail() is called with the original error object as the second parameter', function () {
+          var r = checkUsage(function () {
+            try {
+              return yargs()
+                .fail(function (message, error) {
+                  console.log(error.message)
+                })
+                .exitProcess(false)
+                .wrap(null)
+                .check(function () {
+                  throw new Error('foo')
+                })
+                .argv
+            } catch (error) {
+
+            }
+          })
+          r.logs.should.deep.equal(['foo'])
+          r.should.have.property('exit').and.be.false
+        })
+      })
+      describe('when command() throws error', function () {
+        it('fail() is called with the original error object as the second parameter', function () {
+          var r = checkUsage(function () {
+            try {
+              return yargs('test')
+                .fail(function () {
+                  console.log('is not triggered')
+                })
+                .exitProcess(false)
+                .wrap(null)
+                .command('test', 'test', function (subYargs) {
+                  subYargs
+                    .fail(function (message, error) {
+                      console.log([error.name, error.message])
+                    })
+                    .exitProcess(false)
+
+                  throw new Error('foo')
+                })
+                .argv
+            } catch (error) {
+
+            }
+          })
+          r.logs.should.deep.equal([['Error', 'foo']])
+          r.should.have.property('exit').and.be.false
+        })
       })
-      r.errors.join('\n').split(/\n+/).should.deep.equal([
-        'Usage: ./usage -x NUM -y NUM',
-        'You forgot about -y'
-      ])
-      r.should.have.property('logs').with.length(0)
-      r.should.have.property('exit').and.be.false
     })
   })