Skip to content
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

standalone flags doesn't seem to work in a command driven app powered by commander.js #1290

Closed
open-source-explorer opened this issue Jun 26, 2020 · 17 comments

Comments

@open-source-explorer
Copy link

open-source-explorer commented Jun 26, 2020

Say we've a command driven app:-

program
  .option('-n,--name <name>');

program
  .command('sample')
  .option('-a,--action <action>')
  .action((options) => {
    console.log(options.action);
  });

program.parse(process.argv);

Use cases

  • [cli-name] -n <name> (standalone option)
  • [cli-name] sample -a <value> (command)

The first use case doesn't seem to work.

@open-source-explorer
Copy link
Author

@shadowspawn any help would be appreciated 👏

@open-source-explorer
Copy link
Author

It seems that the cli-flag is invoked only if an available command is passed in 🤔

@shadowspawn
Copy link
Collaborator

Quick answer, I have not run your code to double check. If this doesn't answer your question (I am not sure what cli-flag is), please post what you are typing on the command line and what you are seeing.

You have been extra unlucky and picked TWO option names which clash with existing properties on the Command object!

Have a look at #1226 and https://github.com/tj/commander.js#avoiding-option-name-clashes

Your program is spookily similar to the example program solving this problem: https://github.com/tj/commander.js/blob/master/examples/storeOptionsAsProperties-opts.js

(On a related note, we are adding a throw in Commander v6 to detect the clash and offer advice: #1275)

@open-source-explorer
Copy link
Author

const program = new commander.Command();
program
.storeOptionsAsProperties(false); // <--- change behaviour
program
.name('my-program-name')
.option('-n,--name <name>');
program
.command('show')
.option('-a,--action <action>')
.action((cmd) => {
const options = cmd.opts(); // <--- use opts to access option values
console.log(options.action);
});
program.parse(process.argv);
const programOptions = program.opts(); // <--- use opts to access option values
console.log(programOptions.name);

Even in the above example one can't pass in stand-alone flags.
my-program-name -n <value> shows up help.

@open-source-explorer
Copy link
Author

open-source-explorer commented Jun 26, 2020

You have been extra unlucky and picked TWO option names which clash with existing properties on the Command object!

I didn't get your point here. I just have a command that goes by the name sample (takes in an option -a,--action) and a standalone flag -n, --name.

@open-source-explorer
Copy link
Author

@shadowspawn hope you understood my concern.

@shadowspawn
Copy link
Collaborator

shadowspawn commented Jun 26, 2020

Oh, I see your first issue now, sorry. My other remarks may or may not turn out to be useful!

Your top-level program does not have an action handler. So when you type [cli-name] -n <name> as a standalone option, Commander sees:

  • no action handler to call on the main program
  • a subcommand you could have used (sample)

so it displays the help since you need to specify a subcommand to do anything with this program. (This is a change in behaviour in Commander v5. Older versions would just return and display nothing, which people found unhelpful.)

% node sample.js       
Usage: sample [options] [command]

Options:
  -n,--name <name>  
  -h, --help        display help for command

Commands:
  sample [options]
  help [command]    display help for command

% node sample.js -n John sample
[Function: action]

Note that action seems to be a function... Those other comments I was making will be relevant...

@open-source-explorer
Copy link
Author

I assume commander.js won't be the way to go based on my use cases (standalone flags).

@open-source-explorer
Copy link
Author

open-source-explorer commented Jun 26, 2020

no action handler to call on the main program

@shadowspawn can you please elaborate on this?

@Wilkolicious
Copy link

Wilkolicious commented Jun 26, 2020

no action handler to call on the main program

@shadowspawn can you please elaborate on this?

Your first/root command chain doesn't specify an action handler so it doesn't do anything.
I.e. this part of your example

program
  .option('-n,--name <name>');

It's probably expecting:

program
  .option('-n,--name <name>');
  .action((options) => console.log(options.name));

[...]

@shadowspawn
Copy link
Collaborator

For a program with no subcommands, Commander leaves it up to you what to do when there are no arguments. Your program may not require any options to be specified, like the ls command. Often the program logic will be after the call to parse rather than putting it into an action handler

program
  .option('-t, --test', 'run all tests');
program.parse();
console.log(program.opts());
$ node simple.js 
{ test: undefined }

$ node simple.js -t
{ test: true }

@shadowspawn
Copy link
Collaborator

For a program with subcommands and no action handler, Commander assumes the most useful thing to do is display the help when there are no arguments. If you want different behaviour then just add an action handler (even an empty one!) so Commander can tell you have it covered.

const { program } = require('commander');

program
  .storeOptionsAsProperties(false)
  .passCommandToAction(false);
  
program
   .option('-n,--name <name>')
   .action((options) => {
    console.log(options.name);
  });
 
program
   .command('sample')
   .option('-a,--action <action>')
   .action((options) => {
     console.log(options.action);
   });
 
 program.parse(process.argv);
% node my-way.js -n John
John
% node my-way.js sample --action jump
jump

@shadowspawn
Copy link
Collaborator

shadowspawn commented Jun 26, 2020

You might also be interested in the "default" command support if --name is not intended to be global (and used by sample). When a subcommand is not specified, including when there are no arguments, the "default" command is called.

Well that is probably way more info that you were expecting! Hopefully some of it is useful and gets you going past your original question and beyond. Sorry you stumbled over a built-in behaviour.

@open-source-explorer
Copy link
Author

Thanks for clarifying guys 🙏

@open-source-explorer
Copy link
Author

open-source-explorer commented Jun 27, 2020

program.command('sample').option('-o,--output <value>').action((cmd) => console.log(cmd.output))

program.option('-o,--output <value>').action((option) => console.log(option.output))

// parse args
program.parse(process.argv);
  • node cli.js -o option //option
  • node cli.js sample -o command //undefined

It seems global flags take precedence when subcommand and standalone flags have the same name. Is this an intended behavior?

/cc @shadowspawn

@shadowspawn
Copy link
Collaborator

  1. Yes. Basically you don't want to have flags with the same name at different levels. The parsing runs from the root command down to the subcommand and so the global flags take precedence and prevent the subcommand flag from being set.

  2. You are logging cmd.test and option.target in your example program, but I would expect you want cmd.output and option.output from your option names? (I suspect the logging is left over from checking what happens when the names are not the same!)

@open-source-explorer
Copy link
Author

You are logging cmd.test and option.target in your example program, but I would expect you want cmd.output and option.output from your option names? (I suspect the logging is left over from checking what happens when the names are not the same!)

Yes indeed 😅

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants