Skip to content
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

How to run multiple commands together #886

Closed
pldg opened this issue Nov 7, 2018 · 2 comments
Closed

How to run multiple commands together #886

pldg opened this issue Nov 7, 2018 · 2 comments

Comments

@pldg
Copy link

pldg commented Nov 7, 2018

I'm trying to build a CLI that need to run multiple commands together, some commands even have specific options. I also need to store the value passed on each commands in a variable for later use.

cli/pm.js

#!/usr/bin/env node

const program = require('commander')
const pkg = require('../package.json')
const numbers= require('./pm-numbers.js')
const say = require('./pm-say.js')

program
  .version(pkg.version)
  .option('--flag')

program.parse(process.argv)

console.log(program.flag, numbers, say)

cli/pm-numbers.js

const program = require('commander')

let numbers;

program
  .command('numbers <items...>')
  .action(items => numbers = items.map(Number))

program.parse(process.argv)

module.exports = numbers

cli/pm-say.js

const program = require('commander')

let say;

program
  .command('say <word>')
  .option('--font <type>')
  .action((word, options) => {
    return say = {
      say: word,
      font: options.font
    }
  })

program.parse(process.argv)

module.exports = say

Test:

  • If I run a single command like node cli/pm.js say hello or node cli/pm.js numbers 1 2 3 everything works fine
  • node cli/pm.js --flag print out error: unknown option '--flag'
  • If I run multiple commands like node cli/pm.js numbers 1 2 3 say hello log out numbers === [ 1, 2, 3, NaN, NaN ] and say === undefined
  • node cli-test/pm.js numbers 1 2 3 say hello --font italic print out error: unknown option '--font'

What am I doing wrong? Anyone can help?

@pldg pldg changed the title How to run multiple commands How to run multiple commands together Nov 7, 2018
@shadowspawn
Copy link
Collaborator

commander does not expect or support specifying multiple commands on the same command line, and what you are doing here is processing the whole of process.argv three times. None of the three programs know about each other, and have no way of parsing out the other commands and options.

So when you run

node cli/pm.js --flag

both numbers.js and say.js would complain about an unknown option --flag.

When you run

node cli/pm.js numbers 1 2 3 say hello

numbers.js treats all the arguments as numbers (including say and hello), and say.js does nothing because the (first) command is not say.

I am not sure if other parsing packages support multiple commands on single line, but it is unusual for a command line tool to do this. You might be able to still use commander for some of the work by preprocessing process.argv yourself and splitting the args up into multiple calls to your combined parser. I am imagining a command line with an extra argument to split the commands like:

node cli-test/pm.js numbers 1 2 3 --and say hello --font italic
or
node cli-test/pm.js numbers 1 2 3 + say hello --font italic

Then you could split up the args into the separate groups, and parse numbers 1 2 3 and say hello --font italic.

@Wilkolicious
Copy link

Wilkolicious commented Jun 26, 2020

My use case for running multiple commands is spawning one or more long running processes (e.g. file watches) in addition to other short-lived execution commands. Each command was dynamically created per 'module' that supplied properties (name, options, etc) and the action callback.

Thanks to @shadowspawn for the pointers of splitting the args up.
For anyone else that might need further direction, I ended up with something like this:

/**
 * Split CLI commands into distinct commands
 * for commander to parse
 *
 * @async
 * @param {array} args
 * @return {Promise<array<string[]>>}
 */
async function splitCommands(args) {
  const stringToSplit = args.splice(2).join(' ');

  // Split commands on delimiter
  return stringToSplit
    .split(commandDelimiter)
    .map((command) => {
      const commandArray = command
        .split(' ')
        .filter((commandPart) => commandPart !== '');
      return args.concat(commandArray);
    });
}

/**
 * Invoke commander parser per command
 *
 * @async
 * @param {array<string[]>} commands
 * @return {Promise<commander.Command[]>}
 */
async function parseCommands(commands) {
  return await Promise.all(commands.map((command) => program.parseAsync(command)));
}

Used for example like so:

import commander from 'commander';
const program = new commander.Command();
const commandDelimiter = '+';

async function init() {
  program
    .storeOptionsAsProperties(false)
    .passCommandToAction(false)
    .version(version)
    .name(name)
    .description(description);

  return Promise.resolve();
}

// Refactor in your implementation
const subCommand = new commander.Command('command1');
    subCommand
      .storeOptionsAsProperties(false)
      .passCommandToAction(false)
      .requireOption('-f, --foo')
      .action((options) => console.log(`action1: ${options.foo}`);
program.addCommand(subCommand);

const subCommand = new commander.Command('command2');
    subCommand
      .storeOptionsAsProperties(false)
      .passCommandToAction(false)
      .requireOption('-b, --bar')
      .action((options) => console.log(`action2: ${options.bar}`);
program.addCommand(subCommand);

init()
  .then(() => doSomething())
  .then((something) => doAnotherThing(something))
  .then(() => splitCommands(process.argv))
  .then((commands) => parseCommands(commands))
  .catch((err) => console.log(err));

In shell:

$ node doStuff.js command1 -f wow1 + command2 -b wow2
action1: wow1
action2: wow2

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

3 participants