Commander.js: Show help if mandatory arguments not used

Created on 6 Jun 2016  路  15Comments  路  Source: tj/commander.js

I have the following code:

const version = JSON.parse(fs.readFileSync(path.join(__dirname, './package.json')) + '').version;

program
.version(version)
.arguments('<engine>')
.option('-u, --url <link>','indicates that the address parameter is to be interpreted as an url (value by default)')
.option('-f, --file <link>','indicates that the address parameter is to be interpreted as a file')
.action(main)

program.parse(process.argv);
if (!process.argv.slice(2).length) {
    program.outputHelp();
}

function main(engine){
    console.log(engine);
    if(program.file) {
        renderer.execute(program.file,engine, {file: true}).then(console.log);
    } else {
        renderer.execute(program.url, engine).then(console.log).done();
    }
};

I was trying to have the program output the help text if the argument engine is not specified. Is there any built-in way to do this? At the moment the code works perfectly if I specify the engine. but if I just execute with any arguments then nothing is shown.

Thanks for taking the time to read this.

enhancement

Most helpful comment

And here is a more specific implementation of .exitOverride to look for the error of interest:

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

program.exitOverride((err) => {
  if (err.code === 'commander.missingArgument') {
    program.outputHelp();
  }
  process.exit(err.exitCode);
});

program
  .arguments('<engine>')
  .option('-u, --url <link>','indicates that the address parameter is to be interpreted as an url (value by default)')
  .action((engine) => {
    console.log(`Action called with ${engine}`);
});

program.parse();

All 15 comments

@pedro93

You could try testing commander.args in addition to process.argv. Bear in mind that it may contain a circular reference to commander.

I'm seeing inconsistent behaviour with the <required> arguments. I have a subcommand with arguments('<one> <two>') and a check similar to yours because the action isn't triggered if they aren't passed (as per the docs). So I was surprised to see that if I pass <one> but not <two> I get the error:

error: missing required argument 'two'

So I updated test/test.arguments.js to match this scenario and it passes. Unless I add a <third> required argument. The code has provision for flagging arguments as required/optional but isn't checking them consistently.

The original poster asked about mandatory non-option arguments. There is not currently automatic support for this.

In the simple example with an argument to the program you can make a simple test for whether anything is left after the options are consumed by testing program.args.

const program = require("commander");

program
.arguments('<engine>')
.option('-u, --url <link>','indicates that the address parameter is to be interpreted as an url (value by default)')
.option('-f, --file <link>','indicates that the address parameter is to be interpreted as a file')
.action((engine) => {
    console.log(`Action called with ${engine}`);
});

program.parse(process.argv);
if (program.args.length == 0) {
    console.log("Would show help");
}
$ node index.js
Would show help
$ node index.js aaa
Action called with aaa
$ node index.js -u bbb
Would show help
$ node index.js -u ccc ddd
Action called with ddd

(Note: deleted a couple of comments in this thread that were not contributing to improving this package.)

This issue has not seen much activity in the passing years. Feel free to open a new issue if it comes up again, with new information and renewed interest.

Closing in favour of #230.

Thank you for your contributions.

Hi guys,

I am working on simple cli tool, and encountered the same error but I feel like it was never really addressed.
Indeed #230 propose a way to use ".requiredOption" for mandatory flag options. But the point here is the missing ".arguments()" of the top level command.

I there a proper way to handle that missing argument if we want to display an error message or the help (without having to write our own argv parser)? If not, I am okay to work on that.

There is not currently automatic support for displaying the help if no arguments are supplied, in the example program of original poster.

I gave one approach for detecting in: https://github.com/tj/commander.js/issues/546#issuecomment-478201124

And there is an open issue about detecting including more complicated cases, with some suggestions, in: #1088

Oh indeed your first approach could solve my issue.
I will take time reading about the more complicated cases and see if I can help on that.
Thank you.

If interested, see also #1062 for some discussion and digging into current state of affairs, and multiple ways command:* gets sent and used.

As of today, https://github.com/tj/commander.js/issues/546#issuecomment-478201124 does not work anymore ([email protected])

C:\code\lib>node index.js
error: missing required argument 'engine'

C:\code\lib>node index.js aaa
Action called with aaa

C:\code\lib>node index.js -u bbb
error: missing required argument 'engine'
Finally, I cannot get this sample to display "no command given!" https://github.com/tj/commander.js#specify-the-argument-syntax ~~I'll just check the process.argv.length before the parse command.~~
my ugly workaround
let args = process.argv.slice(2);
let arg, foundArg = false;
do {
    arg = args.shift();
    if (arg) {
        arg.replace(/^--/, '-');
        switch (arg[1]) {
            case 'c':
            case 'n':
                // c and n are 2 optional parameters that specify a single, assuming the user did things correctly here :/
                args.shift();
                break;
            case 'd':
                // optional boolean parameter
                break;
            default:
                foundArg = true;
                break;
        }
    }
} while (!foundArg && args.length > 0);
if (!foundArg) {
    program.help();
}
program.parse(process.argv);


Slightly uglier workaround but more reliable

program.exitOverride();
try {
    program.parse(process.argv);
} catch (err) {
    program._exitCallback = null;
    program.help();
}

@michga

The behaviour changed in 5.0.0:

Looks like I missed that entry in the README! I opened #1311 to update that.

I think you could use .parseOptions() to do the work for you to look for missing non-option arguments, which would be in the spirit of the previous pattern. Instead of hand parsing. It leaves behind state so you'll want to make a different copy of the program. If that sounds like what you want and you would like an example, I can add one.

@shadowspawn thank you for your input.
I couldn't find a way with .parseOptions().
After some additional digging, I came up with this, which seems acceptable in terms of compliancy since .outputHelp() is marked as public:

program.exitOverride(() => {
    // program.args is not defined when asking for the version -V
    let args = program.args || [];
    //  this prevents duplicating the help when asking for it, not ideal but it seems to work
    if (!(args.includes('-h') || args.includes('--help'))) {
        program.outputHelp();
    }
});
program.parse(process.argv);

The only minor issue that remains is that the help is now displayed when asking for the version since it doesn't show up in the args but I can live with that.

https://github.com/tj/commander.js/blob/c5a5e7b70d425d6f739bd84cc622c1d8775743f1/index.js#L1587-L1596

I had to remind myself what .parseOptions returns. This is the pre-flighting I had in mind to check for no arguments after top-level parse.

const { Command } = require("commander");

function makeProgram() {
  const program = new Command();
  program
    .arguments('<engine>')
    .option('-u, --url <link>','indicates that the address parameter is to be interpreted as an url (value by default)')
    .option('-f, --file <link>','indicates that the address parameter is to be interpreted as a file')
    .action((engine) => {
        console.log(`Action called with ${engine}`);
    });
    return program;
  }

if (makeProgram().parseOptions(process.argv).operands.length <= 2) {
  makeProgram().help();
}

makeProgram().parse(process.argv);

And here is a more specific implementation of .exitOverride to look for the error of interest:

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

program.exitOverride((err) => {
  if (err.code === 'commander.missingArgument') {
    program.outputHelp();
  }
  process.exit(err.exitCode);
});

program
  .arguments('<engine>')
  .option('-u, --url <link>','indicates that the address parameter is to be interpreted as an url (value by default)')
  .action((engine) => {
    console.log(`Action called with ${engine}`);
});

program.parse();

@shadowspawn Thank you, this implementation you gave does exactly what I wanted.

Was this page helpful?
0 / 5 - 0 ratings