fix: coerce middleware should be applied once#1978
fix: coerce middleware should be applied once#1978bcoe merged 8 commits intoyargs:masterfrom jly36963:fix-coerce-middleware-applied-multiple-times
Conversation
|
@jly36963 thanks for digging into this one, this would be a great bug to fix. middleware was just overhauled drastically. |
bcoe
left a comment
There was a problem hiding this comment.
Looking reasonable to me, except I think we could extend the test to more explicitly call out the failure that's happening.
| yargs => | ||
| yargs.positional('abc', { | ||
| type: 'string', | ||
| coerce: arg => arg.join(' '), |
There was a problem hiding this comment.
I think it would be worth extending this function so that it throws if coerce is called more than once.
let executionCount = 0
...
coerce: arg => {
if (executionCount > 0) throw Error('coerce applied multiple times');
executionCount++;
return arg.join(' ');
};I would then confirm that the test fails prior to your fix.
|
I made some changes to the unit test, and this is the result:
I intentionally did not put the assertions /
|
There should be no need for the `done()` since your test is assuming sync execution.
|
@andymcintosh @CupOfTea696 I've published @jly36963's fix to
And let us know if it fixes the issues you were bumping into? |
|
@bcoe yup, that fixes the issue! |
Addresses: #1966 and #1802
Debugging
I know that the coerce middleware is being applied twice.
I ran the below example. I logged the execution of the functions that use
applyMiddleware, and I logged the type/value of the arg during coercion.Logs look like this:
The coerce middleware is only supposed to run before validation, but the pre-validation
applyMiddlewaregets called twice.Fix
stack trace
The problem
runCommandgets called recursively, which means that the middleware gets applied for each nested command.Coerce middleware mutates argv, and repeating that mutation is undesired and can easily break.
ie:
coerce: (arg) => arg.join()The solution
I added two properties to the Middleware interface:
mutatesdefaults to false, but is set to true inaddCoerceMiddleware.I then check each middleware during
applyMiddleware:appliedto true.Feedback
If you have recommendations for a better pattern, or better names for the interface properties I added, let me know. I feel like they could definitely be named better.