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

Top level command action does not work. #978

Closed
negiDharmendra opened this issue Jun 18, 2019 · 10 comments
Closed

Top level command action does not work. #978

negiDharmendra opened this issue Jun 18, 2019 · 10 comments

Comments

@negiDharmendra
Copy link

When a CLI has sub-commands. Commander does not invoke top-level command action when any of the sub-command is not provided.

Example: demo.js

#! /usr/bin/env node

const program = require('commander');

program
   .option('-l, --log-level <logLevel>', 'Set log level')
   .action( function() {
           console.log('Executing top-level command ');
    });

program
   .command('dummy-command')
   .action(function () {
          console.log('Executing sub command ');
    });
  • When I run demo dummy-command everything works as expected and it prints Executing sub command on the stdout.
  • When I run demo it does not print **Executing top-level command ** on the stdout.

Is there a way to get this working.

@shadowspawn
Copy link
Collaborator

I do not think there is support for invoking the "top-level" command action handler this way when there is a sub-command as well. (There was a change to call top-level command handler due to #729 but it only works when there are no subcommands. You could specify a default command if using git-style executable, but then options are not supported for the default behaviour so still not quite what you want.)

So needs some extra parsing yourself to detect the top-level case in your circumstance.

@negiDharmendra
Copy link
Author

@shadowspawn The use case here is about a browser automation tool Taiko. Taiko comes with a CLI.

  • User can run a script like taiko some-script.js.
  • User can open an intractive REPL.

By default taiko CLI does not have any sub-commands. Users can extend taiko CLI by plugins. When a user has any Taiko plugin( which can extend Taiko CLI) installed on their system they get registered as sub-commands.

Example:

  • We can see there is no sub-command displayed on stdout when user run taiko -h on their cmd.
Usage: taiko [options]
       taiko <file> [options]

Options:
  -v, --version                    output the version number
  -o, --observe                    enables headful mode and runs the script with 3000ms delay by default.
          			pass --wait-time option to override the default 3000ms

  -l, --load                       run the given file and start the repl to record further steps.

  -w, --wait-time <time in ms>     runs script with provided delay

  --emulate-device <device>        Allows to simulate device viewport. Visit https://github.com/getgauge/taiko/blob/master/lib/devices.js for all the available devices

  --emulate-network <networkType>  Allow to simulate network. Available options are GPRS, Regular2G, Good2G, Regular3G, Good3G, Regular4G, DSL, WiFi, Offline
  --plugin <plugin1,plugin2...>    Load the taiko plugin.
  -h, --help                       output usage information
  • Let's assume the user has installed a Taiko plugin called taiko-dummyplugin. Now we can see there is a sub-command dummyplugin displayed when user run taiko -h on their cmd.
Usage: taiko [options]
       taiko <file> [options]

Options:
  -v, --version                    output the version number
  -o, --observe                    enables headful mode and runs the script with 3000ms delay by default.
          			pass --wait-time option to override the default 3000ms

  -l, --load                       run the given file and start the repl to record further steps.

  -w, --wait-time <time in ms>     runs script with provided delay

  --emulate-device <device>        Allows to simulate device viewport. Visit https://github.com/getgauge/taiko/blob/master/lib/devices.js for all the available devices

  --emulate-network <networkType>  Allow to simulate network. Available options are GPRS, Regular2G, Good2G, Regular3G, Good3G, Regular4G, DSL, WiFi, Offline
  --plugin <plugin1,plugin2...>    Load the taiko plugin.
  -h, --help                       output usage information

Commands:
  dummyplugin [options...]

Now user can run taiko dummyplugin <some-options> or taiko some-script.js . But when a user tries to run only taiko it does not open a REPL as commander does not invoke the action for top-level command when there are no arguments passed to it.

@shadowspawn

This comment has been minimized.

@shadowspawn
Copy link
Collaborator

An answer was provided, and no further activity in a month. Closing this as resolved.

Feel free to open a new issue if it comes up again, with new information and renewed interest.

@oliversturm
Copy link

oliversturm commented Aug 28, 2019

Hi @shadowspawn

I'm trying to use a mechanism similar to your sample, but there's some strange behavior. Here's my test code:

var program = require('commander');

program.version('0.0.1').option('-l, --load <name>', 'load something');

program
  .command('doit [id]')
  .description('do something')
  .option('--better', 'do it better')
  .action((id, cmd) => {
    console.log('executing doit action');
    console.log('id is', id);
    console.log('global load option is ', cmd.parent.load);
    console.log('cmd.better is', cmd.better);
  });

program.parse(process.argv);

if (program.args.length === 0)
  console.log('started without command, executing main logic');

The simple idea is that if a command is given, it should be executed (and nothing else). If no command is given, the default logic at the bottom should execute (but not otherwise). In all cases, the global option --load should be supported.

This works correctly when I execute like this:

➜ node test.js -l loadname doit myid        
executing doit action
id is myid
global load option is  loadname
cmd.better is undefined

However, when I pass the --better option to the command, the whole thing breaks and the "main logic" is executed in addition to the command:

➜ node test.js -l loadname doit --better myid
executing doit action
id is myid
global load option is  loadname
cmd.better is true
started without command, executing main logic

Is this a bug? I'm not quite clear what program.args is expected to contain in various scenarios, but it certainly doesn't seem intentional that the behavior changes depending on whether a command option is given or not.

@shadowspawn
Copy link
Collaborator

shadowspawn commented Aug 28, 2019

@oliversturm
I reproduced and think program.args being empty is a bug. There may be some overlap with #508 or #561, but your failure mode is different than those issues, if you would like to open it as a new issue.

(I feel I have seen similar problems with some of the flag parsing assuming a flag always takes a value, but didn't find an open one.)

@oliversturm
Copy link

@shadowspawn Thanks, I opened a new issue. I think it would be great if there was some "standard" way of detecting whether a command action has already been executed - seems like a rather obvious thing to me. I was endlessly hunting around for a way to specify an action that would execute when no arguments are given on the command line. Then I found that this isn't possible and instead I need to put that code behind the parse call - now it turns out to be rather difficult to figure out the correct state!

@shadowspawn
Copy link
Collaborator

FYI: big changes coming in #1149 which should help with both these cases.

@sebastiantf
Copy link

When a CLI has sub-commands. Commander does not invoke top-level command action when any of the sub-command is not provided.

Example: demo.js

#! /usr/bin/env node

const program = require('commander');

program
   .option('-l, --log-level <logLevel>', 'Set log level')
   .action( function() {
           console.log('Executing top-level command ');
    });

program
   .command('dummy-command')
   .action(function () {
          console.log('Executing sub command ');
    });
  • When I run demo dummy-command everything works as expected and it prints Executing sub command on the stdout.
  • When I run demo it does not print **Executing top-level command ** on the stdout.

Is there a way to get this working.

Has this issue been resolved by any later updates? It seems to work fine. I was having some trouble making the top-level command and sub-commands work together. Even though I faced some issues in the beginning, it worked fine after using .action() for the top-level command, which I wasn't doing before.

So finding this issue, I couldn't reproduce this. I was wondering if this was solved in a later update to commander.

@shadowspawn
Copy link
Collaborator

This was fixed in Commander v5 @sebastiantf

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

4 participants