Repository navigation
util: parseArgs should support required in options[key] #44564
Description
Activity
- addedutilIssues and PRs related to the built-in util module.Issues and PRs related to the built-in util module.feature requestIssues requesting new Node.js features.Issues requesting new Node.js features.
on Sep 7, 2022 - changed the title
[-]utils: parseArgs should support `required` in options[key][/-][+]util: parseArgs should support `required` in options[key][/+]on Sep 7, 2022 Aren't required positional arguments far more common than required options? I think Node.js should follow POSIX conventions when it comes to parsing arguments, and those seem to heavily favor positional arguments for required values (see GNU core utilities, for example).
Positional arguments are suitable for 1 or 2 arguments, but UX guides often recommend using options when there are more to make their meaning clearer. In your example with only two options, at least one of them would make sense as a positional argument and perhaps the other one could be deduced.
// imaging you will have tens of options in your CLI
Normally most or all options are optional. So it isn't very likely people will have lots of required options.
An earlier version of the Python library discouraged required options on principle (optParse). The current version has this to say: https://docs.python.org/3/library/argparse.html#required
Note Required options are generally considered bad form because users expect options to be optional, and thus they should be avoided when possible.
Reacted by Tobias Nießen and Sindre SorhusI agree with @shadowspawn. I would say that required options are relatively uncommon.
A
required: booleanoption gives little flexibility. For example, how would I implement a--helpoption when I have other options that are required unless--helpis used?On the contrary, usually, each option value has to be validated in some way anyway. Its existence or non-existence is usually not the only validation criterion.
Reacted by John Gee, Alex Yang, Kevin Gibbons and Marek LukášThis would probably be a separate issue, but what about
incompatibleandrequiresfields? While unconditionally required options are rare and frowned upon, dependent and incompatible options are far more common. Both these fields would be of typestring[], containing names of other options in the currentparseArgsobject:parseArgs({ args, options: { owner: { type: 'string', requires: ['repo'], incompatible: ['org'], }, org: { type: 'string', requires: ['repo'], // no need to specify incompatible: ['owner'] // it should work both ways on either one // but it can also be explicitly on both if desired }, repo: { type: 'string', // details on requiresOne further down requiresOne: ['owner', 'org'], }, url: { type: 'string', incompatible: ['repo', 'org', 'owner'], } } });
The above code would result in:
# Allowed foo --url=https://gh.com/user/proj foo --owner=user --repo=proj foo --org=nodejs --repo=node foo # Not allowed foo --url=https://... --owner=user foo --org=nodejs --url=https://... foo --repo=proj --owner=user --url=https://... foo --owner=user --org=nodejs --repo=proj foo --repo=node foo --owner=user foo --org=nodejs
- All of these new fields would default to an empty array
[]. If any of them specify either their own option or a non-existent option it should throw aTypeError. requiresis directional, if--arequires--bbut--bdoesn't require--a,foo --bis valid butfoo --ais not.requiresOneis a variation ofrequireswhere the array is treated as a list of ORs instead of ANDs.requiresandrequiresOnecan be used together without issue, but naturally overlapping options between the two will have higher precedence onrequires.incompatibleis bidirectional, if--adeclared itself incompatible with--b, then--bis also implicitly incompatible with--a. Explicitly marking both incompatible with each other is fine and does nothing extra.- If a conflict between the fields
requires*andincompatibleof two options or one by itself happens, it should throw aTypeError.
- All of these new fields would default to an empty array
This would probably be a separate issue, but what about
incompatibleandrequiresfields?Nice detailed description. I suggest opening as a separate issue if you want more visibility.
I think of these as beyond-basic parsing options. I don't think
parseArgsitself needs them and the "vision" for parseArgs was for a basic robust option parser, but that isn't currently a rule and I expect features will be considered case by case.I think of these as beyond-basic parsing options. I don't think
parseArgsitself needs them and the "vision" for parseArgs was for a basic robust option parser, but that isn't currently a rule and I expect features will be considered case by case.I was not aware of the original "vision" for it and its a fair point, but as a counterpoint on the other hand, in this particular case I would argue it makes sense given it only adds optional slightly more advanced (but still arguably pretty basic) configuration, so in my opinion it'd be fine to add whilst still keeping that original vision.
I will make a separate issue for this soon for further discussion and update this comment linking to it when I do.
Separate issue opened at #45062I don't think
parseArgsitself needs them and the "vision" for parseArgs was for a basic robust option parserIMHO, the same can be said about default values, which are trivial to do in userland (see, for example, #44631 (comment)) but which were stilled added.
My main concern here is adding complexity both to the API surface and to the implementation while still not providing a complete solution. The goal has not been to provide a full-fledged and feature-complete argument parser. In this particular case, even with the three newly proposed options (
requires,requiresOne,incompatible) not all sets of subsets can be described, so it is not a complete solution to describing relations between the existence of options, while significantly increasing complexity.Reacted by John GeeIMHO, the same can be said about default values, which are trivial to do in userland (see, for example, #44631 (comment)) but which were stilled added.
Yes. Yes, indeed. Default values could have been left out! However, there were multiple people looking for defaults who still wanted it despite the relative ease of implementation or alternatives. (I speculate a partial factor may be people not familiar or comfortable with destructuring with defaults.)
Checking for champion for not adding: pkgjs/parseargs#142 (comment)
Here's how I'd write this today:
let options = { owner: { type: 'string', required: true, }, repo: { type: 'string', required: true, }, force: { type: 'boolean', }, }; let { values } = parseArgs({ args, options, }); for (let [name, { required }] of Object.entries(options)) { if (required && !(name in values)) { throw new Error(`--${name} must be provided`); } }
This works just as well for ten required options as for two.
Given how easy this is to do in userland, and that it's a relatively unusual thing to want, I don't think it belongs in the standard library.
Reacted by Alex Yang, Ryan Wheale and John GeeHere's how I'd write this today:
let options = { owner: { type: 'string', required: true, }, repo: { type: 'string', required: true, }, force: { type: 'boolean', }, }; let { values } = parseArgs({ args, options, }); for (let [name, { required }] of Object.entries(options)) { if (required && !(name in values)) { throw new Error(`--${name} must be provided`); } }
This works just as well for ten required options as for two.
Given how easy this is to do in userland, and that it's a relatively unusual thing to want, I don't think it belongs in the standard library.
TypeScript will not allow this
Seems like it does to me? Here's a playground.
That playground isn't doing any typechecking because the
utilmodule is unresolved, so the Node types aren't known at all.Yes, it is doing typechecking. You just have to wait a second or two for it to load the types, after which the errors will go away.
I've been waiting over 10 minutes and no types load and it won't typecheck, but no matter, I just looked directly at the source of
@types/node, and it seems that its allowing extraneous keys due to the argument being a constrained generic, which makes it extensible, I'm not sure whether whoever wrote this type intended this or not, but compared to how all the other node types and TS typings in general are done this is outside the norm. It should probably be fixed.I've been waiting over 10 minutes and no types load and it won't typecheck
Well, you can of course always try it locally, if the playground isn't working for you.
its allowing extraneous keys due to the argument being a constrained generic
I wrote the types, and it's generic so that it can do precise type inference on the result. It's not something which can be "fixed" without making the experience of using the library significantly worse, as far as I'm aware.
But also, TypeScript does not generally prohibit having extra properties, outside of contexts where you're passing a literal (for which it can determine that's definitely an error). So I'm not sure why you'd expect to have an error here even without generics.
compared to how all the other node types and TS typings in general are done this is outside the norm
There's no other built-in functions in node which are comparable to
parseArgs, where the result type depends so precisely on the input. Having generics which allow more precise inference is common to the types for other argument parsers, like yargs.In any case, if you think the TypeScript types could be improved, best to open an issue on definitely-typed.
Fiddled around with this a bit more and you seem to be correct, this is indeed a limitation of TypeScript currently, pretty annoying if at some point node decides to be more strict at runtime with extraneous properties for new functions, but I guess until that happens and forces TS team to do something about it this will have to do, thanks for your insights.
To go back to the original topic, I'm still not a big fan of relying on something that isn't by-the-docs (Node-wise) as like I said they could decide to be stricter in the future, but until then I suppose it is a valid solution. If it was explicitly documented in the parseArgs docs that extraneous arguments for user-defined metadata was allowed, I'd be 100% for it.
I'm +1 for this. I opened an issue that had the same goal. I think adding the
requiredfield would work well.github-actions commented
on Jan 17, 2024 on Jan 17, 2024 – with GitHub ActionsContributorMore actionsThere has been no activity on this feature request for 5 months and it is unlikely to be implemented. It will be closed 6 months after the last non-automated comment.
For more information on how the project manages feature requests, please consult the feature request management document.
- addedstaleIssues and PRs marked stale due to inactivity and scheduled for automatic closure.Issues and PRs marked stale due to inactivity and scheduled for automatic closure.
on Jan 17, 2024 github-actions commented
on Feb 17, 2024 on Feb 17, 2024 – with GitHub ActionsContributorMore actionsThere has been no activity on this feature request and it is being closed. If you feel closing this issue is not the right thing to do, please leave a comment.
For more information on how the project manages feature requests, please consult the feature request management document.
Reacted by Alex Yang, jhmaster, Dave Yarwood, Josh Ghoulberg 👻 and Igor Pletnjov
What is the problem this feature will solve?
I'm building a CLI that creates some instances by id and name. But I have to check if all options are filled by the user one by one.
What is the feature you are proposing to solve the problem?
Add
requiredoption.What alternatives have you considered?
No response