Repository: yargs/yargs
Pull request #1119 (2018-04-27 04:41:51)

PR-AN-01: Global Middleware feature.
PR-AN-03: Update API readme.

PR-AN-01: Fix tests.

PR-AN-01: Remove param defaults for Node 4 comp.

PR-AN-01: Remove spread operator for Node 4 comp.

PR-AN-01: Add unit tests for middleware.

PR-AN-01: Bind this to middleware for chaining.

bcoe: @aorinevo great work 👍 love this feature.

Patch:
diff --git a/appveyor.yml b/appveyor.yml
index b51d9dd..79621eb 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -12,7 +12,7 @@ install:
   # Get the latest stable version of Node 0.STABLE.latest
   - ps: Install-Product node $env:nodejs_version
   # Typical npm stuff.
-  - npm -g install npm@latest
+  - npm i npm@5
   - npm install
 
 # Post-install test scripts.
diff --git a/docs/api.md b/docs/api.md
index 37ca64e..2749fc5 100644
--- a/docs/api.md
+++ b/docs/api.md
@@ -929,6 +929,35 @@ To submit a new translation for yargs:
 <a name="nargs"></a>.nargs(key, count)
 -----------
 
+.middleware(callbacks)
+------------------------------------
+
+Define global middleware functions to be called first, in list order, for all cli command.  
+
+The `callbacks` parameter can be a function or a list of functions.  Each callback gets passed a reference to argv.
+
+```js
+const mwFunc1 = argv => console.log('I\'m a middleware function');
+const mwFunc2 = argv => console.log('I\'m another middleware function');
+
+yargs
+  .command('myCommand', 'some command', {}, function(argv){
+    console.log('Running myCommand!');
+  })
+  .middleware([mwFunc1, mwFunc2]).argv;
+```
+When calling `myCommand` from the command line, mwFunc1 gets called first, then mwFunc2, and finally the command's handler.  The console output is:
+```
+I'm a middleware function
+I'm another middleware function
+Running myCommand!
+```
+
+
+<a name="middleware"></a>.middleware(callbacks)
+--------------------
+
+
 The number of arguments that should be consumed after a key. This can be a
 useful hint to prevent parsing ambiguity. For example:
 
diff --git a/lib/command.js b/lib/command.js
index 65322db..bbd986e 100644
--- a/lib/command.js
+++ b/lib/command.js
@@ -9,15 +9,17 @@ const DEFAULT_MARKER = /(^\*)|(^\$0)/
 // handles parsing positional arguments,
 // and populating argv with said positional
 // arguments.
-module.exports = function command (yargs, usage, validation) {
+module.exports = function command (yargs, usage, validation, globalMiddleware) {
   const self = {}
   let handlers = {}
   let aliasMap = {}
   let defaultCommand
+  globalMiddleware = globalMiddleware || []
   self.addHandler = function addHandler (cmd, description, builder, handler, middlewares) {
     let aliases = []
     handler = handler || (() => {})
     middlewares = middlewares || []
+    middlewares = globalMiddleware.concat(middlewares)
     if (Array.isArray(cmd)) {
       aliases = cmd.slice(1)
       cmd = cmd[0]
diff --git a/lib/middleware.js b/lib/middleware.js
new file mode 100644
index 0000000..ca4280b
--- /dev/null
+++ b/lib/middleware.js
@@ -0,0 +1,10 @@
+module.exports = function (globalMiddleware, context) {
+  return function (callback) {
+    if (Array.isArray(callback)) {
+      Array.prototype.push.apply(globalMiddleware, callback)
+    } else if (typeof callback === 'object') {
+      globalMiddleware.push(callback)
+    }
+    return context
+  }
+}
diff --git a/yargs.js b/yargs.js
index d9d34a4..478360d 100644
--- a/yargs.js
+++ b/yargs.js
@@ -11,6 +11,7 @@ const Y18n = require('y18n')
 const objFilter = require('./lib/obj-filter')
 const setBlocking = require('set-blocking')
 const applyExtends = require('./lib/apply-extends')
+const middlewareFactory = require('./lib/middleware')
 const YError = require('./lib/yerror')
 
 exports = module.exports = Yargs
@@ -21,6 +22,7 @@ function Yargs (processArgs, cwd, parentRequire) {
   let command = null
   let completion = null
   let groups = {}
+  let globalMiddleware = []
   let output = ''
   let preservedGroups = {}
   let usage = null
@@ -31,6 +33,8 @@ function Yargs (processArgs, cwd, parentRequire) {
     updateFiles: false
   })
 
+  self.middleware = middlewareFactory(globalMiddleware, self)
+
   if (!cwd) cwd = process.cwd()
 
   self.$0 = process.argv
@@ -117,7 +121,7 @@ function Yargs (processArgs, cwd, parentRequire) {
     // instances of all our helpers -- otherwise just reset.
     usage = usage ? usage.reset(localLookup) : Usage(self, y18n)
     validation = validation ? validation.reset(localLookup) : Validation(self, usage, y18n)
-    command = command ? command.reset() : Command(self, usage, validation)
+    command = command ? command.reset() : Command(self, usage, validation, globalMiddleware)
     if (!completion) completion = Completion(self, usage, command)
 
     completionCommand = null


Test patch:
diff --git a/test/middleware.js b/test/middleware.js
new file mode 100644
index 0000000..9e5b72a
--- /dev/null
+++ b/test/middleware.js
@@ -0,0 +1,56 @@
+'use strict'
+/* global describe, it, beforeEach, afterEach */
+
+const middlewareFactory = require('../lib/middleware')
+let yargs
+require('chai').should()
+
+describe('middleware', () => {
+  beforeEach(() => {
+    yargs = require('../')
+  })
+
+  afterEach(() => {
+    delete require.cache[require.resolve('../')]
+  })
+  it('should add a list of callbacks to global middleware', () => {
+    const globalMiddleware = []
+
+    middlewareFactory(globalMiddleware)(['callback1', 'callback2'])
+
+    globalMiddleware.should.have.lengthOf(2)
+  })
+
+  it('should add a single callback to global middleware', () => {
+    const globalMiddleware = []
+
+    middlewareFactory(globalMiddleware)({})
+
+    globalMiddleware.should.have.lengthOf(1)
+  })
+
+  it('runs all middleware before reaching the handler', function (done) {
+    yargs(['mw'])
+      .middleware([
+        function (argv) {
+          argv.mw1 = 'mw1'
+        },
+        function (argv) {
+          argv.mw2 = 'mw2'
+        }
+      ])
+      .command(
+        'mw',
+        'adds func list to middleware',
+        function () {},
+        function (argv) {
+          // we should get the argv filled with data from the middleware
+          argv.mw1.should.equal('mw1')
+          argv.mw2.should.equal('mw2')
+          return done()
+        }
+      )
+      .exitProcess(false) // defaults to true.
+      .argv
+  })
+})