indexzero / nconf

Hierarchical node.js configuration with files, environment variables, command-line arguments, and atomic object merging.
https://github.com/indexzero/nconf
MIT License
3.87k stars 253 forks source link

How to stop yargs handling --help, --version etc #395

Open jcoglan opened 2 years ago

jcoglan commented 2 years ago

Forgive me if this is a silly question, but after a few hours reading the docs for nconf and yargs I've not been able to figure it out. Say I have this program that reads config from argv or env, and it normalises the parameter names from each source using transform() functions:

const nconf = require('nconf')
const yargs = require('yargs')

const ENV_PREFIX = /^DEMO_/

nconf.argv({
  transform ({ key, value }) {
    key = key.replace(/-/g, '_')
    return { key, value }
  },
  parseValues: true
})

nconf.env({
  transform ({ key, value }) {
    if (ENV_PREFIX.test(key)) {
      key = key.replace(ENV_PREFIX, '').toLowerCase()
      return { key, value }
    }
  },
  parseValues: true
})

let options = {
  setup: nconf.get('setup'),
  max_jobs: nconf.get('max_jobs')
}

console.log(options)

This parses values from each source correctly and selects values with the correct precedence:

$ node nconf-demo.js
{ setup: undefined, max_jobs: undefined }

$ DEMO_SETUP=true DEMO_MAX_JOBS=2 node nconf-demo.js
{ setup: true, max_jobs: 2 }

$ DEMO_SETUP=true DEMO_MAX_JOBS=2 node nconf-demo.js --no-setup --max-jobs 3
{ setup: false, max_jobs: 3 }

However, certain argument names cause special behaviour in yargs, for example if you use --help or --version then yargs executes a special handler and terminates the process:

$ node nconf-demo.js --help
Options:
  --help     Show help                                                 [boolean]
  --version  Show version number                                       [boolean]

Allowing the config parser to exit the process is not desirable behaviour for a long-running process, and it also makes the application impossible to test other than by bypassing its CLI script, so I would like to turn this behaviour off. I tried doing this by modifying the argv() call as follows:

nconf.argv(yargs.help(false).version(false), {
  transform ({ key, value }) {
    key = key.replace(/-/g, '_')
    return { key, value }
  },
  parseValues: true
})

This causes strange behaviour, for example the --max-job setting is ignored:

$ DEMO_SETUP=true DEMO_MAX_JOBS=2 node nconf-demo.js --no-setup --max-jobs 3
{ setup: false, max_jobs: 2 }

It turns out that in this case, the transform() function for argv is not executed, so the key remains as max-jobs instead of being canonicalised to max_jobs.

Is it possible to turn off these special behaviours of yargs so that it functions only as an argument parser, without printing its own output or exiting the process, while using nconf options like transform() at the same time?

As an aside, I discovered that parseValues is also ignored here, but its default value for argv is true contrary to what the docs say. i.e. calling nconf.argv({ parseValues: false }) has no effect; arguments are turned into numbers and booleans rather than remaining as strings.

mhamann commented 2 years ago

That's an interesting find! I need to look at this a bit deeper to see if there's a way to alter this behavior. This might also be a bug in nconf...

In the meantime, if you happen to discover any new information, please let me know!

mhamann commented 2 years ago

@jcoglan I finally got a few minutes to take a closer look at this.

I'm not sure what the original intent of nconf + yargs was, but it makes sense to me that someone might want to use some of the CLI features from yargs (like usage and help) as part of their nconf-enabled app. I would assume there are folks doing this today.

However, as you noted, some people may not want that behavior, which is why nconf supports a custom/external yargs instance being passed to the argv loader (as you showed in your final example).

You also observed that passing a custom yargs instance to nconf results in options like parseValues being ignored/unavailable. While the readme doesn't explicitly state that such a scenario is supported, I do agree that intuitively, someone would expect to be able to use both features in parallel. I've implemented a fix for this behavior and updated the readme to explicitly show its availability. I think this will launch as part of nconf v0.13 next week.

Finally, I investigated your assertion that parseValues: false is not the default for the argv loader, but I could not duplicate the behavior you indicated. When I don't specify that option in the args, values are not parsed (e.g., 'false' is a string, not a boolean). Fliping parseValues: true resulted in the expected behavior of 'false' becoming a boolean. If you still find that this is a problem, please provide a code sample so I can duplicate it.