diff --git a/lib/main.js b/lib/main.js index 47be414..e57074e 100644 --- a/lib/main.js +++ b/lib/main.js @@ -1,130 +1,53 @@ -// 'use strict' -// -// // * / -// // * (\\)? # is it escaped with a backslash? -// // * (\$) # literal $ -// // * (?!\() # shouldnt be followed by parenthesis -// // * (\{?) # first brace wrap opening -// // * ([\w.]+) # key -// // * (?::-((?:\$\{(?:\$\{(?:\$\{[^}]*\}|[^}])*}|[^}])*}|[^}])+))? # optional default nested 3 times -// // * (\}?) # last brace warp closing -// // * /xi -// -// const DOTENV_SUBSTITUTION_REGEX = /(\\)?(\$)(?!\()(\{?)([\w.]+)(?::?-((?:\$\{(?:\$\{(?:\$\{[^}]*\}|[^}])*}|[^}])*}|[^}])+))?(\}?)/gi -// -// function _resolveEscapeSequences (value) { -// return value.replace(/\\\$/g, '$') -// } -// -// function interpolate (value, processEnv, parsed) { -// return value.replace(DOTENV_SUBSTITUTION_REGEX, (match, escaped, dollarSign, openBrace, key, defaultValue, closeBrace) => { -// if (escaped === '\\') { -// return match.slice(1) -// } else { -// if (processEnv[key]) { -// if (processEnv[key] === parsed[key]) { -// return processEnv[key] -// } else { -// // scenario: PASSWORD_EXPAND_NESTED=${PASSWORD_EXPAND} -// return interpolate(processEnv[key], processEnv, parsed) -// } -// } -// -// if (parsed[key]) { -// // avoid recursion from EXPAND_SELF=$EXPAND_SELF -// if (parsed[key] !== value) { -// return interpolate(parsed[key], processEnv, parsed) -// } -// } -// -// if (defaultValue) { -// if (defaultValue.startsWith('$')) { -// return interpolate(defaultValue, processEnv, parsed) -// } else { -// return defaultValue -// } -// } -// -// return '' -// } -// }) -// } -// -// function expand (options) { -// let processEnv = process.env -// if (options && options.processEnv != null) { -// processEnv = options.processEnv -// } -// -// for (const key in options.parsed) { -// let value = options.parsed[key] -// -// const inProcessEnv = Object.prototype.hasOwnProperty.call(processEnv, key) -// if (inProcessEnv) { -// if (processEnv[key] === options.parsed[key]) { -// // assume was set to processEnv from the .env file if the values match and therefore interpolate -// value = interpolate(value, processEnv, options.parsed) -// } else { -// // do not interpolate - assume processEnv had the intended value even if containing a $. -// value = processEnv[key] -// } -// } else { -// // not inProcessEnv so assume interpolation for this .env key -// value = interpolate(value, processEnv, options.parsed) -// } -// -// options.parsed[key] = _resolveEscapeSequences(value) -// } -// -// for (const processKey in options.parsed) { -// processEnv[processKey] = options.parsed[processKey] -// } -// -// return { -// parsed: options.parsed, -// processEnv -// } -// return options -// } -// -// module.exports.expand = expand +'use strict' + +// * / +// * (\\)? # is it escaped with a backslash? +// * (\$) # literal $ +// * (?!\() # shouldnt be followed by parenthesis +// * (\{?) # first brace wrap opening +// * ([\w.]+) # key +// * (?::-((?:\$\{(?:\$\{(?:\$\{[^}]*\}|[^}])*}|[^}])*}|[^}])+))? # optional default nested 3 times +// * (\}?) # last brace warp closing +// * /xi + +const DOTENV_SUBSTITUTION_REGEX = /(\\)?(\$)(?!\()(\{?)([\w.]+)(?::?-((?:\$\{(?:\$\{(?:\$\{[^}]*\}|[^}])*}|[^}])*}|[^}])+))?(\}?)/gi function _resolveEscapeSequences (value) { return value.replace(/\\\$/g, '$') } -function interpolate (value, env) { - const regex = /(? { + if (escaped === '\\') { + return match.slice(1) + } else { + if (processEnv[key]) { + if (processEnv[key] === parsed[key]) { + return processEnv[key] + } else { + // scenario: PASSWORD_EXPAND_NESTED=${PASSWORD_EXPAND} + return interpolate(processEnv[key], processEnv, parsed) + } + } - if (value) { - // self-referential check - if (seen.has(value)) { - result = result.replace(template, defaultValue) - } else { - result = result.replace(template, value) + if (parsed[key]) { + // avoid recursion from EXPAND_SELF=$EXPAND_SELF + if (parsed[key] !== value) { + return interpolate(parsed[key], processEnv, parsed) + } } - } else { - result = result.replace(template, defaultValue) - } - regex.lastIndex = 0 // reset regex search position to re-evaluate after each replacement - } + if (defaultValue) { + if (defaultValue.startsWith('$')) { + return interpolate(defaultValue, processEnv, parsed) + } else { + return defaultValue + } + } - return result + return '' + } + }) } function expand (options) { @@ -132,37 +55,32 @@ function expand (options) { if (options && options.processEnv != null) { processEnv = options.processEnv } - const parsed = options.parsed || {} - - const combined = { ...processEnv, ...parsed } - const combinedReversed = { ...parsed, ...processEnv } - for (const key in parsed) { - const value = parsed[key] - - // interpolate using both file and processEnv (file interpolation wins. used for --overload later) - const fileValue = _resolveEscapeSequences(interpolate(value, combined)) - console.log('value', value) - parsed[key] = fileValue - - if (fileValue === _resolveEscapeSequences(value)) { - continue // no change means no expansion, move on - } + for (const key in options.parsed) { + let value = options.parsed[key] - if (processEnv[key]) { - continue // already has a value in processEnv, move on + const inProcessEnv = Object.prototype.hasOwnProperty.call(processEnv, key) + if (inProcessEnv) { + if (processEnv[key] === options.parsed[key]) { + // assume was set to processEnv from the .env file if the values match and therefore interpolate + value = interpolate(value, processEnv, options.parsed) + } else { + // do not interpolate - assume processEnv had the intended value even if containing a $. + value = processEnv[key] + } + } else { + // not inProcessEnv so assume interpolation for this .env key + value = interpolate(value, processEnv, options.parsed) } - const processEnvValue = interpolate(value, combinedReversed) // could be empty string '' - if (processEnvValue) { - processEnv[key] = _resolveEscapeSequences(processEnvValue) // set it - } + options.parsed[key] = _resolveEscapeSequences(value) } - return { - parsed, - processEnv + for (const processKey in options.parsed) { + processEnv[processKey] = options.parsed[processKey] } + + return options } module.exports.expand = expand diff --git a/tests/main.js b/tests/main.js index 6baff8d..f8f7755 100644 --- a/tests/main.js +++ b/tests/main.js @@ -71,9 +71,7 @@ t.test('uses environment variables existing already on the machine for expansion const parsed = dotenvExpand.expand(dotenv).parsed ct.equal(parsed.MACHINE_EXPAND, 'machine') - ct.equal(process.env.MACHINE_EXPAND, 'machine') ct.equal(parsed.MACHINE_EXPAND_SIMPLE, 'machine') - ct.equal(process.env.MACHINE_EXPAND_SIMPLE, 'machine') ct.end() }) @@ -99,11 +97,9 @@ t.test('prioritizes machine key expansion over .env', ct => { MACHINE_EXPAND: '$MACHINE' } } - const { parsed, processEnv } = dotenvExpand.expand(dotenv) + const parsed = dotenvExpand.expand(dotenv).parsed - ct.equal(parsed.MACHINE_EXPAND, 'machine_env') - ct.equal(processEnv.MACHINE_EXPAND, 'machine') - ct.equal(process.env.MACHINE_EXPAND, 'machine') + ct.equal(parsed.MACHINE_EXPAND, 'machine') ct.end() }) @@ -141,11 +137,9 @@ t.test('does not overwrite preset variables', ct => { SOME_ENV: 'development' } } - const { parsed, processEnv } = dotenvExpand.expand(dotenv) + const parsed = dotenvExpand.expand(dotenv).parsed - ct.equal(parsed.SOME_ENV, 'development') - ct.equal(processEnv.SOME_ENV, 'production') - ct.equal(process.env.SOME_ENV, 'production') + ct.equal(parsed.SOME_ENV, 'production') ct.end() }) @@ -188,402 +182,400 @@ t.test('expands environment variables (process.env)', ct => { t.test('expands environment variables (process.env)', ct => { const dotenv = require('dotenv').config({ path: 'tests/.env.test' }) - const { parsed, processEnv } = dotenvExpand.expand(dotenv) + dotenvExpand.expand(dotenv) - ct.equal(parsed.BASIC_EXPAND, '$BASIC') ct.equal(process.env.BASIC_EXPAND, 'basic') - ct.equal(processEnv.BASIC_EXPAND, 'basic') - - ct.end() -}) - -// t.test('expands environment variables existing already on the machine', ct => { -// process.env.MACHINE = 'machine' -// -// const dotenv = require('dotenv').config({ path: 'tests/.env.test', processEnv: {} }) -// dotenvExpand.expand(dotenv) -// -// ct.equal(process.env.MACHINE_EXPAND, 'machine') -// -// ct.end() -// }) -// -// t.test('expands environment variables existing already on the machine (process.env)', ct => { -// process.env.MACHINE = 'machine' -// -// const dotenv = require('dotenv').config({ path: 'tests/.env.test' }) -// dotenvExpand.expand(dotenv) -// -// ct.equal(process.env.MACHINE_EXPAND, 'machine') -// -// ct.end() -// }) -// -// t.test('expands missing environment variables to an empty string', ct => { -// const dotenv = require('dotenv').config({ path: 'tests/.env.test', processEnv: {} }) -// const parsed = dotenvExpand.expand(dotenv).parsed -// -// ct.equal(parsed.UNDEFINED_EXPAND, '') -// -// ct.end() -// }) -// -// t.test('expands missing environment variables to an empty string (process.env)', ct => { -// const dotenv = require('dotenv').config({ path: 'tests/.env.test' }) -// const parsed = dotenvExpand.expand(dotenv).parsed -// -// ct.equal(parsed.UNDEFINED_EXPAND, '') -// -// ct.end() -// }) -// -// t.test('expands environment variables existing already on the machine even with a default', ct => { -// process.env.MACHINE = 'machine' -// -// const dotenv = require('dotenv').config({ path: 'tests/.env.test', processEnv: {} }) -// dotenvExpand.expand(dotenv) -// -// ct.equal(process.env.EXPAND_DEFAULT, 'machine') -// -// ct.end() -// }) -// -// t.test('expands environment variables existing already on the machine even with a default when nested', ct => { -// process.env.MACHINE = 'machine' -// -// const dotenv = require('dotenv').config({ path: 'tests/.env.test', processEnv: {} }) -// dotenvExpand.expand(dotenv) -// -// ct.equal(process.env.EXPAND_DEFAULT_NESTED, 'machine') -// ct.equal(process.env.EXPAND_DEFAULT_NESTED2, 'machine') -// -// ct.end() -// }) -// -// t.test('expands environment variables undefined with one already on the machine even with a default when nested', ct => { -// process.env.MACHINE = 'machine' -// -// const dotenv = require('dotenv').config({ path: 'tests/.env.test', processEnv: {} }) -// dotenvExpand.expand(dotenv) -// -// ct.equal(process.env.UNDEFINED_EXPAND_NESTED, 'machine') -// -// ct.end() -// }) -// -// t.test('expands missing environment variables to an empty string but replaces with default', ct => { -// const dotenv = require('dotenv').config({ path: 'tests/.env.test', processEnv: {} }) -// const parsed = dotenvExpand.expand(dotenv).parsed -// -// ct.equal(parsed.UNDEFINED_EXPAND_DEFAULT, 'default') -// ct.equal(parsed.UNDEFINED_EXPAND_DEFAULT2, 'default') -// -// ct.end() -// }) -// -// t.test('expands environent variables and concats with default nested', ct => { -// const dotenv = require('dotenv').config({ path: 'tests/.env.test', processEnv: {} }) -// const parsed = dotenvExpand.expand(dotenv).parsed -// -// ct.equal(parsed.EXPAND_DEFAULT_NESTED_TWICE, 'machine_envdefault') -// ct.equal(parsed.EXPAND_DEFAULT_NESTED_TWICE2, 'machine_envdefault') -// -// ct.end() -// }) -// -// t.test('expands environent variables and concats with default nested', ct => { -// const dotenv = require('dotenv').config({ path: 'tests/.env.test' }) -// const parsed = dotenvExpand.expand(dotenv).parsed -// -// ct.equal(parsed.EXPAND_DEFAULT_NESTED_TWICE, 'machine_envdefault') -// ct.equal(parsed.EXPAND_DEFAULT_NESTED_TWICE2, 'machine_envdefault') -// -// ct.end() -// }) -// -// t.test('expands missing environment variables to an empty string but replaces with default nested', ct => { -// const dotenv = require('dotenv').config({ path: 'tests/.env.test', processEnv: {} }) -// const parsed = dotenvExpand.expand(dotenv).parsed -// -// ct.equal(parsed.UNDEFINED_EXPAND_DEFAULT_NESTED, 'default') -// ct.equal(parsed.UNDEFINED_EXPAND_DEFAULT_NESTED2, 'default') -// -// ct.end() -// }) -// -// t.test('expands missing environment variables to an empty string but replaces with default nested twice', ct => { -// const dotenv = require('dotenv').config({ path: 'tests/.env.test', processEnv: {} }) -// const parsed = dotenvExpand.expand(dotenv).parsed -// -// ct.equal(parsed.UNDEFINED_EXPAND_DEFAULT_NESTED_TWICE, 'default') -// ct.equal(parsed.UNDEFINED_EXPAND_DEFAULT_NESTED_TWICE2, 'default') -// -// ct.end() -// }) -// -// t.test('prioritizes machine key expansion over .env', ct => { -// process.env.MACHINE = 'machine' -// -// const dotenv = require('dotenv').config({ path: 'tests/.env.test', processEnv: {} }) -// const parsed = dotenvExpand.expand(dotenv).parsed -// -// ct.equal(parsed.MACHINE_EXPAND, 'machine') -// -// ct.end() -// }) -// -// t.test('multiple expand', ct => { -// const dotenv = require('dotenv').config({ path: 'tests/.env.test', processEnv: {} }) -// const parsed = dotenvExpand.expand(dotenv).parsed -// -// ct.equal(parsed.MONGOLAB_URI, 'mongodb://username:password@abcd1234.mongolab.com:12345/heroku_db') -// -// ct.end() -// }) -// -// t.test('should expand recursively', ct => { -// const dotenv = require('dotenv').config({ path: 'tests/.env.test', processEnv: {} }) -// const parsed = dotenvExpand.expand(dotenv).parsed -// -// ct.equal(parsed.MONGOLAB_URI_RECURSIVELY, 'mongodb://username:password@abcd1234.mongolab.com:12345/heroku_db') -// -// ct.end() -// }) -// -// t.test('multiple expand', ct => { -// const dotenv = require('dotenv').config({ path: 'tests/.env.test', processEnv: {} }) -// const parsed = dotenvExpand.expand(dotenv).parsed -// -// ct.equal(parsed.NO_CURLY_BRACES_URI, 'mongodb://username:password@abcd1234.mongolab.com:12345/heroku_db') -// -// ct.end() -// }) -// -// t.test('should expand recursively', ct => { -// const dotenv = require('dotenv').config({ path: 'tests/.env.test', processEnv: {} }) -// const parsed = dotenvExpand.expand(dotenv).parsed -// -// ct.equal(parsed.NO_CURLY_BRACES_URI_RECURSIVELY, 'mongodb://username:password@abcd1234.mongolab.com:12345/heroku_db') -// -// ct.end() -// }) -// -// t.test('can write to an object rather than process.env if user provides it', ct => { -// const myObject = {} -// const dotenv = { -// processEnv: myObject, -// parsed: { -// SHOULD_NOT_EXIST: 'testing' -// } -// } -// const parsed = dotenvExpand.expand(dotenv).parsed -// const evaluation = typeof process.env.SHOULD_NOT_EXIST -// -// ct.equal(parsed.SHOULD_NOT_EXIST, 'testing') -// ct.equal(myObject.SHOULD_NOT_EXIST, 'testing') -// ct.equal(evaluation, 'undefined') -// -// ct.end() -// }) -// -// t.test('expands environment variables existing already on the machine even with a default with special characters', ct => { -// const dotenv = require('dotenv').config({ path: 'tests/.env.test', processEnv: {} }) -// const parsed = dotenvExpand.expand(dotenv).parsed -// -// ct.equal(parsed.EXPAND_DEFAULT_SPECIAL_CHARACTERS, 'machine_env') -// ct.equal(parsed.EXPAND_DEFAULT_SPECIAL_CHARACTERS2, 'machine_env') -// -// ct.end() -// }) -// -// t.test('expands environment variables existing already on the machine even with a default with special characters (process.env)', ct => { -// const dotenv = require('dotenv').config({ path: 'tests/.env.test' }) -// const parsed = dotenvExpand.expand(dotenv).parsed -// -// ct.equal(parsed.EXPAND_DEFAULT_SPECIAL_CHARACTERS, 'machine_env') -// ct.equal(parsed.EXPAND_DEFAULT_SPECIAL_CHARACTERS2, 'machine_env') -// -// ct.end() -// }) -// -// t.test('should expand with default value correctly', ct => { -// const dotenv = require('dotenv').config({ path: 'tests/.env.test', processEnv: {} }) -// const parsed = dotenvExpand.expand(dotenv).parsed -// -// ct.equal(parsed.UNDEFINED_EXPAND_DEFAULT_SPECIAL_CHARACTERS, '/default/path:with/colon') -// ct.equal(parsed.UNDEFINED_EXPAND_DEFAULT_SPECIAL_CHARACTERS2, '/default/path:with/colon') -// ct.equal(parsed.NO_CURLY_BRACES_UNDEFINED_EXPAND_DEFAULT_SPECIAL_CHARACTERS, ':-/default/path:with/colon') -// ct.equal(parsed.NO_CURLY_BRACES_UNDEFINED_EXPAND_DEFAULT_SPECIAL_CHARACTERS2, '-/default/path:with/colon') -// -// ct.end() -// }) -// -// t.test('should expand with default nested value correctly', ct => { -// const dotenv = require('dotenv').config({ path: 'tests/.env.test', processEnv: {} }) -// const parsed = dotenvExpand.expand(dotenv).parsed -// -// ct.equal(parsed.UNDEFINED_EXPAND_DEFAULT_SPECIAL_CHARACTERS_NESTED, '/default/path:with/colon') -// ct.equal(parsed.UNDEFINED_EXPAND_DEFAULT_SPECIAL_CHARACTERS_NESTED2, '/default/path:with/colon') -// -// ct.end() -// }) -// -// t.test('should expand variables with "." in names correctly', ct => { -// const dotenv = require('dotenv').config({ path: 'tests/.env.test', processEnv: {} }) -// const parsed = dotenvExpand.expand(dotenv).parsed -// -// ct.equal(parsed['POSTGRESQL.MAIN.USER'], parsed['POSTGRESQL.BASE.USER']) -// -// ct.end() -// }) -// -// t.test('handles value of only $', ct => { -// const dotenv = require('dotenv').config({ path: 'tests/.env.test', processEnv: {} }) -// const parsed = dotenvExpand.expand(dotenv).parsed -// -// ct.equal(parsed.DOLLAR, '$') -// -// ct.end() -// }) -// -// t.test('handles $one$two', ct => { -// const dotenv = require('dotenv').config({ path: 'tests/.env.test', processEnv: {} }) -// const parsed = dotenvExpand.expand(dotenv).parsed -// -// ct.equal(parsed.ONETWO, 'onetwo') -// ct.equal(parsed.ONETWO_SIMPLE, 'onetwo') -// ct.equal(parsed.ONETWO_SIMPLE2, 'onetwo') -// ct.equal(parsed.ONETWO_SUPER_SIMPLE, 'onetwo') -// -// ct.end() -// }) -// -// t.test('handles two dollar signs', ct => { -// const dotenv = require('dotenv').config({ path: 'tests/.env.test', processEnv: {} }) -// const parsed = dotenvExpand.expand(dotenv).parsed -// -// ct.equal(parsed.TWO_DOLLAR_SIGNS, 'abcd$') -// -// ct.end() -// }) -// -// t.test('does not choke', ct => { -// const dotenv = require('dotenv').config({ path: 'tests/.env.test', processEnv: {} }) -// const parsed = dotenvExpand.expand(dotenv).parsed -// -// ct.equal(parsed.DONT_CHOKE1, '.kZh`>4[,[DDU-*Jt+[;8-,@K=,9%;F9KsoXqOE)gpG^X!{)Q+/9Fc(QF}i[NEi!') -// ct.equal(parsed.DONT_CHOKE2, '=;+=CNy3)-D=zI6gRP2w$B@0K;Y]e^EFnCmx$Dx?;.9wf-rgk1BcTR0]JtY&fw;>HEwms`D8E2H') -// ct.equal(parsed.DONT_CHOKE4, 'm]zjzfRItw2gs[2:{p{ugENyFw9m)tH6_VCQzer`*noVaIMunx=[1-AMgAcwmPkToxTaB?kgdF5y`A8m=Oa-B!)') -// ct.equal(parsed.DONT_CHOKE6, 'xlC&* { -// const dotenv = require('dotenv').config({ path: 'tests/.env.test', processEnv: {} }) -// const parsed = dotenvExpand.expand(dotenv).parsed -// -// ct.equal(parsed.EXPAND_SELF, '') // because it ends up accessing parsed[key]. -// -// ct.end() -// }) -// -// t.test('expands DOMAIN with ${HOST}', ct => { -// const dotenv = require('dotenv').config({ path: 'tests/.env.test' }) -// const parsed = dotenvExpand.expand(dotenv).parsed -// -// ct.equal(parsed.HOST, 'something') -// ct.equal(parsed.DOMAIN, 'https://something') -// -// ct.end() -// }) -// -// t.test('does not attempt to expand password if already existed in processEnv', ct => { -// process.env.PASSWORD = 'pas$word' -// -// const dotenv = require('dotenv').config({ path: 'tests/.env.test' }) -// dotenvExpand.expand(dotenv) -// -// ct.equal(process.env.PASSWORD, 'pas$word') -// -// ct.end() -// }) -// -// t.test('does not expand dollar sign that are not variables', ct => { -// const dotenv = { -// parsed: { -// NO_VARIABLES: '\\$.$+$-$$' -// } -// } -// const parsed = dotenvExpand.expand(dotenv).parsed -// -// ct.equal(parsed.NO_VARIABLES, '$.$+$-$$') -// -// ct.end() -// }) -// -// t.test('expands recursively', ct => { -// const dotenv = { -// parsed: { -// MOCK_SERVER_PORT: '8090', -// MOCK_SERVER_HOST: 'http://localhost:${MOCK_SERVER_PORT}', -// BACKEND_API_HEALTH_CHECK_URL: '${MOCK_SERVER_HOST}/ci-health-check' -// } -// } -// const parsed = dotenvExpand.expand(dotenv).parsed -// -// ct.equal(parsed.MOCK_SERVER_PORT, '8090') -// ct.equal(parsed.MOCK_SERVER_HOST, 'http://localhost:8090') -// ct.equal(parsed.BACKEND_API_HEALTH_CHECK_URL, 'http://localhost:8090/ci-health-check') -// -// ct.end() -// }) -// -// t.test('expands recursively reverse order', ct => { -// const dotenv = { -// parsed: { -// BACKEND_API_HEALTH_CHECK_URL: '${MOCK_SERVER_HOST}/ci-health-check', -// MOCK_SERVER_HOST: 'http://localhost:${MOCK_SERVER_PORT}', -// MOCK_SERVER_PORT: '8090' -// } -// } -// const parsed = dotenvExpand.expand(dotenv).parsed -// -// ct.equal(parsed.MOCK_SERVER_PORT, '8090') -// ct.equal(parsed.MOCK_SERVER_HOST, 'http://localhost:8090') -// ct.equal(parsed.BACKEND_API_HEALTH_CHECK_URL, 'http://localhost:8090/ci-health-check') -// -// ct.end() -// }) -// -// t.test('expands recursively', ct => { -// const dotenv = require('dotenv').config({ path: 'tests/.env.test' }) -// dotenvExpand.expand(dotenv) -// -// ct.equal(process.env.PASSWORD_EXPAND, 'password') -// ct.equal(process.env.PASSWORD_EXPAND_SIMPLE, 'password') -// ct.equal(process.env.PASSWORD, 'password') -// ct.equal(process.env.PASSWORD_EXPAND_NESTED, 'password') -// ct.equal(process.env.PASSWORD_EXPAND_NESTED, 'password') -// -// ct.end() -// }) -// -// t.test('expands recursively but is smart enough to not attempt expansion of a pre-set env in process.env', ct => { -// process.env.PASSWORD = 'pas$word' -// -// const dotenv = require('dotenv').config({ path: 'tests/.env.test' }) -// dotenvExpand.expand(dotenv) -// -// ct.equal(process.env.PASSWORD_EXPAND, 'pas$word') -// ct.equal(process.env.PASSWORD_EXPAND_SIMPLE, 'pas$word') -// ct.equal(process.env.PASSWORD, 'pas$word') -// ct.equal(process.env.PASSWORD_EXPAND_NESTED, 'pas$word') -// ct.equal(process.env.PASSWORD_EXPAND_NESTED, 'pas$word') -// -// ct.end() -// }) + + ct.end() +}) + +t.test('expands environment variables existing already on the machine', ct => { + process.env.MACHINE = 'machine' + + const dotenv = require('dotenv').config({ path: 'tests/.env.test', processEnv: {} }) + dotenvExpand.expand(dotenv) + + ct.equal(process.env.MACHINE_EXPAND, 'machine') + + ct.end() +}) + +t.test('expands environment variables existing already on the machine (process.env)', ct => { + process.env.MACHINE = 'machine' + + const dotenv = require('dotenv').config({ path: 'tests/.env.test' }) + dotenvExpand.expand(dotenv) + + ct.equal(process.env.MACHINE_EXPAND, 'machine') + + ct.end() +}) + +t.test('expands missing environment variables to an empty string', ct => { + const dotenv = require('dotenv').config({ path: 'tests/.env.test', processEnv: {} }) + const parsed = dotenvExpand.expand(dotenv).parsed + + ct.equal(parsed.UNDEFINED_EXPAND, '') + + ct.end() +}) + +t.test('expands missing environment variables to an empty string (process.env)', ct => { + const dotenv = require('dotenv').config({ path: 'tests/.env.test' }) + const parsed = dotenvExpand.expand(dotenv).parsed + + ct.equal(parsed.UNDEFINED_EXPAND, '') + + ct.end() +}) + +t.test('expands environment variables existing already on the machine even with a default', ct => { + process.env.MACHINE = 'machine' + + const dotenv = require('dotenv').config({ path: 'tests/.env.test', processEnv: {} }) + dotenvExpand.expand(dotenv) + + ct.equal(process.env.EXPAND_DEFAULT, 'machine') + + ct.end() +}) + +t.test('expands environment variables existing already on the machine even with a default when nested', ct => { + process.env.MACHINE = 'machine' + + const dotenv = require('dotenv').config({ path: 'tests/.env.test', processEnv: {} }) + dotenvExpand.expand(dotenv) + + ct.equal(process.env.EXPAND_DEFAULT_NESTED, 'machine') + ct.equal(process.env.EXPAND_DEFAULT_NESTED2, 'machine') + + ct.end() +}) + +t.test('expands environment variables undefined with one already on the machine even with a default when nested', ct => { + process.env.MACHINE = 'machine' + + const dotenv = require('dotenv').config({ path: 'tests/.env.test', processEnv: {} }) + dotenvExpand.expand(dotenv) + + ct.equal(process.env.UNDEFINED_EXPAND_NESTED, 'machine') + + ct.end() +}) + +t.test('expands missing environment variables to an empty string but replaces with default', ct => { + const dotenv = require('dotenv').config({ path: 'tests/.env.test', processEnv: {} }) + const parsed = dotenvExpand.expand(dotenv).parsed + + ct.equal(parsed.UNDEFINED_EXPAND_DEFAULT, 'default') + ct.equal(parsed.UNDEFINED_EXPAND_DEFAULT2, 'default') + + ct.end() +}) + +t.test('expands environent variables and concats with default nested', ct => { + const dotenv = require('dotenv').config({ path: 'tests/.env.test', processEnv: {} }) + const parsed = dotenvExpand.expand(dotenv).parsed + + ct.equal(parsed.EXPAND_DEFAULT_NESTED_TWICE, 'machine_envdefault') + ct.equal(parsed.EXPAND_DEFAULT_NESTED_TWICE2, 'machine_envdefault') + + ct.end() +}) + +t.test('expands environent variables and concats with default nested', ct => { + const dotenv = require('dotenv').config({ path: 'tests/.env.test' }) + const parsed = dotenvExpand.expand(dotenv).parsed + + ct.equal(parsed.EXPAND_DEFAULT_NESTED_TWICE, 'machine_envdefault') + ct.equal(parsed.EXPAND_DEFAULT_NESTED_TWICE2, 'machine_envdefault') + + ct.end() +}) + +t.test('expands missing environment variables to an empty string but replaces with default nested', ct => { + const dotenv = require('dotenv').config({ path: 'tests/.env.test', processEnv: {} }) + const parsed = dotenvExpand.expand(dotenv).parsed + + ct.equal(parsed.UNDEFINED_EXPAND_DEFAULT_NESTED, 'default') + ct.equal(parsed.UNDEFINED_EXPAND_DEFAULT_NESTED2, 'default') + + ct.end() +}) + +t.test('expands missing environment variables to an empty string but replaces with default nested twice', ct => { + const dotenv = require('dotenv').config({ path: 'tests/.env.test', processEnv: {} }) + const parsed = dotenvExpand.expand(dotenv).parsed + + ct.equal(parsed.UNDEFINED_EXPAND_DEFAULT_NESTED_TWICE, 'default') + ct.equal(parsed.UNDEFINED_EXPAND_DEFAULT_NESTED_TWICE2, 'default') + + ct.end() +}) + +t.test('prioritizes machine key expansion over .env', ct => { + process.env.MACHINE = 'machine' + + const dotenv = require('dotenv').config({ path: 'tests/.env.test', processEnv: {} }) + const parsed = dotenvExpand.expand(dotenv).parsed + + ct.equal(parsed.MACHINE_EXPAND, 'machine') + + ct.end() +}) + +t.test('multiple expand', ct => { + const dotenv = require('dotenv').config({ path: 'tests/.env.test', processEnv: {} }) + const parsed = dotenvExpand.expand(dotenv).parsed + + ct.equal(parsed.MONGOLAB_URI, 'mongodb://username:password@abcd1234.mongolab.com:12345/heroku_db') + + ct.end() +}) + +t.test('should expand recursively', ct => { + const dotenv = require('dotenv').config({ path: 'tests/.env.test', processEnv: {} }) + const parsed = dotenvExpand.expand(dotenv).parsed + + ct.equal(parsed.MONGOLAB_URI_RECURSIVELY, 'mongodb://username:password@abcd1234.mongolab.com:12345/heroku_db') + + ct.end() +}) + +t.test('multiple expand', ct => { + const dotenv = require('dotenv').config({ path: 'tests/.env.test', processEnv: {} }) + const parsed = dotenvExpand.expand(dotenv).parsed + + ct.equal(parsed.NO_CURLY_BRACES_URI, 'mongodb://username:password@abcd1234.mongolab.com:12345/heroku_db') + + ct.end() +}) + +t.test('should expand recursively', ct => { + const dotenv = require('dotenv').config({ path: 'tests/.env.test', processEnv: {} }) + const parsed = dotenvExpand.expand(dotenv).parsed + + ct.equal(parsed.NO_CURLY_BRACES_URI_RECURSIVELY, 'mongodb://username:password@abcd1234.mongolab.com:12345/heroku_db') + + ct.end() +}) + +t.test('can write to an object rather than process.env if user provides it', ct => { + const myObject = {} + const dotenv = { + processEnv: myObject, + parsed: { + SHOULD_NOT_EXIST: 'testing' + } + } + const parsed = dotenvExpand.expand(dotenv).parsed + const evaluation = typeof process.env.SHOULD_NOT_EXIST + + ct.equal(parsed.SHOULD_NOT_EXIST, 'testing') + ct.equal(myObject.SHOULD_NOT_EXIST, 'testing') + ct.equal(evaluation, 'undefined') + + ct.end() +}) + +t.test('expands environment variables existing already on the machine even with a default with special characters', ct => { + const dotenv = require('dotenv').config({ path: 'tests/.env.test', processEnv: {} }) + const parsed = dotenvExpand.expand(dotenv).parsed + + ct.equal(parsed.EXPAND_DEFAULT_SPECIAL_CHARACTERS, 'machine_env') + ct.equal(parsed.EXPAND_DEFAULT_SPECIAL_CHARACTERS2, 'machine_env') + + ct.end() +}) + +t.test('expands environment variables existing already on the machine even with a default with special characters (process.env)', ct => { + const dotenv = require('dotenv').config({ path: 'tests/.env.test' }) + const parsed = dotenvExpand.expand(dotenv).parsed + + ct.equal(parsed.EXPAND_DEFAULT_SPECIAL_CHARACTERS, 'machine_env') + ct.equal(parsed.EXPAND_DEFAULT_SPECIAL_CHARACTERS2, 'machine_env') + + ct.end() +}) + +t.test('should expand with default value correctly', ct => { + const dotenv = require('dotenv').config({ path: 'tests/.env.test', processEnv: {} }) + const parsed = dotenvExpand.expand(dotenv).parsed + + ct.equal(parsed.UNDEFINED_EXPAND_DEFAULT_SPECIAL_CHARACTERS, '/default/path:with/colon') + ct.equal(parsed.UNDEFINED_EXPAND_DEFAULT_SPECIAL_CHARACTERS2, '/default/path:with/colon') + ct.equal(parsed.NO_CURLY_BRACES_UNDEFINED_EXPAND_DEFAULT_SPECIAL_CHARACTERS, '/default/path:with/colon') + ct.equal(parsed.NO_CURLY_BRACES_UNDEFINED_EXPAND_DEFAULT_SPECIAL_CHARACTERS2, '/default/path:with/colon') + + ct.end() +}) + +t.test('should expand with default nested value correctly', ct => { + const dotenv = require('dotenv').config({ path: 'tests/.env.test', processEnv: {} }) + const parsed = dotenvExpand.expand(dotenv).parsed + + ct.equal(parsed.UNDEFINED_EXPAND_DEFAULT_SPECIAL_CHARACTERS_NESTED, '/default/path:with/colon') + ct.equal(parsed.UNDEFINED_EXPAND_DEFAULT_SPECIAL_CHARACTERS_NESTED2, '/default/path:with/colon') + + ct.end() +}) + +t.test('should expand variables with "." in names correctly', ct => { + const dotenv = require('dotenv').config({ path: 'tests/.env.test', processEnv: {} }) + const parsed = dotenvExpand.expand(dotenv).parsed + + ct.equal(parsed['POSTGRESQL.MAIN.USER'], parsed['POSTGRESQL.BASE.USER']) + + ct.end() +}) + +t.test('handles value of only $', ct => { + const dotenv = require('dotenv').config({ path: 'tests/.env.test', processEnv: {} }) + const parsed = dotenvExpand.expand(dotenv).parsed + + ct.equal(parsed.DOLLAR, '$') + + ct.end() +}) + +t.test('handles $one$two', ct => { + const dotenv = require('dotenv').config({ path: 'tests/.env.test', processEnv: {} }) + const parsed = dotenvExpand.expand(dotenv).parsed + + ct.equal(parsed.ONETWO, 'onetwo') + ct.equal(parsed.ONETWO_SIMPLE, 'onetwo') + ct.equal(parsed.ONETWO_SIMPLE2, 'onetwo') + ct.equal(parsed.ONETWO_SUPER_SIMPLE, 'onetwo') + + ct.end() +}) + +t.test('handles two dollar signs', ct => { + const dotenv = require('dotenv').config({ path: 'tests/.env.test', processEnv: {} }) + const parsed = dotenvExpand.expand(dotenv).parsed + + ct.equal(parsed.TWO_DOLLAR_SIGNS, 'abcd$') + + ct.end() +}) + +t.test('does not choke', ct => { + const dotenv = require('dotenv').config({ path: 'tests/.env.test', processEnv: {} }) + const parsed = dotenvExpand.expand(dotenv).parsed + + ct.equal(parsed.DONT_CHOKE1, '.kZh`>4[,[DDU-*Jt+[;8-,@K=,9%;F9KsoXqOE)gpG^X!{)Q+/9Fc(QF}i[NEi!') + ct.equal(parsed.DONT_CHOKE2, '=;+=CNy3)-D=zI6gRP2w$B@0K;Y]e^EFnCmx$Dx?;.9wf-rgk1BcTR0]JtY&fw;>HEwms`D8E2H') + ct.equal(parsed.DONT_CHOKE4, 'm]zjzfRItw2gs[2:{p{ugENyFw9m)tH6_VCQzer`*noVaIMunx=[1-AMgAcwmPkToxTaB?kgdF5y`A8m=Oa-B!)') + ct.equal(parsed.DONT_CHOKE6, 'xlC&* { + const dotenv = require('dotenv').config({ path: 'tests/.env.test', processEnv: {} }) + const parsed = dotenvExpand.expand(dotenv).parsed + + ct.equal(parsed.EXPAND_SELF, '') // because it ends up accessing parsed[key]. + + ct.end() +}) + +t.test('expands DOMAIN with ${HOST}', ct => { + const dotenv = require('dotenv').config({ path: 'tests/.env.test' }) + const parsed = dotenvExpand.expand(dotenv).parsed + + ct.equal(parsed.HOST, 'something') + ct.equal(parsed.DOMAIN, 'https://something') + + ct.end() +}) + +t.test('does not attempt to expand password if already existed in processEnv', ct => { + process.env.PASSWORD = 'pas$word' + + const dotenv = require('dotenv').config({ path: 'tests/.env.test' }) + dotenvExpand.expand(dotenv) + + ct.equal(process.env.PASSWORD, 'pas$word') + + ct.end() +}) + +t.test('does not expand dollar sign that are not variables', ct => { + const dotenv = { + parsed: { + NO_VARIABLES: '\\$.$+$-$$' + } + } + const parsed = dotenvExpand.expand(dotenv).parsed + + ct.equal(parsed.NO_VARIABLES, '$.$+$-$$') + + ct.end() +}) + +t.test('expands recursively', ct => { + const dotenv = { + parsed: { + MOCK_SERVER_PORT: '8090', + MOCK_SERVER_HOST: 'http://localhost:${MOCK_SERVER_PORT}', + BACKEND_API_HEALTH_CHECK_URL: '${MOCK_SERVER_HOST}/ci-health-check' + } + } + const parsed = dotenvExpand.expand(dotenv).parsed + + ct.equal(parsed.MOCK_SERVER_PORT, '8090') + ct.equal(parsed.MOCK_SERVER_HOST, 'http://localhost:8090') + ct.equal(parsed.BACKEND_API_HEALTH_CHECK_URL, 'http://localhost:8090/ci-health-check') + + ct.end() +}) + +t.test('expands recursively reverse order', ct => { + const dotenv = { + parsed: { + BACKEND_API_HEALTH_CHECK_URL: '${MOCK_SERVER_HOST}/ci-health-check', + MOCK_SERVER_HOST: 'http://localhost:${MOCK_SERVER_PORT}', + MOCK_SERVER_PORT: '8090' + } + } + const parsed = dotenvExpand.expand(dotenv).parsed + + ct.equal(parsed.MOCK_SERVER_PORT, '8090') + ct.equal(parsed.MOCK_SERVER_HOST, 'http://localhost:8090') + ct.equal(parsed.BACKEND_API_HEALTH_CHECK_URL, 'http://localhost:8090/ci-health-check') + + ct.end() +}) + +t.test('expands recursively', ct => { + const dotenv = require('dotenv').config({ path: 'tests/.env.test' }) + dotenvExpand.expand(dotenv) + + ct.equal(process.env.PASSWORD_EXPAND, 'password') + ct.equal(process.env.PASSWORD_EXPAND_SIMPLE, 'password') + ct.equal(process.env.PASSWORD, 'password') + ct.equal(process.env.PASSWORD_EXPAND_NESTED, 'password') + ct.equal(process.env.PASSWORD_EXPAND_NESTED, 'password') + + ct.end() +}) + +t.test('expands recursively but is smart enough to not attempt expansion of a pre-set env in process.env', ct => { + process.env.PASSWORD = 'pas$word' + + const dotenv = require('dotenv').config({ path: 'tests/.env.test' }) + dotenvExpand.expand(dotenv) + + ct.equal(process.env.PASSWORD_EXPAND, 'pas$word') + ct.equal(process.env.PASSWORD_EXPAND_SIMPLE, 'pas$word') + ct.equal(process.env.PASSWORD, 'pas$word') + ct.equal(process.env.PASSWORD_EXPAND_NESTED, 'pas$word') + ct.equal(process.env.PASSWORD_EXPAND_NESTED, 'pas$word') + + ct.end() +})