-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
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
opts collisions #2260
Comments
Thinking more... there are some times when there is a command which might take an option, and it also has a subcommand which would also take that option e.g.
To set that up I would write something like const endpointOption = new Option('-e, --endpoint <url>')
.default('http://api.github.com/')
const program = new Command()
.name('git-help')
.addCommand(statusCommand())
function statusCommand () {
const status = new Command('status')
.addOption(endpointOption)
.action(async () => {})
status
.addCommand(
new Command('issues')
.addOption(endpointOption)
.action(async () => {})
)
return status
} I need to specify the option at multiple levels here so that it's in the hmmm is this just a badly designed CLI? |
Thanks for the clear description and example code. Some quick clues ((I'll post a longer reply later). Take a look at:
|
It is intended that program options are recognised anywhere on command-line. Selected quotes from README:
Global options can be a bit more complicated for the author and end user, but there are a few additions specifically to help. You can access program options by calling If you do want to have the same named option on the program and on a subcommand and both be usable, you can make the options positional. See |
AH, this could be a good! I found reasoning about this pretty hard, so ran some tests to make this clearer: import test from 'tape'
import { Command, Option } from 'commander'
test('too many opts on the dance floor', async (t) => {
const accountOption = () => {
return new Option('-a, --account <name|address>')
.argParser((str) => str.toUpperCase())
.default('DERP')
}
const program = new Command()
.addOption(accountOption())
.action(opts => { // root_action
console.log(opts)
console.log(program.opts())
})
const danceCommand = new Command('dance')
.addOption(accountOption())
.action(opts => { // dance_action
console.log(opts)
console.log(program.opts())
console.log(danceCommand.opts())
console.log(danceCommand.optsWithGlobals())
})
program.addCommand(danceCommand)
const inputA = '--account naynay'
await program.parseAsync(inputA.split(' '), { from: 'user' })
// logging from root_action reveals:
// opts => { account: 'NAYNAY' })
// program.opts() => { account: 'NAYNAY' })
const inputB = 'dance --account naynay'
await program.parseAsync(inputB.split(' '), { from: 'user' })
// logging from dance_action reveals:
// opts => { account: 'DERP' }
// program.opts() => { account: 'NAYNAY' }
// danceCommand.opts() => { account: 'DERP' }
// danceCommand.optsWithGlobals() => { account: 'NAYNAY' }
t.end()
}) Honestly I find the results surprising / I don't know if I'm confident enough in the results to be able to safely know which opts to be using ... unless I test each option against the default and look for any deviations? |
Why do you have the same named option at two different levels? Can you give some examples of how you intend to call the program from the command-line? I don't currently understand what you are trying to achieve. |
To put it another way, are the program options meant to apply to all subcommands? If the options are just for the program it sometimes makes sense to move the functionality into a subcommand and make it the default command. You can call it the same way, but the refactor shifts the "program" down a level so it is alongside the other subcommands and the options are independent. |
Throwing an error for options used at multiple levels in the commands would highlight some problems, but people do this deliberately as well to show the option in the help:
Not a clear enough win for now. An answer was provided, and no further activity in a month. Closing this as resolved. Feel free to open a new issue if it comes up again, with new information and renewed interest. |
Problem: if you specify the an option with the same name at multiple levels, you get unpredictable results.
This is probably developer error, but it could be an edge case worth documenting (here as an issue) or something to proactively check for.
test:
I discovered this because I was seeing the
default('DERP')
value arrive subcommandaction
even when I had explicitly set the--account
flag. I initially thought there was collision / race condition happening betweenargParser
anddefault
, because logging insideargParser
showed that that was being called. (keyword sprinkling to help others searching for this error)I think this is happening because command is generously allowing options to be anywhere positionally (I saw mention in another issue this can be disabled).
Propose:
if options can be inserted anywhere as a default, add a check that there are not duplicate options that would collide up and down the command/subcommand tree
The text was updated successfully, but these errors were encountered: