nconf
nconf copied to clipboard
Nconf mutates defaults
For some reason, nconf
mutates the value passed to defaults
:
$ cat ./index.js
const nconf = require('nconf')
const defaults = {
foo: {
bar: 'default-foobar'
}
}
nconf.argv()
.env('__')
.defaults(defaults)
nconf.get()
console.log(defaults.foo.bar == 'default-foobar')
$ node ./index
true
$ foo__bar=nope node ./index
false
$ npm ls nconf
[email protected] /home/hdon/nconf-bug
└── [email protected]
Confirmed the issue here as well.
It's a bug in /lib/nconf/stores/memory.js(200). If the value being merged is an object and a key of the same name doesn't exist in the target, the object reference is assigned rather than a copy. That works fine for a single merge, but once a subsequent merge happens nconf will start rewriting the referenced object.
The same appears to be true for arrays.
The code will need to be modified so that object and array values that don't exist in the target are deep cloned.
So I think i'm running into this, but want to confirm. I'm creating a defaults object and one of the default values is a function that auto-generates a password.
I'm usiing .env()
.argv()
and .defaults
, and then after all of that, I'm using nconf.use('memory')
and then mapping over each key to make sure all the keys are in the memory store. Regardless of what order i call .defaults()
it's generating a new password each time, instead of using the value found in the env store.
I'm intended to write a submit a PR for a new format called shellvars
, but until then I want to confirm this is due to this bug. Can you check out the code below and confirm?
Toggle to view code
#!/usr/bin/env node
const path = require('path')
require('dotenv').config({
path: path.join(__dirname, '..', '.env')
})
const nconf = require('nconf')
const generatePassword = require('password-generator')
const fs = require('fs')
const configKeys = ['POSTGRES_DB', 'POSTGRES_USER', 'POSTGRES_PASSWORD', 'DB_HOST', 'DB_PORT', 'sslmode']
const defaults = {
'POSTGRES_DB': 'my_db',
'POSTGRES_USER': 'mydbadmin',
'POSTGRES_PASSWORD': generatePassword(12, false),
'DB_HOST': 'localhost',
'DB_PORT': 5432,
'sslmode': 'disable'
}
// local env vars into config object,
// only loading keys specified,
// and casting values to types if possible
nconf
.defaults(defaults)
.env(configKeys)
.argv({
parseValues: true
})
const config = configKeys.reduce((accumulator,value)=>{
return {
...accumulator,
[value]: nconf.get(value)
}
},{})
//DATABASE_URL used by pgweb
const DB_URL = postgres://${config.POSTGRES_USER}:${config.POSTGRES_PASSWORD}@postgres:${config.DB_PORT}/${config.POSTGRES_DB}?sslmode=${config.sslmode}
// POSTGRES_URL used by server api
const PG_URL = postgres://${config.POSTGRES_USER}:${config.POSTGRES_PASSWORD}@${config.DB_HOST}:5432/${config.POSTGRES_DB}
nconf.use('memory')
// map merged configs into a single store and set POSTGRES_URL and DATABASE_URL to that config store
Object.keys(configKeys).map( key => nconf.set(key, nconf.get(key)))
nconf.set('DATABASE_URL', DB_URL)
nconf.set('POSTGRES_URL', PG_URL)
// load and save config store to disk in shellvars format
nconf.load((err,data) => {
if (err) {
console.error('error loading config into memory')
return
}
let config = Object.keys(data)
.filter(key => key.length >= 5)
.reduce((accumulator,value,index) => {
return ${accumulator}${value}=${data[value]}\n
},'')
fs.writeFile( path.resolve(__dirname, '.env'), config, err => {
if (err) console.error('Error saving cc-server config', err.code, err.message)
console.log('cc-server config saved')
})
})
For those looking for a quick and dirty fix:
Replace:
if (typeof target[key] !== 'object' || Array.isArray(target[key])) {
target[key] = value;
return true;
}
with
if (typeof target[key] !== 'object' || Array.isArray(target[key])) {
target[key] = JSON.parse(JSON.stringify(value));
return true;
}
in memory.js