Hi there !
Is it possible to define subcommands in commander ?
e.g.
node script.js thecommand subcommand1 -op1 -op2
node script.js thecommand subcommand2 -op3
A particularly intended usage would be to define program.command('thecommand').action('./path/to/subcommands.js') in root index.js or other starting point and then I would have some dir with commands (path/to/subcommands), where I can then define subcommands
program
.command('subcommand1').action(...)
.command('subcommand2').action(...)
or maybe some kind of array with commands
module.exports = [
{ command: 'subcommand1', options: '...', action: someFn },
{ command: 'subcommand2', options: '...', action: someFn }
]
I found something promising here, but it is not documented and I cannot figure out how it works
You can do this using git-style sub-commands. You call parse again when handling the first subcommand to add another layer of subcommands.
In the test code you linked to, the first level command processing which detects cache is in pm and the second level processing which extends this to cache clear and cache validate is in pm-cache.js.
yes, I figured that out already. But it is not always convenient as it requires separate file with specific name in specific location which is unflexible. So I ended up with something like this (posting here, maybe someone will find useful):
function patchCommander(commander) {
commander.Command.prototype.forwardSubcommands = function() {
var self = this;
var listener = function(args, unknown) {
// Parse any so-far unknown options
args = args || [];
unknown = unknown || [];
var parsed = self.parseOptions(unknown);
if (parsed.args.length) args = parsed.args.concat(args);
unknown = parsed.unknown;
// Output help if necessary
if (unknown.includes('--help') || unknown.includes('-h')) {
self.outputHelp();
process.exit(0);
}
self.parseArgs(args, unknown);
};
if (this._args.length > 0) {
console.error('forwardSubcommands cannot be applied to command with explicit args');
}
var parent = this.parent || this;
var name = parent === this ? '*' : this._name;
parent.on('command:' + name, listener);
if (this._alias) parent.on('command:' + this._alias, listener);
return this;
};
// some other utils here ...
}
patchCommander is obviously triggered over the main instance somewhere in the beginning of the code. And then I can do e.g.:
const subCommand = program
.command('journal')
.description('Journal utils') // this should also be separate line, I don't remember why already
.forwardSubcommands(); // instead of "action"
subCommand
.command('list <path>')
.action(List);
subCommand
.command('print-recent <path>')
.action(PrintRecent);
works well so far ... :)
I don't know what the state of this is, but it seems that nearly ALL cli developers would like some custom way to define subcommands...
Relevant issue: https://github.com/tj/commander.js/issues/532
Relevant pull request: https://github.com/tj/commander.js/pull/854
I don't know what the state of this is, but it seems that nearly ALL cli developers would like some custom way to define subcommands...
I am not sure what people have in mind when upvoting this. I know there is interest in more flexibility in specifying the external executable for git-style commands.
Is there interest in being able to do sub subcommands using action handlers and not using external executables? (#63 #245 #655)
Is there interest in being able to do sub subcommands using action handlers and not using external executables?
Speaking for my self that is exactly what I would like :)
I'd love to have this aswell.
@a-fas code works good so far, I only added !args.length || !self.listeners('command:' + args[0]) as condition to the output-help if, since otherwise ./app command1 command2 --help would print command1s help page, not command2s.
I think if it seems useful i can PR it :)
(Will do next week)
On a related note, there is a closed pull request to "connect the wires" for nested routines in #245. The first comment is a nice summary. (I have not analysed it deeply enough to contrast with @a-fas approach yet.)
I had assumed this is how it functioned by default. Super unintuitive to involve separate files by default. Also subcommands that are in separate files expect the original filename prepended to theirs, even if you specify a custom .name. This is inconsistent because if you run the program with npm start it looks for bin-subcommand, but if you run it with node bin/index.js it expects index-subcommand. Again, this is after I specified a custom .name.
Expanding on my new hero, @a-fas
If you tweak @a-fas's snippet and put it in a separate file, commander-patch.js and export it as a module, you can just call the patch instead of commander.
// File name: ./commander-patch.js
const oldCommander = require( 'commander' );
function patchCommander( commander ){
if( commander.hasOwnProperty( 'forwardSubcommands' ) ) return commander;
commander.Command.prototype.forwardSubcommands = function(){
var self = this;
var listener = function( args, unknown ){
// Parse any so-far unknown options
args = args || [];
unknown = unknown || [];
var parsed = self.parseOptions( unknown );
if( parsed.args.length ) args = parsed.args.concat( args );
unknown = parsed.unknown;
// Output help if necessary
if( unknown.includes( '--help' ) || unknown.includes( '-h' ) ){
self.outputHelp();
process.exit( 0 );
}
self.parseArgs( args, unknown );
};
if( this._args.length > 0 ){
console.error( 'forwardSubcommands cannot be applied to command with explicit args' );
}
var parent = this.parent || this;
var name = parent === this ? '*' : this._name;
parent.on( 'command:' + name, listener );
if( this._alias ) parent.on( 'command:' + this._alias, listener );
return this;
};
return commander;
}
const commander = patchCommander( oldCommander );
module.exports = commander;
#!/usr/bin/env node
// File name: index.js
const program = require( './commander-patch' );
const projectCommand = program
.version( '1.0.0' )
.command( 'project' )
.description( 'All Project-related commands' )
.forwardSubcommands();
projectCommand
.command( 'list' )
.action( () => {
console.log( 'ran' )
} );
program.parse( process.argv );
With this, if you run node index.js project list
You will get a response, ran
I should also mention that @a-fas's solution doesn't seem to support the .option() flag. I was able to workaround this by using Inquirer.js
I was stumbling in the same case and have found a bunch of those issues open here.
I noticed you can do something like this, which works pretty well :
const mainCommand = newCommand()
.description('Main command with subcommand');
mainCommand.command('sub')
.description('This is a subcommand')
.action(() => console.log('code goes here'));
Help seems working properly, except when no command at all is provided, help is not displayed.
Fixed the latter with this construct :
if (process.argv.length <= 2) {
mainCommand.outputHelp();
return;
}
mainCommand.parse(process.argv);
I was stumbling in the same case and have found a bunch of those issues open here.
I noticed you can do something like this, which works pretty well :const mainCommand = newCommand() .description('Main command with subcommand'); mainCommand.command('sub') .description('This is a subcommand') .action(() => console.log('code goes here'));Help seems working properly, except when no command at all is provided, help is not displayed.
Fixed the latter with this construct :if (process.argv.length <= 2) { importCommand.outputHelp(); return; } importCommand.parse(process.argv);
I wonder if that could be added in the prototype, somehow?
(Side note: #655 proposed space separated words to describe subcommands.)
Submitted a PR, with docs and examples. Sorry for taking so long. :)
@alexstandiford on options: it does support them, the tricky thing is that the option you are looking for may be in parent ... or parent of parent ... I faced that too so I'm using another util to collect all the options from all levels. I submitted it within the PR. Here is the code just for reference + have a look at readme changes and tests
Command.prototype.collectAllOptions = function() {
var allOpts = {};
var node = this;
while (node) {
allOpts = node.options
.map(o => o.attributeName())
.filter(o => typeof node[o] !== 'function')
.reduce((r, o) => ({ [o]: node[o], ...r }), allOpts); // deeper opts enjoy the priority
node = node.parent;
}
return allOpts;
};
and then in action handler
// cmd
// option('quiet') << UPPER LEVELS
// subcmd
// option('force')
// action('doit <param1> <param2>', actionXhandler)
...
function actionXhandler(param1, param2, commanderInstance) {
if (commanderInstance.collectAllOpts().quiet) {
...
}
}
@tkausl I included your fix and it works but honestly I don't 100% understand the idea with the listener check. I did some tests and in fact the code in if just never called for me. But again, it works as needed and displayes help for the last command level specified
Replying here to a question from #1024 to keep the discussion centralized (P.S. PR number 210 - nice ! 馃槣 )
... whether this can be made a default behavior, rather than a special mode ...
I'm using the following approach:
forwardSubcommands must be called from top level ...Something like this:
// featureX.cmd.js
module.exports = (program) => {
const subCommand = program
.command('journal')
.description('Journal tools')
.forwardSubcommands();
subCommand
.command('read <name>')
.description('Blah blah')
.action(readJournal);
subCommand
.command('delete <name>')
.action(deleteJournal);
}
...
// main.js
const commander = require('commander');
const featureX = require('./commands/featureX.cmd.js')
featureX(commander); // assign the sub commands
In fact I don't like this code structure. It is indirect and confusing. What if reuse existing command and action for subcommands and detect intended behavior based on parameter type ?
Either this
// featureX.cmd.js
const commander = new require('commander').Command; // require commander explicitly
...
commander // define subcommands inside an independent instance
.command('read <name>')
.description('Blah blah')
.action(readJournal);
commander
.command('delete <name>')
.action(deleteJournal);
module.exports = commander; // fully set up sub command branch
...
// main.js
const commander = require('commander');
const featureX = require('./commands/featureX.cmd.js')
commander
.command('journal')
.description('Journal tools')
.action(featureX); // pass instance of Commander
But then main module start to know details about the subcommand which might be unwanted. So maybe push it a bit further.
// featureX.cmd.js
const commander = new require('commander').Command;
...
commander.name = 'journal'; // Command name - maybe this is OK to leave outside ...
commander.description('Journal tools); // Description should be inside
// code from above example
...
// main.js
const commander = require('commander');
const featureX = require('./commands/featureX.cmd.js')
commander.command(featureX); // pass instance of Commander
Or maybe both are OK.
Any thoughts ?
I think your first example is better. I _really_ like it because it feels a little more like you鈥檙e writing a mongoose model, or something like that.
But then main module start to know details about the subcommand which might be unwanted. So maybe push it a bit further.
Can you elaborate on this a bit? I鈥檓 not sure I understand.
Hmmm, you mean the very first example ? With function ?
It's not my favorite because the upper level (main.js) actively passes an object into a lower level (featureX) where this object is somehow unpredictably modified. This is not transparent and confusing imho. But not that it is terrible - probably just the matter of preference :)
But then main module start to know details about the subcommand which might be unwanted. So maybe push it a bit further.
Can you elaborate on this a bit? I鈥檓 not sure I understand.
I just meant kind of information encapsulation - the description and, probably, command name ("journal") are the attributes of the featureX module, so they should be inside. Like in example 1 and 3. Imho the main.js should just import the module and consume it without giving names and descriptions.
Sorry, no. I meant this example:
// featureX.cmd.js
const commander = new require('commander').Command; // require commander explicitly
...
commander // define subcommands inside an independent instance
.command('read <name>')
.description('Blah blah')
.action(readJournal);
commander
.command('delete <name>')
.action(deleteJournal);
module.exports = commander; // fully set up sub command branch
...
// main.js
const commander = require('commander');
const featureX = require('./commands/featureX.cmd.js')
commander
.command('journal')
.description('Journal tools')
.action(featureX); // pass instance of Commander
But to be honest, I think this and your third example both achieve the intended goal for the sub-command usage, and if the third example does create a nicer object structure as you mentioned.
Ah, then it is OK :)
In fact, I think example 2 and 3 do not contradict each other, maybe both can be implemented, with the 3rd being the recommended way. I'll have a look - will adjust the PR.
UPD: sorry, meant example 2 and 3, edited
I do like example 3 from https://github.com/tj/commander.js/issues/764#issuecomment-524634727 for cases where want to separate out the .action implementation from the top-level program, and avoid the subcommand being in control.
I do not like adding _another_ overload of .command. Although the syntax is likely clear enough when reading a working program, I think overloads with different parameters types (rather than optional parameters) are a bit of a nightmare when people are being guided by the IDE completion suggestions. This is more of a problem in JavaScript than in TypeScript.
My question in #1024 about making this a "default behavior" was questioning whether need to call forwardSubcommands, or could just make it work if add a subcommand to a subcommand.
overload of
.command
why "another" ? It doesn't have overloads of the first param at the moment, or I'm missing something ? I partially agree, but here I'm out of new ideas :) A straight forward option would be smth like:
command('journal', 'description', { useSubcommand: subcommand });
command('journal', { useSubcommand: subcommand });
command(null, { useSubcommand: subcommand });
where all 3 options are accepted + a non-empty parameters for name and description would have a higher priority than the one defined inside the subcommand (??? imho)
Though I still think that .command(subcommand) is not terrible ... OK, maybe .command({ useSubcommand: subcommand }) ... but not insisting really - .command(null, { useSubcommand: subcommand }) looks OK for me too.
What do you think ?
default behavior
Hmmm, problem is that I need to get a new instance of Commander for subcommands. And .action returns current instance already. So if we leave current approach this should be a separate method. Just a side-note: probably naming should be changed - forwardSubcommands actually creates a new instance so something like createSubcommand would be more appropriate.
another overload of .command
why "another" ?
I put recent work into trying to make it clearer that there are two ways to declare a command depending on whether intend using an .action handler or separate executable file due to the ongoing confusion between the two styles of calling .command.
default behavior
What I am wondering about is whether this is feasible, simply:
journalCommand = program
.command('journal');
journalCommand
.command('list')
.action(() => {});
And for the new encapsulation style, perhaps a new method like:
const featureX = require('./commands/featureX.cmd.js')
program.addCommand(featureX);
added some draft code along with an example, see 372b7becc72dd003468fa6a8694849f96404dfc6
the method is called useSubcommand
still needs some fine tuning and cleanup of previous version
updated the PR, the code is finalized from my view. Sorry for long silence :)
Inspired and informed by the work by @a-fas, I have a PR to implement the more general approach I speculated about in https://github.com/tj/commander.js/issues/764#issuecomment-526132922
See #1149 (#1129 was first try and now abandoned)
Nested subcommands and .addCommand() are included in the published pre-release of Commander v5.0.0 (#1163)
Commander v5.0.0 has been released.
Most helpful comment
I don't know what the state of this is, but it seems that nearly ALL cli developers would like some custom way to define subcommands...