Skip to content

Commit 5be206a

Browse files
trevorlintonbcoe
authored andcommitted
feat: add applyBeforeValidation, for applying sync middleware before validation
1 parent cc8af76 commit 5be206a

File tree

4 files changed

+200
-7
lines changed

4 files changed

+200
-7
lines changed

docs/api.md

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -945,7 +945,7 @@ To submit a new translation for yargs:
945945

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

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

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

972+
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.
973+
974+
Each callback is passed a reference to argv. The argv can be modified to affect the behavior of the validation and command execution.
975+
976+
For example, an environment variable could potentially populate a required option:
977+
978+
```js
979+
var argv = require('yargs')
980+
.middleware(function (argv) {
981+
argv.username = process.env.USERNAME
982+
argv.password = process.env.PASSWORD
983+
}, true)
984+
.command('do-something-logged-in', 'You must be logged in to perform this task'
985+
{
986+
'username': {
987+
'demand': true,
988+
'string': true
989+
},
990+
'password': {
991+
'demand': true,
992+
'string': true
993+
}
994+
},
995+
function(argv) {
996+
console.log('do something with the user login and password', argv.username, argv.password)
997+
})
998+
)
999+
.argv
1000+
1001+
```
1002+
9721003
<a name="nargs"></a>.nargs(key, count)
9731004
-----------
9741005

lib/command.js

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
const inspect = require('util').inspect
44
const isPromise = require('./is-promise')
5-
const {applyMiddleware} = require('./middleware')
5+
const {applyMiddleware, applyPreCheckMiddlware} = require('./middleware')
66
const path = require('path')
77
const Parser = require('yargs-parser')
88

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

227+
let preCheckMiddlewares = globalMiddleware.slice(0).concat(commandHandler.middlewares || [])
228+
preCheckMiddlewares = preCheckMiddlewares.filter((x) => x.applyBeforeValidation === true)
229+
if (preCheckMiddlewares.length > 0 && !yargs._hasOutput()) {
230+
applyPreCheckMiddlware(innerArgv, preCheckMiddlewares, yargs)
231+
}
232+
227233
// we apply validation post-hoc, so that custom
228234
// checks get passed populated positional arguments.
229235
if (!yargs._hasOutput()) yargs._runValidation(innerArgv, aliases, positionalMap, yargs.parsed.error)
230236

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

234-
const middlewares = globalMiddleware.slice(0).concat(commandHandler.middlewares || [])
240+
let middlewares = globalMiddleware.slice(0).concat(commandHandler.middlewares || [])
241+
middlewares = middlewares.filter((x) => x.applyBeforeValidation !== true)
235242

236-
innerArgv = applyMiddleware(innerArgv, middlewares)
243+
innerArgv = applyMiddleware(innerArgv, middlewares, yargs)
237244

238245
const handlerResult = isPromise(innerArgv)
239246
? innerArgv.then(argv => commandHandler.handler(argv))

lib/middleware.js

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,27 @@
11
const isPromise = require('./is-promise')
22

33
module.exports = function (globalMiddleware, context) {
4-
return function (callback) {
4+
return function (callback, applyBeforeValidation = false) {
55
if (Array.isArray(callback)) {
6+
for (let i = 0; i < callback.length; i++) {
7+
callback[i].applyBeforeValidation = applyBeforeValidation
8+
}
69
Array.prototype.push.apply(globalMiddleware, callback)
710
} else if (typeof callback === 'function') {
11+
callback.applyBeforeValidation = applyBeforeValidation
812
globalMiddleware.push(callback)
913
}
1014
return context
1115
}
1216
}
1317

14-
module.exports.applyMiddleware = function (argv, middlewares) {
18+
module.exports.applyMiddleware = function (argv, middlewares, yargs) {
1519
return middlewares
1620
.reduce((accumulation, middleware) => {
1721
if (isPromise(accumulation)) {
1822
return accumulation
1923
.then(initialObj =>
20-
Promise.all([initialObj, middleware(initialObj)])
24+
Promise.all([initialObj, middleware(initialObj, yargs)])
2125
)
2226
.then(([initialObj, middlewareObj]) =>
2327
Object.assign(initialObj, middlewareObj)
@@ -31,3 +35,11 @@ module.exports.applyMiddleware = function (argv, middlewares) {
3135
}
3236
}, argv)
3337
}
38+
39+
module.exports.applyPreCheckMiddlware = function (argv, middlewares, yargs) {
40+
for (let i = 0; i < middlewares.length; i++) {
41+
if (middlewares[i](argv, yargs) instanceof Promise) {
42+
throw new Error('The passed in middleware with applyBeforeValidation set to true may not be used with async functions.')
43+
}
44+
}
45+
}

test/middleware.js

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,149 @@ describe('middleware', () => {
4949
.parse()
5050
})
5151

52+
it('runs the before-validation middlware before reaching the handler', function (done) {
53+
yargs(['mw'])
54+
.middleware(function (argv) {
55+
argv.mw = 'mw'
56+
}, true)
57+
.command(
58+
'mw',
59+
'adds func to middleware',
60+
{
61+
'mw': {
62+
'demand': true,
63+
'string': true
64+
}
65+
},
66+
function (argv) {
67+
// we should get the argv filled with data from the middleware
68+
argv.mw.should.equal('mw')
69+
return done()
70+
}
71+
)
72+
.exitProcess(false) // defaults to true.
73+
.parse()
74+
})
75+
76+
it('runs the before-validation middleware and ensures theres a context object with commands and availableOptions', function (done) {
77+
yargs(['mw'])
78+
.middleware(function (argv) {
79+
argv.mw = 'foobar'
80+
argv.other = true
81+
}, true)
82+
.command(
83+
'mw',
84+
'adds func to middleware',
85+
{
86+
'mw': {
87+
'demand': true,
88+
'string': true
89+
}
90+
},
91+
function (argv) {
92+
// we should get the argv filled with data from the middleware
93+
argv.mw.should.equal('foobar')
94+
argv.other.should.equal(true)
95+
return done()
96+
}
97+
)
98+
.exitProcess(false) // defaults to true.
99+
.parse()
100+
})
101+
102+
it('runs the before-validation middlware with an array passed in and ensures theres a context object with commands and availableOptions', function (done) {
103+
yargs(['mw'])
104+
.middleware([function (argv) {
105+
argv.mw = 'mw'
106+
argv.other = true
107+
}], true)
108+
.command(
109+
'mw',
110+
'adds func to middleware',
111+
{
112+
'mw': {
113+
'demand': true,
114+
'string': true
115+
}
116+
},
117+
function (argv) {
118+
// we should get the argv filled with data from the middleware
119+
argv.mw.should.equal('mw')
120+
argv.other.should.equal(true)
121+
return done()
122+
}
123+
)
124+
.exitProcess(false) // defaults to true.
125+
.parse()
126+
})
127+
128+
it('runs the before-validation middlware ensures if an async function is ran it throws an error', function (done) {
129+
try {
130+
yargs(['mw'])
131+
.middleware([async function (argv) {
132+
argv.mw = 'mw'
133+
argv.other = true
134+
}], true)
135+
.command(
136+
'mw',
137+
'adds func to middleware',
138+
{
139+
'mw': {
140+
'demand': true,
141+
'string': true
142+
}
143+
},
144+
function (argv) {
145+
// we should get the argv filled with data from the middleware
146+
argv.mw.should.equal('mw')
147+
argv.other.should.equal(true)
148+
return done(new Error('This should not have reached this point.'))
149+
}
150+
)
151+
.exitProcess(false) // defaults to true.
152+
.parse()
153+
} catch (err) {
154+
expect(err.message).to.equal('The passed in middleware with applyBeforeValidation set to true may not be used with async functions.')
155+
done()
156+
}
157+
})
158+
159+
it('Ensure middleware does not run non-before-validation middleware, and vice versa', function (done) {
160+
let execPreOnce = false
161+
let execPostOnce = false
162+
yargs(['mw'])
163+
.middleware([function (argv) {
164+
expect(execPreOnce).to.equal(false)
165+
execPreOnce = true
166+
expect(argv).to.be.an('object')
167+
argv.mw = 'mw'
168+
argv.other = true
169+
}], true)
170+
.middleware([function (argv) {
171+
expect(execPostOnce).to.equal(false)
172+
execPostOnce = true
173+
expect(argv).to.be.an('object')
174+
}])
175+
.command(
176+
'mw',
177+
'adds func to middleware',
178+
{
179+
'mw': {
180+
'demand': true,
181+
'string': true
182+
}
183+
},
184+
function (argv) {
185+
// we should get the argv filled with data from the middleware
186+
argv.mw.should.equal('mw')
187+
argv.other.should.equal(true)
188+
return done()
189+
}
190+
)
191+
.exitProcess(false) // defaults to true.
192+
.parse()
193+
})
194+
52195
it('runs all middleware before reaching the handler', function (done) {
53196
yargs(['mw'])
54197
.middleware([

0 commit comments

Comments
 (0)