Skip to content

How to detect an unsupported command? #338

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

Closed
goloroden opened this issue Feb 3, 2015 · 16 comments
Closed

How to detect an unsupported command? #338

goloroden opened this issue Feb 3, 2015 · 16 comments

Comments

@goloroden
Copy link

I have a CLI application that uses commander.js and defines two commands, foo and bar. Since I am using git-style commands, basically my main file looks like this:

program
  .command('foo', 'does something')
  .command('bar', 'does something else')
  .parse(process.argv);

if (process.argv.length === 2) {
  program.help();
}

This works if I call my app without any arguments (then the help is shown, as intended), and it works if I call it with foo or bar as commands.

What does not work is if I specify a non-existing command. In this case, simply nothing happens at all:

$ myapp baz

How can I catch this case? I read that you may use

program.on('*', function () {
  // ...
});

and basically this works, but this executes not only for unknown commands, but for any command. In the same way, specifying a wildcard command does not work either: If I add

program.command('*').action(function () { ... });

this catches unknown commands (which is exactly what I want), but now if you run the app with --help it lists a * command (which is obviously not what I want).

So, to cut a long story short: How do I deal correctly with unknown commands?

@chrishiestand
Copy link

My $0.02 - by default commander.js should sanitize commands and automatically show usage and exit if an unsupported command is used (it doesn't currently).

@goloroden
Copy link
Author

Basically I agree with you, but sometimes it could be useful to handle arbitrary input that is not a valid command.

@SomeKittens
Copy link
Collaborator

@chrishiestand That's not too hard to implement yourself, and like @goloroden, people might want other options.

Unfortunately, at this time program.on('*' is your best bet. I don't think we'll be setting any defaults soon.

@goloroden
Copy link
Author

@SomeKittens: Why has this been closed? Just because what @chrishiestand is not too hard to implement, my original question has not yet been answered. Is

Unfortunately, at this time program.on('*' is your best bet.

really the last word on this?

@goloroden
Copy link
Author

So, I finally ended up with this:

program
  .version(...)
  .command(...)
  .on('*', function (command) {
    this.commands.some(function (command) {
      return command._name === argv[0];
    }) || this.help();
  })
  parse(...);

This does the job perfectly for me :-)

@ghost
Copy link

ghost commented Mar 11, 2015

For me:

program.version(pkg.version)
  .command("clean", "...")
   // ...
  .parse(process.argv)

if (cli.args.length < 1) {
  program.help()
} else {
  if (!program._execs[program.args[0]]) {
    // unknown option 
  }
}

@SomeKittens
Copy link
Collaborator

@goloroden There's nothing wrong with your suggestion - on the contrary, this is something lots of folks hit. I don't think we'll be implementing a default at this time (but I want to revisit this when if/when the plugin system ever happens).

@satazor
Copy link

satazor commented Jun 20, 2015

I hit this same problem but I couldn't use @goloroden suggestion because I wasn't using git like commands. Instead I created a executed variable that is false and I make it true if a command executes. Then I just check against it to print the help message:

var executed = false;

program
  .command('foo')
  .action(foo);

program.parse(process.argv);

if (!executed) {
    program.help();
}

function foo() {
    executed = true;
    // ...
}

@vitalets
Copy link

For those coming to this issue - the @goloroden solution does not work now. But there is official way mentioned in README:

// error on unknown commands
program.on('command:*', function () {
  console.error('Invalid command: %s\nSee --help for a list of available commands.', program.args.join(' '));
  process.exit(1);
});

@psyose
Copy link

psyose commented Jul 18, 2018

In my use case, I wanted a single command that the cli program defaults to when executed.
So I added a the command to the arguments array like this


const foo = (options) => {
  console.log(options.file)
}

program
  .command('foo')
  .option('-f, --file <filename>', 'The file you want node to read')
  .usage('--file data.csv')
  .action(foo)

process.argv.splice(2, 0, 'foo') # <------ this is the workaround

program.parse(process.argv)

@tcf909
Copy link

tcf909 commented Oct 1, 2018

For those coming to this issue - the @goloroden solution does not work now. But there is official way mentioned in README:

// error on unknown commands
program.on('command:*', function () {
  console.error('Invalid command: %s\nSee --help for a list of available commands.', program.args.join(' '));
  process.exit(1);
});

But this seems to work for the root tree:

cli.on('command:*', function (args) {

	if( this._execs[args[0]] )
		return;

	console.error('Invalid command: %s\nSee --help for a list of available commands.', cli.args.join(' '));
	process.exit(1);

});

@frankcchen
Copy link

frankcchen commented Oct 22, 2018

@vitalets This method wouldn't work on git-style subcommands.

For example in main.js

program
    .command('foo', 'this is foo')
    .command('bar' 'this is bar')
    .on('command:*', function () {
        console.error('Invalid command: %s\nSee --help for a list of available commands.', program.args.join(' '));
        process.exit(1);
    });

In this case, even if I do
$node main.js foo
It still gets intercepted by the 'on' method and deemed as invalid command. Any idea how to solve it?

@DonMartin76
Copy link

DonMartin76 commented Feb 13, 2019

I just ran into this as well, and this seems to work quite well, @frankcchen:

program
    .command('postgres <command>', 'manage a local Postgres container')
    .command('start [options]', 'start the service')
    .command('stop [options]', 'stop the service')
    .on('command:*', function (command) {
        const firstCommand = command[0];
        if (!this.commands.find(c => c._name == firstCommand)) {
            console.error('Invalid command: %s\nSee --help for a list of available commands.', program.args.join(' '));
            process.exit(1);
        }
    })
    .parse(process.argv);

This is more or less a combination of the solution @goloroden offered at one point, and the adaption to a newer version of commander. This is tested with version 2.19.0.

Diluka added a commit to Diluka/Mock that referenced this issue Nov 6, 2019
The proper way to print help info only when there is no command. see tj/commander.js#338 (comment)
@shadowspawn
Copy link
Collaborator

Unsupported commands are detected by default from Commander v5, available now as a published pre-release (#1163)

@hacknaked
Copy link

@vitalets That solution does not work with default commands:

program
  .command('install', { isDefault: true })
  ...

@blujedis
Copy link

@hacknaked has shown the current correct solution but I would add one tip. I would suggest adding .allowUnknownOption() and perhaps a variadic args argument. This will ensure you can catch anything passed and in the case of allowUnknownOption it will prevent unknown flags from causing commander to complain.

program.command('404', { isDefault: true })
  .argument('[args...]', 'Catch all arguments/flags provided.')
  .allowUnknownOption()
  .action((args) => {
    // maybe show help as fallback.
    program.help();
  });

The below example is shown in the event { isDefault: true } isn't enough . I'm NOT suggesting you do it this way, using isDefault is much cleaner, but if you need more control, you can directly manipulate process.argv prior to passing to Commander. Commander accepts the input of arguments for this very reason. All you are doing is taking the same process.argv it will use by default and manipulating a bit before processing.

NOTE: This below must be after you've defined all your .command() entries since we are accessing the list of known commands.

// calls to program.command() above ↑

program.command('404')
  .argument('[args...]', 'Catch all arguments and flags.')
  .allowUnknownOption()
  .action((args) => {
     // do something w/ args or maybe show help!
     program.help();
  });

const argv = [...process.argv];         // clone argv.
const cleanArgv = argv.slice(2);        // remove node path etc.
const firstArg = cleanArgv              // first command ignore lfags.
  .filter(v => !/^--?/.test(v))
  .shift();  

// empty argv fallback to help.                         
if (!cleanArgv.length)                  
  argv.push('help');

// if unknown command insert "404" catch all.
else if (!program.commands.includes(firstArg))
  argv.splice(2, 0, 'fallback');

program.parse(argv);

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