New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: middleware #881
feat: middleware #881
Conversation
@Khaledgarbaya thanks for opening this pull! I have a lot of OSS time this weekend and will give feedback. |
@Khaledgarbaya one thing that's been on my mind, before we extend yargs with too many more features, is I'd love to split the massive README.md in the root folder into a few docs in a
Haven't fully thought it out yet -- but would like to perform this decomposition in the fairly near future as we build out some more features. |
@bcoe I guess we can wait until the restructuring PR is done and we can discuss this PR. I have no problem with this. |
@Khaledgarbaya sorry 👋 have been pretty busy juggling various other works (working on some pretty cool things at npm 😄 ). I've taken a stab at restructuring the README over the past week, and would love your feedback: What section would this middleware fall under do you think. My only real concern with the pull request currently, is that the |
hi @bcoe I was thinking of that too recently, I am thinking of two ways.
var argv = require('yargs')
.usage('Usage: $0 <command> [options]')
.command('count', 'Count the lines in a file')
.middlewares({'count': [middleware1, middlware2]})
.argv;
var argv = require('yargs')
.usage('Usage: $0 <command> [options]')
.command('count', 'Count the lines in a file')
.middlewares([middleware1, middlware2])
.argv; First option will let us to define middlewares that can run before each command for example if we specify In conclusion option 2 will still introduce a new argument to the command and option 1 will reduce the number of arguments by one and give us the possibility to run the same middleware for all the commands if we want. Given the structure of yargs I am leaning tward solution 1 since it won't involve a lot of changes to the library and in the future maybe we can improve the command creation. To answer the documentation question I think middlewares should be in the advanced section. I can add that once the docs are merged. Best, |
👋 hey @Khaledgarbaya sorry that I've been dropping this on the floor for so long, I've been on a couple trips this summer, and have also been dealing with some family issues long story short haven't had quite as many hours to work on OSS as I would like. Now that I've refactored the docs for yargs, why don't you take a stab at adding a new advanced topic document on middleware, with a few practical examples; I think we can center the discussion of the optimal API around this document, and then work on getting this landed. With yargs I've tried to take a slow methodical approach to evolving the API, which I understand can be a bit frustrating -- I promise we can get this landed though 👍 |
hey, @bcoe, no worries I was also on vacation and I will definitely try to find to move this PR forward. |
lib/command.js
Outdated
@@ -3,7 +3,7 @@ const inspect = require('util').inspect | |||
const camelCase = require('camelcase') | |||
|
|||
const DEFAULT_MARKER = '*' | |||
|
|||
const objectAssign = require('object-assign') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
now that we require >=Node 4, you should be able to just use Object.assign
.
@Khaledgarbaya a good way to get this slick feature over the finish line would be to join the community slack that I recently created: http://devtoolscommunity.herokuapp.com/ would love to coordinate with you, and help you land this. |
3d0cf30
to
1bea82d
Compare
Hey @bcoe, I made some changes and added the docs Sorry for taking so long |
|
||
``` | ||
-------------- -------------- --------- | ||
stdin ----> argv ----> | Middleware 1 | ----> | Middleware 2 | ---> | Command | |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
love this diagram 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is looking great. Your implementation is really slick, it's awesome that it's ultimately basically 5 lines of code 😛
Just a few comments, but I think this is almost ready to land!
docs/advanced.md
Outdated
.middlewares([normalizeCredentials]) | ||
.argv; | ||
``` | ||
|
||
|
||
See the [yargs-parser](https://github.com/yargs/yargs-parser#configuration) module |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this section should be moved up to be with the "Customizing Yargs' Parser" section.
test/yargs.js
Outdated
@@ -301,7 +301,26 @@ describe('yargs dsl tests', () => { | |||
.exitProcess(false) // defaults to true. | |||
.argv | |||
}) | |||
|
|||
it('run middlewares before reaching the handler', function (done) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
slight grammar nit: "it('runs all middleware before reaching the handler'").
test/yargs.js
Outdated
function (argv) { | ||
// we should get the argv filled with data from the middleware | ||
argv._[0].should.equal('foo') | ||
console.log(argv) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we should get rid of this console.log.
return done() | ||
}, | ||
[function (argv) { | ||
return {hello: 'world'} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
slick, so middleware is additive? we should definitely make sure we mention that it can both add new keys (while maintaining the existing) or overwrite old keys.
docs/advanced.md
Outdated
@@ -403,6 +403,51 @@ some of yargs' parsing features: | |||
} | |||
} | |||
``` | |||
## Midlewares |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's just make this heading: "Middleware".
docs/advanced.md
Outdated
### Usage | ||
|
||
Here the middleware will check if the `username` and `password` is passed by the user | ||
if not it will load them from `~./credential` file and fil in the argv.username and argv.password. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
slight grammar nits:
In this example, our middleware will check if the `username` and `password` is provided. If not, it will load them from `~/.credentials`, and fill in the `argv.username` and `argv.password` values.
docs/advanced.md
Outdated
|
||
Here the middleware will check if the `username` and `password` is passed by the user | ||
if not it will load them from `~./credential` file and fil in the argv.username and argv.password. | ||
This means in your command you are sure that `argv.username` and `argv.password` have values in them. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you can probably drop this sentence (I think it's clear enough).
docs/advanced.md
Outdated
} | ||
``` | ||
|
||
#### yarg |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"""#### yargs parsing configuration"""
docs/advanced.md
Outdated
if not it will load them from `~./credential` file and fil in the argv.username and argv.password. | ||
This means in your command you are sure that `argv.username` and `argv.password` have values in them. | ||
|
||
#### middlware |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"#### middleware function"
@@ -223,6 +223,12 @@ module.exports = function command (yargs, usage, validation) { | |||
|
|||
if (commandHandler.handler && !yargs._hasOutput()) { | |||
yargs._setHasOutput() | |||
if (commandHandler.middlewares.length > 0) { | |||
const middlewareArgs = commandHandler.middlewares.reduce(function (initialObj, middleware) { | |||
return Object.assign(initialObj, middleware(innerArgv)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
to give people the ability to remove an argument in middleware, I think we should perhaps give middleware the actual object to modify, rather than modifying by reference with Object.assign
, then a middleware function could:
delete argv.password
and it would not show up in the final argv passed to the command. What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@bcoe In this case, we would change how the middleware rather than being additive, it changes the actual argv
object. I didn't want to touch the exact object at first but if you don't see any side effects when editing the ref I can change that.
@Morishiri interested in providing feedback on this feature? I'd love to unblock @Khaledgarbaya. |
I just took a look at it and it looks really nice, interesting feature, really usefull :) Fully approved |
2e4126f
to
813b20f
Compare
813b20f
to
f28561d
Compare
@Khaledgarbaya thank you for the awesome contribution 🎉 |
@Khaledgarbaya, @Darkylin please try |
Im on yargs@next and Im attempting to use middleware as documented
but I get
Possible documentation or implementation error? |
@jacobrosenthal I believe this is an issue with documentation, @Khaledgarbaya ultimately decided that it made the most sense to pass middleware as the last argument to a @Khaledgarbaya, am I correct in this assertion? mind updating the docs when you have a moment? |
Hey @bcoe @jacobrosenthal |
That all worked great btw. However one follow up question. Is there any benefit of using the middleware argument vs calling a function (or two functions) in the command builder slot as far as internal lifecycle or something? |
@jacobrosenthal I think @Khaledgarbaya's idea was that you'd be able to apply a complex stack of transforms to all arguments in You might be able to get similar behavior with a builder for individual arguments, whereas middleware processes the final argv object. @Khaledgarbaya out of curiosity do you have any real world examples of using this feature you can point us towards yet. |
OK this is a very WIP and ugly first draft of the middleware feature.
I will need some help to change passing the middleware using a chaining function instead of an array.
For more context check #876
@bcoe I would love to get some feedback from you.
TODO :