I'm trying to implement prompting users for missing required args/mandatory options.
I thought I could use a preSubcommand hook, change process.argv
with prompts, and then allow commander to parse the updated argv.
My hook gets executed before any exit however, After the complete of my hook's callback, I will get this error: error: required option '--foo' not specified
.
It seems that parsing continues after the hook on the old process.argv.
For example my-cli subcommand --foo bar
will work fine.
my-cli subcommand
will prompt for foo
and push it to process.argv
inside the hook but then exit with the error above.
Any ideas? I tried to suppress this error with exitOverride
and re-calling parse
afterwards, but my callback is not being reached. Surrounding in try-catch doesn't work either.
Here's my code:
program
.name('cli')
.description('foo')
.version(version, '-v, --version')
.addCommand(sub)
.hook('preSubcommand', async (program, subcommand) => {
await promptForMissingArgs(program, subcommand);
console.log('here');
console.log(process.argv);
program.parse(process.argv); // called again with correct process.argv but still exits
})
.exitOverride((exitCode) => {
console.log(`Exiting with code ${exitCode.exitCode}`); // not reached
// if (exitCode.exitCode !== 0) {
// program.parse(process.argv);
// }
});
// parse command line arguments and execute command
try {
program.parse(process.argv);
} catch (error) {
console.log('here'); // doesn't reach here
console.error(error);
}
From the docs I see this: if the first arg is a subcommand call preSubcommand hooks pass remaining arguments to subcommand, and process same way
https://github.com/tj/commander.js/blob/HEAD/docs/parsing-and-hooks.md
Is it because my hook is async?
Instead of trying to change process.argv, I was able to set options during preSubcommand
hook. I was not able to find a way to change argument values so I settled with options.
@shadowspawn answer is also valid but I have multiple subcommands so I chose to keep my prompting in the shared preSubcommand
hook rather than preAction
. This also let me keep options as mandatory because they are not checked yet at this stage in the parsing lifecycle
import { Command } from 'commander';
const program = new Command();
const subcommand= new Command();
(async () => {
subcommand
.option('-p, --port <number>')
.action(async (options) => {
console.log(options);
});
program.addCommand(subcommand)
.hook('preSubcommand', async (thisCommand, sub) => {
await promptForMissingArgs(thisCommand, sub);
})
await program.parseAsync();
})();
With promptForMissingArgs
implementation something like this:
async function promptForMissingArgs(command, subcommand) {
const answers = await inquirer.prompt(getMissingFromArgv(subcommand.opts()));
Object.keys(answers).forEach(ans => subcommand.setOptionValue(ans, answers[ans]));
}
function getMissingFromArgv(opts) {
// compare process.argv against mandatory opts() to see what is missing
// return list of prompts for inquirer
}