yargs: Environment variables trigger errors for irrelevant commands (in strict mode)

I find strict mode really useful because it reports errors for unknown options (this is what I want).

However, when you have a program with multiple commands it will report an error for environment variables that don’t apply to the command you are running. It is common practice to define environment variables once and for all in your shell profile so this gets annoying quickly.

Here is an example script.js to illustrate the issue:

const argv = require('yargs')
  .help('help')
  .env('MY_SCRIPT')
  .strict()
  .command('build')
  .command('run', 'runs something', (yargs) => {
    return yargs
      .options({
        'source-dir': {
          demand: false,
          default: '/some/dir',
          type: 'string',
        },
      });
  })
  .argv;

console.log(argv);

This works as expected (without errors):

$ MY_SCRIPT_SOURCE_DIR="/a/path" node script.js run

But this triggers an error which I think is a bug:

$ MY_SCRIPT_SOURCE_DIR="/a/path" node script.js build
script.js build

Options:
  --help  Show help                                                    [boolean]

Unknown argument: sourceDir

If it’s not easily fixable, is there a workaround you can suggest?

About this issue

  • Original URL
  • State: open
  • Created 7 years ago
  • Reactions: 10
  • Comments: 22 (14 by maintainers)

Commits related to this issue

Most upvoted comments

It seems like there’s a pretty clear, intuitive distinction between argument sources that will commonly include unexpected options (config / rc files, env variables) and sources that should not include unexpected options. (argv array)

For any given argument source: a) in strict mode, omit those options even if they’re found. Pretend they’re not there. b) in non-strict mode, do parse all options, even unexpected ones.

Update the .strict() function to enable strict mode only for the argv array by default, not for config files or env variables. Allow it to accept an object literal specifying which sources should be in strict mode.

yargs.strict({
    config: true,
    env: false,
    argv: true
});

EDIT: Technically there are 3 possible behaviors for any given argument source: a) fail if unexpected options are found (like today’s strict mode) b) silently omit unexpected options. c) include all options, even unexpected ones (like today’s non-strict mode)

In non-strict mode, everything is option c) In strict mode, sources that are passed true above should behave like a) and those passed false should behave like b). We could also support passing a string literal, such as loose, to get c) behavior even when calling strict() but I’m not sure anyone wants that.

Also, it would be nice to consider the possibility of dynamically appending [sub]command names to the prefix.

require('yargs')
  .env('COMMAND')
  .command('action', '', {option: {demandOption: true}})
  .demandCommand()
  .strict()
  .parse()
$ command action --option=value # intended action
$ COMMAND_OPTION=value command action # current behavior
$ COMMAND_ACTION_OPTION=value command action # proposed behavior

You can sometimes work around this by eschewing .env in favor of .config with an object constructed just from environment variables that you care about. For example:

yargs(process.argv)
  .option('foo')
  .config({ foo: process.env.FOO })
  .strict();

This way, yargs doesn’t freak out about unknown environment variables (because it doesn’t parse environment variables at all).

I think this highlights that another way to think about this issue is that .env doesn’t provide enough options for filtering environment variables out. It supports a prefix string, but there may be vars with said prefix that you still want to ignore (this comes up a lot if you don’t use a prefix).

@mleguen I like the idea of being able to override strict() with an object that then looks something like:

{
  strictEnvironment: true,
  strictConfig: true,
  strictArgv: true
}

That would allow you to toggle whether strict is applied to env, argv and config respectively … I think whitelisting specific arguments ends up being a few too many dials and knobs for a user to turn.

@kumar303 @gajus could this solution potentially do the trick for you?

@cxsper @pigulla no updates, if someone wanted to draft a proposal PR for a potential solution, very much open to the contribution.

LGTM