Skip to content

Commit

Permalink
feat: add applyBeforeValidation, for applying sync middleware before …
Browse files Browse the repository at this point in the history
…validation
  • Loading branch information
trevorlinton authored and bcoe committed Feb 8, 2019
1 parent cc8af76 commit 5be206a
Show file tree
Hide file tree
Showing 4 changed files with 200 additions and 7 deletions.
33 changes: 32 additions & 1 deletion docs/api.md
Expand Up @@ -945,7 +945,7 @@ To submit a new translation for yargs:

*The [Microsoft Terminology Search](http://www.microsoft.com/Language/en-US/Search.aspx) can be useful for finding the correct terminology in your locale.*

<a name="middleware"></a>.middleware(callbacks)
<a name="middleware"></a>.middleware(callbacks, [applyBeforeValidation])
------------------------------------

Define global middleware functions to be called first, in list order, for all cli command.
Expand All @@ -969,6 +969,37 @@ I'm another middleware function
Running myCommand!
```

Middleware can be applied before validation by setting the second parameter to `true`. This will execute the middleware prior to validation checks, but after parsing.

Each callback is passed a reference to argv. The argv can be modified to affect the behavior of the validation and command execution.

For example, an environment variable could potentially populate a required option:

```js
var argv = require('yargs')
.middleware(function (argv) {
argv.username = process.env.USERNAME
argv.password = process.env.PASSWORD
}, true)
.command('do-something-logged-in', 'You must be logged in to perform this task'
{
'username': {
'demand': true,
'string': true
},
'password': {
'demand': true,
'string': true
}
},
function(argv) {
console.log('do something with the user login and password', argv.username, argv.password)
})
)
.argv

```

<a name="nargs"></a>.nargs(key, count)
-----------

Expand Down
13 changes: 10 additions & 3 deletions lib/command.js
Expand Up @@ -2,7 +2,7 @@

const inspect = require('util').inspect
const isPromise = require('./is-promise')
const {applyMiddleware} = require('./middleware')
const {applyMiddleware, applyPreCheckMiddlware} = require('./middleware')
const path = require('path')
const Parser = require('yargs-parser')

Expand Down Expand Up @@ -224,16 +224,23 @@ module.exports = function command (yargs, usage, validation, globalMiddleware) {
positionalMap = populatePositionals(commandHandler, innerArgv, currentContext, yargs)
}

let preCheckMiddlewares = globalMiddleware.slice(0).concat(commandHandler.middlewares || [])
preCheckMiddlewares = preCheckMiddlewares.filter((x) => x.applyBeforeValidation === true)
if (preCheckMiddlewares.length > 0 && !yargs._hasOutput()) {
applyPreCheckMiddlware(innerArgv, preCheckMiddlewares, yargs)
}

// we apply validation post-hoc, so that custom
// checks get passed populated positional arguments.
if (!yargs._hasOutput()) yargs._runValidation(innerArgv, aliases, positionalMap, yargs.parsed.error)

if (commandHandler.handler && !yargs._hasOutput()) {
yargs._setHasOutput()

const middlewares = globalMiddleware.slice(0).concat(commandHandler.middlewares || [])
let middlewares = globalMiddleware.slice(0).concat(commandHandler.middlewares || [])
middlewares = middlewares.filter((x) => x.applyBeforeValidation !== true)

innerArgv = applyMiddleware(innerArgv, middlewares)
innerArgv = applyMiddleware(innerArgv, middlewares, yargs)

const handlerResult = isPromise(innerArgv)
? innerArgv.then(argv => commandHandler.handler(argv))
Expand Down
18 changes: 15 additions & 3 deletions lib/middleware.js
@@ -1,23 +1,27 @@
const isPromise = require('./is-promise')

module.exports = function (globalMiddleware, context) {
return function (callback) {
return function (callback, applyBeforeValidation = false) {
if (Array.isArray(callback)) {
for (let i = 0; i < callback.length; i++) {
callback[i].applyBeforeValidation = applyBeforeValidation
}
Array.prototype.push.apply(globalMiddleware, callback)
} else if (typeof callback === 'function') {
callback.applyBeforeValidation = applyBeforeValidation
globalMiddleware.push(callback)
}
return context
}
}

module.exports.applyMiddleware = function (argv, middlewares) {
module.exports.applyMiddleware = function (argv, middlewares, yargs) {
return middlewares
.reduce((accumulation, middleware) => {
if (isPromise(accumulation)) {
return accumulation
.then(initialObj =>
Promise.all([initialObj, middleware(initialObj)])
Promise.all([initialObj, middleware(initialObj, yargs)])
)
.then(([initialObj, middlewareObj]) =>
Object.assign(initialObj, middlewareObj)
Expand All @@ -31,3 +35,11 @@ module.exports.applyMiddleware = function (argv, middlewares) {
}
}, argv)
}

module.exports.applyPreCheckMiddlware = function (argv, middlewares, yargs) {
for (let i = 0; i < middlewares.length; i++) {
if (middlewares[i](argv, yargs) instanceof Promise) {
throw new Error('The passed in middleware with applyBeforeValidation set to true may not be used with async functions.')
}
}
}
143 changes: 143 additions & 0 deletions test/middleware.js
Expand Up @@ -49,6 +49,149 @@ describe('middleware', () => {
.parse()
})

it('runs the before-validation middlware before reaching the handler', function (done) {
yargs(['mw'])
.middleware(function (argv) {
argv.mw = 'mw'
}, true)
.command(
'mw',
'adds func to middleware',
{
'mw': {
'demand': true,
'string': true
}
},
function (argv) {
// we should get the argv filled with data from the middleware
argv.mw.should.equal('mw')
return done()
}
)
.exitProcess(false) // defaults to true.
.parse()
})

it('runs the before-validation middleware and ensures theres a context object with commands and availableOptions', function (done) {
yargs(['mw'])
.middleware(function (argv) {
argv.mw = 'foobar'
argv.other = true
}, true)
.command(
'mw',
'adds func to middleware',
{
'mw': {
'demand': true,
'string': true
}
},
function (argv) {
// we should get the argv filled with data from the middleware
argv.mw.should.equal('foobar')
argv.other.should.equal(true)
return done()
}
)
.exitProcess(false) // defaults to true.
.parse()
})

it('runs the before-validation middlware with an array passed in and ensures theres a context object with commands and availableOptions', function (done) {
yargs(['mw'])
.middleware([function (argv) {
argv.mw = 'mw'
argv.other = true
}], true)
.command(
'mw',
'adds func to middleware',
{
'mw': {
'demand': true,
'string': true
}
},
function (argv) {
// we should get the argv filled with data from the middleware
argv.mw.should.equal('mw')
argv.other.should.equal(true)
return done()
}
)
.exitProcess(false) // defaults to true.
.parse()
})

it('runs the before-validation middlware ensures if an async function is ran it throws an error', function (done) {
try {
yargs(['mw'])
.middleware([async function (argv) {
argv.mw = 'mw'
argv.other = true
}], true)
.command(
'mw',
'adds func to middleware',
{
'mw': {
'demand': true,
'string': true
}
},
function (argv) {
// we should get the argv filled with data from the middleware
argv.mw.should.equal('mw')
argv.other.should.equal(true)
return done(new Error('This should not have reached this point.'))
}
)
.exitProcess(false) // defaults to true.
.parse()
} catch (err) {
expect(err.message).to.equal('The passed in middleware with applyBeforeValidation set to true may not be used with async functions.')
done()
}
})

it('Ensure middleware does not run non-before-validation middleware, and vice versa', function (done) {
let execPreOnce = false
let execPostOnce = false
yargs(['mw'])
.middleware([function (argv) {
expect(execPreOnce).to.equal(false)
execPreOnce = true
expect(argv).to.be.an('object')
argv.mw = 'mw'
argv.other = true
}], true)
.middleware([function (argv) {
expect(execPostOnce).to.equal(false)
execPostOnce = true
expect(argv).to.be.an('object')
}])
.command(
'mw',
'adds func to middleware',
{
'mw': {
'demand': true,
'string': true
}
},
function (argv) {
// we should get the argv filled with data from the middleware
argv.mw.should.equal('mw')
argv.other.should.equal(true)
return done()
}
)
.exitProcess(false) // defaults to true.
.parse()
})

it('runs all middleware before reaching the handler', function (done) {
yargs(['mw'])
.middleware([
Expand Down

0 comments on commit 5be206a

Please sign in to comment.