Commander.js: Commander incorrectly assumes that process.argv[0] is 'nodejs' and argv[1] is script name

Created on 23 Mar 2016  路  15Comments  路  Source: tj/commander.js

Command assumes that the first value in process.argv is the name of the nodejs executable being used, and the the second value is the name of the script which was passed to nodejs.

This is mostly fine except in the situation where a packaged electron app is used. For a packaged app a custom binary is used (typically with the name of the app) and it automatically calls the main.js script at start up. It's process.argv starts with the name of the exe and immediately the first of the user supplied arguments.

A possible solution may be to add an extra method which only takes the list of arguments. The calling application would then be responsible for extracting the real arguments from process.argv. (The method could also just return the parsed results and not modify the commander object. See #183 )

enhancement

Most helpful comment

What about this:

if (process.defaultApp != true) {
  process.argv.unshift(null)
}

It works for me using both electron and electron-packager generated executable, and it seems to be the "official" way to handle this.

Obviously implementing this on the library itself would break some existing workarounds, so I would keep the existing behaviour by default, and add a parameter to normalize the arguments. For example something like this:

/**
 * Parse `argv`, settings options and invoking commands when defined.
 *
 * @param {Array} argv
 * @param {Boolean} normalize
 * @return {Command} for chaining
 * @api public
 */
Command.prototype.parse = function(argv, normalize) {

All 15 comments

I fixed my issue in an Electron app with this workaround:

program
  .version('0.1.0')
  .option('-c, --config <filename>', 'set config file');

if (process.argv.length > 1) {
  program.parse(process.argv);
}

There should be a way to tell parse() to treat the arguments as-is. This is the offending line:

https://github.com/tj/commander.js/blob/v2.9.0/index.js#L455

If you have the args as a value, args, then it seems like you have to do:

program.parse([null, null].concat(args))

to get the expected behavior. If you still want to be clever without changing the API, then instead of:

var parsed = this.parseOptions(this.normalize(argv.slice(2)));

you could do:

var effectiveArgs = argv === process.argv ? argv.slice(2) : argv;
var parsed = this.parseOptions(this.normalize(effectiveArgs));

That way, if argv is a user-defined array, it does not need the prefix, but if it is the canonical process.argv argument, then slice(2) is applied as normal.

Though I guess this would break anyone who has already hacked around this behavior?

I've catched this problem today, the solution below only works if you are executing the application with 'node start', not if you are directly executing an electron-builder executable (number of parameters change).

if (process.argv.length > 1) {
  program.parse(process.argv);
}

This works for both scenario. pkg.name is the application name specified in your package.json.

const normalizedArgv = ('' + process.argv[0]).indexOf(pkg.name) >= 0
    ? ['node', pkg.name, ...process.argv.slice(1)]
    : process.argv;
if (normalizedArgv.length > 1) {
    program.parse(normalizedArgv);
}

it doesn't work in all scenarios, but this worked when executing with electron app.js and executing the electron-builder generated binnary, in this case app:

if (process.argv.length > 2 ) opts.parse([""].concat(process.argv));

For some reason the workarounds posted here didn't really do it for me. So here is mine:

let exe = process.argv.shift()
if(!process.argv[0] || process.argv[0] && process.argv[0] !== '.') {
  process.argv.unshift('')
}
process.argv.unshift(exe)

commander.parse(process.argv)

It just forces your process.argv to always start with ['<yourelectronexe>', '.'], so commander can properly parse it. No matter the enviroment you're in (dev or packaged).


Edit: You will run into problems when passing the current directory via ., e.g. meview .. As a temporary workaround pass ./ or precede the dot with other arguments.

Edit 2 Better workaround if you use electron-builder or similar, which renames the electron executable:

let execPath = process.execPath.toLowerCase()
if(execPath.endsWith(pkg.name) || execPath.endsWith(pkg.name + '.exe')) {
  let exe = process.argv.shift()
  process.argv.unshift('')
  process.argv.unshift(exe) 
}

In dev mode, your execPath will contain electron and in production the execPath will have the name of your app as the executable name.

Thanks @RoyalBingBong, this fixed the issue for me!

What about this:

if (process.defaultApp != true) {
  process.argv.unshift(null)
}

It works for me using both electron and electron-packager generated executable, and it seems to be the "official" way to handle this.

Obviously implementing this on the library itself would break some existing workarounds, so I would keep the existing behaviour by default, and add a parameter to normalize the arguments. For example something like this:

/**
 * Parse `argv`, settings options and invoking commands when defined.
 *
 * @param {Array} argv
 * @param {Boolean} normalize
 * @return {Command} for chaining
 * @api public
 */
Command.prototype.parse = function(argv, normalize) {

I hit this myself when I wrote some tests in a client application which called parse, and I forgot to supply the first two parameters.

Commander is currently expecting node conventions: https://nodejs.org/api/process.html#process_process_argv

The process.argv property returns an array containing the command line arguments passed when the Node.js process was launched. The first element will be process.execPath. See process.argv0 if access to the original value of argv[0] is needed. The second element will be the path to the JavaScript file being executed. The remaining elements will be any additional command line arguments.

I currently prefer the suggestion from the original poster to add a new method, rather than pass another parameter to parse, or somehow detect:

A possible solution may be to add an extra method which only takes the list of arguments.

What might this method be called? (The best name I have come up with so far is parseUserArgs.)

(Adding an option parameter to .parse() with a named property is the alternative, and extensible in future if needed.)

Update: the addition of .parseAsync makes adding a differently named routine slightly less attractive, as there would need to be two versions for sync/async.

Before calling parse I added what is suggested above:

  if (process.defaultApp != true) {
    process.argv.unshift('electron');
  }

Even though it might be good if commander supported a non agrv 0/1 dependent parse, its really an electron issue (https://github.com/electron/electron/issues/4690#issuecomment-217435222) and the above code keeps it consistent :rose:

1044 is a variation on assumption "argv[1] is script name", when program launched by:

node .

Possible API for an options parameter to .parse() to specify different conventions for the passed arguments:

// default node style process.argv, { format: 'node' }
.parse(['node', 'script.js', 'serve'])

// No script parameter, such as Electron when process.defaultApp==true
.parse(['electron.exe', 'serve'], { format: 'execPath' })

// No leading special arguments
.parse(['serve'],  { format: 'args' })

node references:

I didn't like the previous API attempt, functional but too ugly. Another attempt:

// default node conventions, user args start at argv[2[
program.parse(process.argv);
program.parse(process.argv, { from: "node" });

// No special arguments, user args start at argv[0] !
program.parse(['serve'], { from: "user" });

// Electron, user args start from argv[1] or argv[2]
program.parse(process.argv, { from: "electron" });

The "from" option is included in the published pre-release of Commander v5.0.0 (#1163)

On a related note, there is also support for omitting the arguments entirely and implicitly defaulting to process.argv, and auto-detecting election in this case.

Commander v5.0.0 has been released with support for specifying what format arguments are in, and auto-detection of Electron if no arguments are supplied. to parse.

https://github.com/tj/commander.js/releases/tag/v5.0.0

Was this page helpful?
0 / 5 - 0 ratings

Related issues

mtrabelsi picture mtrabelsi  路  3Comments

mathiasbynens picture mathiasbynens  路  3Comments

shadowspawn picture shadowspawn  路  4Comments

DeoLeung picture DeoLeung  路  4Comments

san-templates picture san-templates  路  5Comments