From 0053f772e0141b0a8ba8fca086331ebef8404879 Mon Sep 17 00:00:00 2001 From: Scott Motte Date: Thu, 8 Feb 2024 17:44:55 -0800 Subject: [PATCH 1/5] demonstrate failing test --- tests/.env.test | 6 ++++++ tests/main.js | 10 ++++++++++ 2 files changed, 16 insertions(+) diff --git a/tests/.env.test b/tests/.env.test index f389b4a..633a557 100644 --- a/tests/.env.test +++ b/tests/.env.test @@ -34,3 +34,9 @@ POSTGRESQL.BASE.USER=postgres POSTGRESQL.MAIN.USER=${POSTGRESQL.BASE.USER} DOLLAR=$ + +ONE=one +TWO=two +ONETWO=${ONE}${TWO} +ONETWO_SIMPLE=${ONE}$TWO +ONETWO_SUPER_SIMPLE=$ONE$TWO diff --git a/tests/main.js b/tests/main.js index 210f0fe..d5700a7 100644 --- a/tests/main.js +++ b/tests/main.js @@ -367,3 +367,13 @@ t.test('handles value of only $', ct => { 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_SUPER_SIMPLE, 'onetwo') + + ct.end() +}) From 589f29ad740a765a51ce47967138ee785acfb2fa Mon Sep 17 00:00:00 2001 From: Scott Motte Date: Fri, 9 Feb 2024 10:30:43 -0800 Subject: [PATCH 2/5] handling everything except nested --- lib/main.js | 129 ++++++++++++++++++++++++++++++------------------ tests/.env.test | 1 + tests/main.js | 2 + 3 files changed, 84 insertions(+), 48 deletions(-) diff --git a/lib/main.js b/lib/main.js index 567e7f3..ee673f4 100644 --- a/lib/main.js +++ b/lib/main.js @@ -1,57 +1,90 @@ 'use strict' -// like String.prototype.search but returns the last index -function _searchLast (str, rgx) { - const matches = Array.from(str.matchAll(rgx)) - return matches.length > 0 ? matches.slice(-1)[0].index : -1 -} - -function _interpolate (value, processEnv, parsed) { - // find the last unescaped dollar sign in the value to evaluate - const lastUnescapedDollarSignIndex = _searchLast(value, /(?!(?<=\\))\$/g) - - // return early unless unescaped dollar sign - if (lastUnescapedDollarSignIndex === -1) { - return value - } - - // This is the right-most group of variables in the string - const rightMostGroup = value.slice(lastUnescapedDollarSignIndex) - - /** - * This finds the inner most variable/group divided - * by variable name and default value (if present) - * ( - * (?!(?<=\\))\$ // only match dollar signs that are not escaped - * {? // optional opening curly brace - * ([\w.]+) // match the variable name - * (?::-([^}\\]*))? // match an optional default value - * }? // optional closing curly brace - * ) - */ - const matchGroup = /((?!(?<=\\))\${?([\w.]+)(?::-([^}\\]*))?}?)/ - const match = rightMostGroup.match(matchGroup) - - if (match != null) { - const [, group, key, defaultValue] = match - const replacementString = processEnv[key] || defaultValue || parsed[key] || '' - const modifiedValue = value.replace(group, replacementString) - - // return early for scenario like processEnv.PASSWORD = 'pas$word' - if (processEnv[key] && modifiedValue === processEnv[key]) { - return modifiedValue - } - - return _interpolate(modifiedValue, processEnv, parsed) - } - - return value -} +// // like String.prototype.search but returns the last index +// function _searchLast (str, rgx) { +// const matches = Array.from(str.matchAll(rgx)) +// return matches.length > 0 ? matches.slice(-1)[0].index : -1 +// } +// +// function _interpolate (value, processEnv, parsed) { +// // find the last unescaped dollar sign in the value to evaluate +// const lastUnescapedDollarSignIndex = _searchLast(value, /(?!(?<=\\))\$/g) +// +// // return early unless unescaped dollar sign +// if (lastUnescapedDollarSignIndex === -1) { +// return value +// } +// +// // This is the right-most group of variables in the string +// const rightMostGroup = value.slice(lastUnescapedDollarSignIndex) +// +// console.log('rightMostGroup', rightMostGroup) +// +// /** +// * This finds the inner most variable/group divided +// * by variable name and default value (if present) +// * ( +// * (?!(?<=\\))\$ // only match dollar signs that are not escaped +// * {? // optional opening curly brace +// * ([\w.]+) // match the variable name +// * (?::-([^}\\]*))? // match an optional default value +// * }? // optional closing curly brace +// * ) +// */ +// const matchGroup = /((?!(?<=\\))\${?([\w.]+)(?::-([^}\\]*))?}?)/ +// const match = rightMostGroup.match(matchGroup) +// +// if (match != null) { +// const [, group, key, defaultValue] = match +// const replacementString = processEnv[key] || defaultValue || parsed[key] || '' +// const modifiedValue = value.replace(group, replacementString) +// +// // return early for scenario like processEnv.PASSWORD = 'pas$word' +// if (processEnv[key] && modifiedValue === processEnv[key]) { +// return modifiedValue +// } +// +// return _interpolate(modifiedValue, processEnv, parsed) +// } +// +// return value +// } function _resolveEscapeSequences (value) { return value.replace(/\\\$/g, '$') } +function substitute (value, processEnv, parsed) { + // * ( + // * (?!(?<=\\))\$ // only match dollar signs that are not escaped + // * {? // optional opening curly brace + // * ([\w.]+) // match the variable name + // * (?::-([^}\\]*))? // match an optional default value + // * }? // optional closing curly brace + // * ) + // + // * / + // * (\\)? # is it escaped with a backslash? + // * (\$) # literal $ + // * (?!\() # shouldnt be followed by parenthesis + // * \{? # allow brace wrapping + // * ([\w.]+)? # optional alpha nums + // * (?::-([^}\\]*))? # optional default + // * \}? # closing brace + // * /xi + const SUB_REGEX = /(\\)?(\$)(?!\()(\{?)([\w.]+)?(?::-([^}\\]*))?(\}?)/gi + + return value.replace(SUB_REGEX, (match, escaped, dollarSign, openBrace, key, defaultValue, closeBrace) => { + if (escaped === '\\') { + return match.slice(1) + } else if (key) { + return processEnv[key] || defaultValue || parsed[key] || '' + } else { + return match + } + }) +} + function expand (options) { let processEnv = process.env if (options && options.processEnv != null) { @@ -65,7 +98,7 @@ function expand (options) { if (Object.prototype.hasOwnProperty.call(processEnv, key)) { value = processEnv[key] } else { - value = _interpolate(value, processEnv, options.parsed) + value = substitute(value, processEnv, options.parsed) } options.parsed[key] = _resolveEscapeSequences(value) diff --git a/tests/.env.test b/tests/.env.test index 633a557..b5d991b 100644 --- a/tests/.env.test +++ b/tests/.env.test @@ -39,4 +39,5 @@ ONE=one TWO=two ONETWO=${ONE}${TWO} ONETWO_SIMPLE=${ONE}$TWO +ONETWO_SIMPLE2=$ONE${TWO} ONETWO_SUPER_SIMPLE=$ONE$TWO diff --git a/tests/main.js b/tests/main.js index d5700a7..e21b160 100644 --- a/tests/main.js +++ b/tests/main.js @@ -373,6 +373,8 @@ t.test('handles $one$two', ct => { 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() From 2771f31ceeed555b48da99e7cfd1197de3f0d30a Mon Sep 17 00:00:00 2001 From: Scott Motte Date: Fri, 9 Feb 2024 16:20:09 -0800 Subject: [PATCH 3/5] nested interpolation - excepting 1 --- lib/main.js | 25 +++++++++++++++++++------ tests/.env.test | 35 +++++++++++++++++++---------------- tests/main.js | 28 ++++++++++++++-------------- 3 files changed, 52 insertions(+), 36 deletions(-) diff --git a/lib/main.js b/lib/main.js index ee673f4..f19339a 100644 --- a/lib/main.js +++ b/lib/main.js @@ -54,7 +54,7 @@ function _resolveEscapeSequences (value) { return value.replace(/\\\$/g, '$') } -function substitute (value, processEnv, parsed) { +function interpolate (value, processEnv, parsed) { // * ( // * (?!(?<=\\))\$ // only match dollar signs that are not escaped // * {? // optional opening curly brace @@ -68,17 +68,30 @@ function substitute (value, processEnv, parsed) { // * (\$) # literal $ // * (?!\() # shouldnt be followed by parenthesis // * \{? # allow brace wrapping - // * ([\w.]+)? # optional alpha nums + // * ([\w.]+)? # optional alpha nums // * (?::-([^}\\]*))? # optional default // * \}? # closing brace // * /xi - const SUB_REGEX = /(\\)?(\$)(?!\()(\{?)([\w.]+)?(?::-([^}\\]*))?(\}?)/gi + // const SUB_REGEX = /(\\)?(\$)(?!\()(\{?)([\w.]+)?(?::-([^}\\]*))?(\}?)/gi + const SUB_REGEX = /(\\)?(\$)(?!\()(\{?)([\w.]+)(?::-((?:\$\{[^}]*\}|[^}])+))?(\}?)/gi return value.replace(SUB_REGEX, (match, escaped, dollarSign, openBrace, key, defaultValue, closeBrace) => { if (escaped === '\\') { return match.slice(1) } else if (key) { - return processEnv[key] || defaultValue || parsed[key] || '' + if (processEnv[key]) { + return processEnv[key] + } + + if (defaultValue) { + if (defaultValue.startsWith('$')) { + return interpolate(defaultValue, processEnv, parsed) + } else { + return defaultValue + } + } + + return parsed[key] || '' } else { return match } @@ -94,11 +107,11 @@ function expand (options) { for (const key in options.parsed) { let value = options.parsed[key] - // don't interpolate the processEnv value if it exists there already + // don't interpolate if it exists already in processEnv if (Object.prototype.hasOwnProperty.call(processEnv, key)) { value = processEnv[key] } else { - value = substitute(value, processEnv, options.parsed) + value = interpolate(value, processEnv, options.parsed) } options.parsed[key] = _resolveEscapeSequences(value) diff --git a/tests/.env.test b/tests/.env.test index b5d991b..32f236b 100644 --- a/tests/.env.test +++ b/tests/.env.test @@ -1,20 +1,23 @@ -NODE_ENV=test BASIC=basic BASIC_EXPAND=$BASIC MACHINE=machine_env MACHINE_EXPAND=$MACHINE -UNDEFINED_EXPAND=$UNDEFINED_ENV_KEY + ESCAPED_EXPAND=\$ESCAPED -DEFINED_EXPAND_WITH_DEFAULT=${MACHINE:-default} -DEFINED_EXPAND_WITH_DEFAULT_NESTED=${MACHINE:-${UNDEFINED_ENV_KEY:-default}} -UNDEFINED_EXPAND_WITH_DEFINED_NESTED=${UNDEFINED_ENV_KEY:-${MACHINE:-default}} -UNDEFINED_EXPAND_WITH_DEFAULT=${UNDEFINED_ENV_KEY:-default} -UNDEFINED_EXPAND_WITH_DEFAULT_NESTED=${UNDEFINED_ENV_KEY:-${UNDEFINED_ENV_KEY_2:-default}} -DEFINED_EXPAND_WITH_DEFAULT_NESTED_TWICE=${UNDEFINED_ENV_KEY:-${MACHINE}${UNDEFINED_ENV_KEY_3:-default}} -UNDEFINED_EXPAND_WITH_DEFAULT_NESTED_TWICE=${UNDEFINED_ENV_KEY:-${UNDEFINED_ENV_KEY_2:-${UNDEFINED_ENV_KEY_3:-default}}} -DEFINED_EXPAND_WITH_DEFAULT_WITH_SPECIAL_CHARACTERS=${MACHINE:-/default/path:with/colon} -UNDEFINED_EXPAND_WITH_DEFAULT_WITH_SPECIAL_CHARACTERS=${UNDEFINED_ENV_KEY:-/default/path:with/colon} -UNDEFINED_EXPAND_WITH_DEFAULT_WITH_SPECIAL_CHARACTERS_NESTED=${UNDEFINED_ENV_KEY:-${UNDEFINED_ENV_KEY_2:-/default/path:with/colon}} + +EXPAND_DEFAULT=${MACHINE:-default} +EXPAND_DEFAULT_NESTED=${MACHINE:-${UNDEFINED:-default}} +EXPAND_DEFAULT_NESTED_TWICE=${UNDEFINED:-${MACHINE}${UNDEFINED:-default}} +EXPAND_DEFAULT_SPECIAL_CHARACTERS=${MACHINE:-/default/path:with/colon} + +UNDEFINED_EXPAND=$UNDEFINED +UNDEFINED_EXPAND_NESTED=${UNDEFINED:-${MACHINE:-default}} +UNDEFINED_EXPAND_DEFAULT=${UNDEFINED:-default} +UNDEFINED_EXPAND_DEFAULT_NESTED=${UNDEFINED:-${UNDEFINED:-default}} +UNDEFINED_EXPAND_DEFAULT_NESTED_TWICE=${UNDEFINED:-${UNDEFINED:-${UNDEFINED:-default}}} +UNDEFINED_EXPAND_DEFAULT_SPECIAL_CHARACTERS=${UNDEFINED:-/default/path:with/colon} +UNDEFINED_EXPAND_DEFAULT_SPECIAL_CHARACTERS_NESTED=${UNDEFINED:-${UNDEFINED_2:-/default/path:with/colon}} + MONGOLAB_DATABASE=heroku_db MONGOLAB_USER=username MONGOLAB_PASSWORD=password @@ -25,10 +28,10 @@ MONGOLAB_URI=mongodb://${MONGOLAB_USER}:${MONGOLAB_PASSWORD}@${MONGOLAB_DOMAIN}: MONGOLAB_USER_RECURSIVELY=${MONGOLAB_USER}:${MONGOLAB_PASSWORD} MONGOLAB_URI_RECURSIVELY=mongodb://${MONGOLAB_USER_RECURSIVELY}@${MONGOLAB_DOMAIN}:${MONGOLAB_PORT}/${MONGOLAB_DATABASE} -WITHOUT_CURLY_BRACES_URI=mongodb://$MONGOLAB_USER:$MONGOLAB_PASSWORD@$MONGOLAB_DOMAIN:$MONGOLAB_PORT/$MONGOLAB_DATABASE -WITHOUT_CURLY_BRACES_USER_RECURSIVELY=$MONGOLAB_USER:$MONGOLAB_PASSWORD -WITHOUT_CURLY_BRACES_URI_RECURSIVELY=mongodb://$MONGOLAB_USER_RECURSIVELY@$MONGOLAB_DOMAIN:$MONGOLAB_PORT/$MONGOLAB_DATABASE -WITHOUT_CURLY_BRACES_UNDEFINED_EXPAND_WITH_DEFAULT_WITH_SPECIAL_CHARACTERS=$UNDEFINED_ENV_KEY:-/default/path:with/colon +NO_CURLY_BRACES_URI=mongodb://$MONGOLAB_USER:$MONGOLAB_PASSWORD@$MONGOLAB_DOMAIN:$MONGOLAB_PORT/$MONGOLAB_DATABASE +NO_CURLY_BRACES_USER_RECURSIVELY=$MONGOLAB_USER:$MONGOLAB_PASSWORD +NO_CURLY_BRACES_URI_RECURSIVELY=mongodb://$MONGOLAB_USER_RECURSIVELY@$MONGOLAB_DOMAIN:$MONGOLAB_PORT/$MONGOLAB_DATABASE +NO_CURLY_BRACES_UNDEFINED_EXPAND_DEFAULT_SPECIAL_CHARACTERS=$UNDEFINED:-/default/path:with/colon POSTGRESQL.BASE.USER=postgres POSTGRESQL.MAIN.USER=${POSTGRESQL.BASE.USER} diff --git a/tests/main.js b/tests/main.js index e21b160..3cf06bb 100644 --- a/tests/main.js +++ b/tests/main.js @@ -67,7 +67,7 @@ t.test('does not expand environment variables existing already on the machine th t.test('expands missing environment variables to an empty string', ct => { const dotenv = { parsed: { - UNDEFINED_EXPAND: '$UNDEFINED_ENV_KEY' + UNDEFINED_EXPAND: '$UNDEFINED' } } const parsed = dotenvExpand.expand(dotenv).parsed @@ -194,7 +194,7 @@ t.test('expands environment variables existing already on the machine even with const dotenv = require('dotenv').config({ path: 'tests/.env.test', processEnv: {} }) dotenvExpand.expand(dotenv) - ct.equal(process.env.DEFINED_EXPAND_WITH_DEFAULT, 'machine') + ct.equal(process.env.EXPAND_DEFAULT, 'machine') ct.end() }) @@ -205,7 +205,7 @@ t.test('expands environment variables existing already on the machine even with const dotenv = require('dotenv').config({ path: 'tests/.env.test', processEnv: {} }) dotenvExpand.expand(dotenv) - ct.equal(process.env.DEFINED_EXPAND_WITH_DEFAULT_NESTED, 'machine') + ct.equal(process.env.EXPAND_DEFAULT_NESTED, 'machine') ct.end() }) @@ -216,7 +216,7 @@ t.test('expands environment variables undefined with one already on the machine const dotenv = require('dotenv').config({ path: 'tests/.env.test', processEnv: {} }) dotenvExpand.expand(dotenv) - ct.equal(process.env.UNDEFINED_EXPAND_WITH_DEFINED_NESTED, 'machine') + ct.equal(process.env.UNDEFINED_EXPAND_NESTED, 'machine') ct.end() }) @@ -225,7 +225,7 @@ t.test('expands missing environment variables to an empty string but replaces wi const dotenv = require('dotenv').config({ path: 'tests/.env.test', processEnv: {} }) const parsed = dotenvExpand.expand(dotenv).parsed - ct.equal(parsed.UNDEFINED_EXPAND_WITH_DEFAULT, 'default') + ct.equal(parsed.UNDEFINED_EXPAND_DEFAULT, 'default') ct.end() }) @@ -234,7 +234,7 @@ 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.DEFINED_EXPAND_WITH_DEFAULT_NESTED_TWICE, 'machinedefault') + ct.equal(parsed.EXPAND_DEFAULT_NESTED_TWICE, 'machinedefault') ct.end() }) @@ -243,7 +243,7 @@ t.test('expands missing environment variables to an empty string but replaces wi const dotenv = require('dotenv').config({ path: 'tests/.env.test', processEnv: {} }) const parsed = dotenvExpand.expand(dotenv).parsed - ct.equal(parsed.UNDEFINED_EXPAND_WITH_DEFAULT_NESTED, 'default') + ct.equal(parsed.UNDEFINED_EXPAND_DEFAULT_NESTED, 'default') ct.end() }) @@ -252,7 +252,7 @@ t.test('expands missing environment variables to an empty string but replaces wi const dotenv = require('dotenv').config({ path: 'tests/.env.test', processEnv: {} }) const parsed = dotenvExpand.expand(dotenv).parsed - ct.equal(parsed.UNDEFINED_EXPAND_WITH_DEFAULT_NESTED_TWICE, 'default') + ct.equal(parsed.UNDEFINED_EXPAND_DEFAULT_NESTED_TWICE, 'default') ct.end() }) @@ -290,7 +290,7 @@ t.test('multiple expand', ct => { const dotenv = require('dotenv').config({ path: 'tests/.env.test', processEnv: {} }) const parsed = dotenvExpand.expand(dotenv).parsed - ct.equal(parsed.WITHOUT_CURLY_BRACES_URI, 'mongodb://username:password@abcd1234.mongolab.com:12345/heroku_db') + ct.equal(parsed.NO_CURLY_BRACES_URI, 'mongodb://username:password@abcd1234.mongolab.com:12345/heroku_db') ct.end() }) @@ -299,7 +299,7 @@ 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.WITHOUT_CURLY_BRACES_URI_RECURSIVELY, 'mongodb://username:password@abcd1234.mongolab.com:12345/heroku_db') + ct.equal(parsed.NO_CURLY_BRACES_URI_RECURSIVELY, 'mongodb://username:password@abcd1234.mongolab.com:12345/heroku_db') ct.end() }) @@ -326,7 +326,7 @@ t.test('expands environment variables existing already on the machine even with const dotenv = require('dotenv').config({ path: 'tests/.env.test', processEnv: {} }) const parsed = dotenvExpand.expand(dotenv).parsed - ct.equal(parsed.DEFINED_EXPAND_WITH_DEFAULT_WITH_SPECIAL_CHARACTERS, 'machine') + ct.equal(parsed.EXPAND_DEFAULT_SPECIAL_CHARACTERS, 'machine') ct.end() }) @@ -335,8 +335,8 @@ 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_WITH_DEFAULT_WITH_SPECIAL_CHARACTERS, '/default/path:with/colon') - ct.equal(parsed.WITHOUT_CURLY_BRACES_UNDEFINED_EXPAND_WITH_DEFAULT_WITH_SPECIAL_CHARACTERS, '/default/path:with/colon') + ct.equal(parsed.UNDEFINED_EXPAND_DEFAULT_SPECIAL_CHARACTERS, '/default/path:with/colon') + ct.equal(parsed.NO_CURLY_BRACES_UNDEFINED_EXPAND_DEFAULT_SPECIAL_CHARACTERS, '/default/path:with/colon') ct.end() }) @@ -345,7 +345,7 @@ 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_WITH_DEFAULT_WITH_SPECIAL_CHARACTERS_NESTED, '/default/path:with/colon') + ct.equal(parsed.UNDEFINED_EXPAND_DEFAULT_SPECIAL_CHARACTERS_NESTED, '/default/path:with/colon') ct.end() }) From 8f19734d3dc6878ad88c50d15418e82540ed85a0 Mon Sep 17 00:00:00 2001 From: Scott Motte Date: Fri, 9 Feb 2024 16:34:02 -0800 Subject: [PATCH 4/5] adjust to handle 3 levels nested --- lib/main.js | 24 +++++++----------------- 1 file changed, 7 insertions(+), 17 deletions(-) diff --git a/lib/main.js b/lib/main.js index f19339a..6e42ca9 100644 --- a/lib/main.js +++ b/lib/main.js @@ -55,30 +55,22 @@ function _resolveEscapeSequences (value) { } function interpolate (value, processEnv, parsed) { - // * ( - // * (?!(?<=\\))\$ // only match dollar signs that are not escaped - // * {? // optional opening curly brace - // * ([\w.]+) // match the variable name - // * (?::-([^}\\]*))? // match an optional default value - // * }? // optional closing curly brace - // * ) - // // * / // * (\\)? # is it escaped with a backslash? // * (\$) # literal $ // * (?!\() # shouldnt be followed by parenthesis - // * \{? # allow brace wrapping - // * ([\w.]+)? # optional alpha nums - // * (?::-([^}\\]*))? # optional default - // * \}? # closing brace + // * (\{?) # first brace wrap opening + // * ([\w.]+) # key + // * (?::-((?:\$\{(?:\$\{(?:\$\{[^}]*\}|[^}])*}|[^}])*}|[^}])+))? # optional default nested 3 times + // * (\}?) # last brace warp closing // * /xi - // const SUB_REGEX = /(\\)?(\$)(?!\()(\{?)([\w.]+)?(?::-([^}\\]*))?(\}?)/gi - const SUB_REGEX = /(\\)?(\$)(?!\()(\{?)([\w.]+)(?::-((?:\$\{[^}]*\}|[^}])+))?(\}?)/gi + + const SUB_REGEX = /(\\)?(\$)(?!\()(\{?)([\w.]+)(?::-((?:\$\{(?:\$\{(?:\$\{[^}]*\}|[^}])*}|[^}])*}|[^}])+))?(\}?)/gi return value.replace(SUB_REGEX, (match, escaped, dollarSign, openBrace, key, defaultValue, closeBrace) => { if (escaped === '\\') { return match.slice(1) - } else if (key) { + } else { if (processEnv[key]) { return processEnv[key] } @@ -92,8 +84,6 @@ function interpolate (value, processEnv, parsed) { } return parsed[key] || '' - } else { - return match } }) } From 315436bfc4b07831eb796f9026e7becc14ab8dc8 Mon Sep 17 00:00:00 2001 From: Scott Motte Date: Fri, 9 Feb 2024 16:36:38 -0800 Subject: [PATCH 5/5] =?UTF-8?q?changelog=20=F0=9F=AA=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4463397..3319046 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ All notable changes to this project will be documented in this file. See [standa ## [Unreleased](https://github.com/motdotla/dotenv-expand/compare/v11.0.0...master) -## [11.0.0](https://github.com/motdotla/dotenv-expand/compare/v10.0.0...v11.0.0) (2024-02-08) +## [11.0.0](https://github.com/motdotla/dotenv-expand/compare/v10.0.0...v11.0.0) (2024-02-10) ### Added @@ -15,6 +15,7 @@ All notable changes to this project will be documented in this file. See [standa ### Changed - Do not expand prior `process.env` environment variables. NOTE: make sure to see updated README regarding `dotenv.config({ processEnv: {} })` ([#104](https://github.com/motdotla/dotenv-expand/pull/104)) +- 🐞 handle `$var1$var2` ([#103](https://github.com/motdotla/dotenv-expand/issues/103), [#104](https://github.com/motdotla/dotenv-expand/pull/104)) ### Removed