Commander.js: How to detect an unsupported command?

Created on 3 Feb 2015  路  15Comments  路  Source: tj/commander.js

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:

  .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?

Most helpful comment

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).

All 15 comments

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).

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

@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.

@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?

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 :-)

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 
  }
}

@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).

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;
    // ...
}

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);
});

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)

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);

});

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

For example in main.js

    .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?

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

    .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.

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

@vitalets That solution does not work with default commands:

  .command('install', { isDefault: true })
  ...
Was this page helpful?
0 / 5 - 0 ratings