From c803fa5adab948dd770921d633a9b3bf9a82fd5d Mon Sep 17 00:00:00 2001
From: Scott Motte <mot@mot.la>
Date: Wed, 13 Nov 2024 16:18:37 -0800
Subject: [PATCH 1/4] wip - better expansion to match dotenvx's

---
 lib/main.js   | 202 +++++++++----
 tests/main.js | 806 +++++++++++++++++++++++++-------------------------
 2 files changed, 549 insertions(+), 459 deletions(-)

diff --git a/lib/main.js b/lib/main.js
index e57074e..47be414 100644
--- a/lib/main.js
+++ b/lib/main.js
@@ -1,53 +1,130 @@
-'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
+// '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
 
 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)
-        }
-      }
+function interpolate (value, env) {
+  const regex = /(?<!\\)\${([^{}]+)}|(?<!\\)\$([A-Za-z_][A-Za-z0-9_]*)/g
 
-      if (parsed[key]) {
-        // avoid recursion from EXPAND_SELF=$EXPAND_SELF
-        if (parsed[key] !== value) {
-          return interpolate(parsed[key], processEnv, parsed)
-        }
-      }
+  let result = value
+  let match
+  const seen = new Set() // self-referential checker
 
-      if (defaultValue) {
-        if (defaultValue.startsWith('$')) {
-          return interpolate(defaultValue, processEnv, parsed)
-        } else {
-          return defaultValue
-        }
-      }
+  while ((match = regex.exec(result)) !== null) {
+    seen.add(result)
 
-      return ''
+    const [template, bracedExpression, unbracedExpression] = match
+    const expression = bracedExpression || unbracedExpression
+    const r = expression.split(/:-|-/)
+    const key = r.shift()
+    const defaultValue = r.join('-')
+    const value = env[key]
+
+    if (value) {
+      // self-referential check
+      if (seen.has(value)) {
+        result = result.replace(template, defaultValue)
+      } else {
+        result = result.replace(template, value)
+      }
+    } else {
+      result = result.replace(template, defaultValue)
     }
-  })
+
+    regex.lastIndex = 0 // reset regex search position to re-evaluate after each replacement
+  }
+
+  return result
 }
 
 function expand (options) {
@@ -55,32 +132,37 @@ function expand (options) {
   if (options && options.processEnv != null) {
     processEnv = options.processEnv
   }
+  const parsed = options.parsed || {}
 
-  for (const key in options.parsed) {
-    let value = options.parsed[key]
+  const combined = { ...processEnv, ...parsed }
+  const combinedReversed = { ...parsed, ...processEnv }
 
-    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)
+  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
     }
 
-    options.parsed[key] = _resolveEscapeSequences(value)
-  }
+    if (processEnv[key]) {
+      continue // already has a value in processEnv, move on
+    }
 
-  for (const processKey in options.parsed) {
-    processEnv[processKey] = options.parsed[processKey]
+    const processEnvValue = interpolate(value, combinedReversed) // could be empty string ''
+    if (processEnvValue) {
+      processEnv[key] = _resolveEscapeSequences(processEnvValue) // set it
+    }
   }
 
-  return options
+  return {
+    parsed,
+    processEnv
+  }
 }
 
 module.exports.expand = expand
diff --git a/tests/main.js b/tests/main.js
index f8f7755..6baff8d 100644
--- a/tests/main.js
+++ b/tests/main.js
@@ -71,7 +71,9 @@ 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()
 })
@@ -97,9 +99,11 @@ t.test('prioritizes machine key expansion over .env', ct => {
       MACHINE_EXPAND: '$MACHINE'
     }
   }
-  const parsed = dotenvExpand.expand(dotenv).parsed
+  const { parsed, processEnv } = dotenvExpand.expand(dotenv)
 
-  ct.equal(parsed.MACHINE_EXPAND, 'machine')
+  ct.equal(parsed.MACHINE_EXPAND, 'machine_env')
+  ct.equal(processEnv.MACHINE_EXPAND, 'machine')
+  ct.equal(process.env.MACHINE_EXPAND, 'machine')
 
   ct.end()
 })
@@ -137,9 +141,11 @@ t.test('does not overwrite preset variables', ct => {
       SOME_ENV: 'development'
     }
   }
-  const parsed = dotenvExpand.expand(dotenv).parsed
+  const { parsed, processEnv } = dotenvExpand.expand(dotenv)
 
-  ct.equal(parsed.SOME_ENV, 'production')
+  ct.equal(parsed.SOME_ENV, 'development')
+  ct.equal(processEnv.SOME_ENV, 'production')
+  ct.equal(process.env.SOME_ENV, 'production')
 
   ct.end()
 })
@@ -182,400 +188,402 @@ 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' })
-  dotenvExpand.expand(dotenv)
+  const { parsed, processEnv } = dotenvExpand.expand(dotenv)
 
+  ct.equal(parsed.BASIC_EXPAND, '$BASIC')
   ct.equal(process.env.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<S:b_')
-  ct.equal(parsed.DONT_CHOKE3, 'MUcKSGSY@HCON<1S_siWTP`DgS*Ug],mu]SkqI|7V2eOk9:>&fw;>HEwms`D8E2H')
-  ct.equal(parsed.DONT_CHOKE4, 'm]zjzfRItw2gs[2:{p{ugENyFw9m)tH6_VCQzer`*noVaI<vqa3?FZ9+6U;K#Bfd')
-  ct.equal(parsed.DONT_CHOKE5, '#la__nK?IxNlQ%`5q&DpcZ>Munx=[1-AMgAcwmPkToxTaB?kgdF5y`A8m=Oa-B!)')
-  ct.equal(parsed.DONT_CHOKE6, 'xlC&*<j4J<d._<JKH0RBJV!4(ZQEN-+&!0p137<g*hdY2H4xk?/;KO1$(W{:Wc}Q')
-  ct.equal(parsed.DONT_CHOKE7, '?$6)m*xhTVewc#NVVgxX%eBhJjoHYzpXFg=gzn[rWXPLj5UWj@z$/UDm8o79n/p%')
-  ct.equal(parsed.DONT_CHOKE8, '@}:[4#g%[R-CFR});bY(Z[KcDQDsVn2_y4cSdU<Mjy!c^F`G<!Ks7]kbS]N1:bP:')
-
-  ct.end()
-})
-
-t.test('expands self without a recursive call stack error (process.env)', ct => {
-  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.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<S:b_')
+//   ct.equal(parsed.DONT_CHOKE3, 'MUcKSGSY@HCON<1S_siWTP`DgS*Ug],mu]SkqI|7V2eOk9:>&fw;>HEwms`D8E2H')
+//   ct.equal(parsed.DONT_CHOKE4, 'm]zjzfRItw2gs[2:{p{ugENyFw9m)tH6_VCQzer`*noVaI<vqa3?FZ9+6U;K#Bfd')
+//   ct.equal(parsed.DONT_CHOKE5, '#la__nK?IxNlQ%`5q&DpcZ>Munx=[1-AMgAcwmPkToxTaB?kgdF5y`A8m=Oa-B!)')
+//   ct.equal(parsed.DONT_CHOKE6, 'xlC&*<j4J<d._<JKH0RBJV!4(ZQEN-+&!0p137<g*hdY2H4xk?/;KO1$(W{:Wc}Q')
+//   ct.equal(parsed.DONT_CHOKE7, '?$6)m*xhTVewc#NVVgxX%eBhJjoHYzpXFg=gzn[rWXPLj5UWj@z$/UDm8o79n/p%')
+//   ct.equal(parsed.DONT_CHOKE8, '@}:[4#g%[R-CFR});bY(Z[KcDQDsVn2_y4cSdU<Mjy!c^F`G<!Ks7]kbS]N1:bP:')
+//
+//   ct.end()
+// })
+//
+// t.test('expands self without a recursive call stack error (process.env)', ct => {
+//   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()
+// })

From 48881d7364482d87d21298e51dffc609ad865db4 Mon Sep 17 00:00:00 2001
From: Scott Motte <mot@mot.la>
Date: Sat, 16 Nov 2024 09:15:01 -0800
Subject: [PATCH 2/4] Revert "wip - better expansion to match dotenvx's"

This reverts commit c803fa5adab948dd770921d633a9b3bf9a82fd5d.
---
 lib/main.js   | 202 ++++---------
 tests/main.js | 806 +++++++++++++++++++++++++-------------------------
 2 files changed, 459 insertions(+), 549 deletions(-)

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 = /(?<!\\)\${([^{}]+)}|(?<!\\)\$([A-Za-z_][A-Za-z0-9_]*)/g
-
-  let result = value
-  let match
-  const seen = new Set() // self-referential checker
-
-  while ((match = regex.exec(result)) !== null) {
-    seen.add(result)
-
-    const [template, bracedExpression, unbracedExpression] = match
-    const expression = bracedExpression || unbracedExpression
-    const r = expression.split(/:-|-/)
-    const key = r.shift()
-    const defaultValue = r.join('-')
-    const value = env[key]
+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 (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<S:b_')
-//   ct.equal(parsed.DONT_CHOKE3, 'MUcKSGSY@HCON<1S_siWTP`DgS*Ug],mu]SkqI|7V2eOk9:>&fw;>HEwms`D8E2H')
-//   ct.equal(parsed.DONT_CHOKE4, 'm]zjzfRItw2gs[2:{p{ugENyFw9m)tH6_VCQzer`*noVaI<vqa3?FZ9+6U;K#Bfd')
-//   ct.equal(parsed.DONT_CHOKE5, '#la__nK?IxNlQ%`5q&DpcZ>Munx=[1-AMgAcwmPkToxTaB?kgdF5y`A8m=Oa-B!)')
-//   ct.equal(parsed.DONT_CHOKE6, 'xlC&*<j4J<d._<JKH0RBJV!4(ZQEN-+&!0p137<g*hdY2H4xk?/;KO1$(W{:Wc}Q')
-//   ct.equal(parsed.DONT_CHOKE7, '?$6)m*xhTVewc#NVVgxX%eBhJjoHYzpXFg=gzn[rWXPLj5UWj@z$/UDm8o79n/p%')
-//   ct.equal(parsed.DONT_CHOKE8, '@}:[4#g%[R-CFR});bY(Z[KcDQDsVn2_y4cSdU<Mjy!c^F`G<!Ks7]kbS]N1:bP:')
-//
-//   ct.end()
-// })
-//
-// t.test('expands self without a recursive call stack error (process.env)', ct => {
-//   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<S:b_')
+  ct.equal(parsed.DONT_CHOKE3, 'MUcKSGSY@HCON<1S_siWTP`DgS*Ug],mu]SkqI|7V2eOk9:>&fw;>HEwms`D8E2H')
+  ct.equal(parsed.DONT_CHOKE4, 'm]zjzfRItw2gs[2:{p{ugENyFw9m)tH6_VCQzer`*noVaI<vqa3?FZ9+6U;K#Bfd')
+  ct.equal(parsed.DONT_CHOKE5, '#la__nK?IxNlQ%`5q&DpcZ>Munx=[1-AMgAcwmPkToxTaB?kgdF5y`A8m=Oa-B!)')
+  ct.equal(parsed.DONT_CHOKE6, 'xlC&*<j4J<d._<JKH0RBJV!4(ZQEN-+&!0p137<g*hdY2H4xk?/;KO1$(W{:Wc}Q')
+  ct.equal(parsed.DONT_CHOKE7, '?$6)m*xhTVewc#NVVgxX%eBhJjoHYzpXFg=gzn[rWXPLj5UWj@z$/UDm8o79n/p%')
+  ct.equal(parsed.DONT_CHOKE8, '@}:[4#g%[R-CFR});bY(Z[KcDQDsVn2_y4cSdU<Mjy!c^F`G<!Ks7]kbS]N1:bP:')
+
+  ct.end()
+})
+
+t.test('expands self without a recursive call stack error (process.env)', ct => {
+  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()
+})

From 318118d7d1d912ea7377a37fd69ce75bf798445f Mon Sep 17 00:00:00 2001
From: Scott Motte <mot@mot.la>
Date: Sat, 16 Nov 2024 11:46:44 -0800
Subject: [PATCH 3/4] =?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    |  13 +++++-
 lib/main.js     | 112 +++++++++++++++++++++++++++---------------------
 tests/.env.test |   3 ++
 tests/main.js   |  35 +++++++++++----
 4 files changed, 104 insertions(+), 59 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 80f9dc7..7e48d6e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,7 +2,18 @@
 
 All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
 
-## [Unreleased](https://github.com/motdotla/dotenv-expand/compare/v11.0.7...master)
+## [Unreleased](https://github.com/motdotla/dotenv-expand/compare/v12.0.0...master)
+
+## [12.0.0](https://github.com/motdotla/dotenv-expand/compare/v11.0.7...v12.0.0) (2024-11-16)
+
+### Added
+
+* 🎉 support alternate value expansion ([#131](https://github.com/motdotla/dotenv-expand/pull/131))
+
+### Changed
+
+* 🎉 Expansion logic rewritten to match [dotenvx's](https://github.com/dotenvx/dotenvx). (*note: I recommend dotenvx over dotenv-expand when you are ready. I'm putting all my effort there for a unified standard .env implementation that works everywhere and matches bash, docker-compose, and more. In some cases it slightly improves on them. This leads to more reliability for your secrets and config.) ([#131](https://github.com/motdotla/dotenv-expand/pull/131))
+* ⚠️ BREAKING: do NOT expand in reverse order. Instead, order your .env file keys from first to last as they depend on each other for expansion - principle of least surprise. ([#131](https://github.com/motdotla/dotenv-expand/pull/131))
 
 ## [11.0.7](https://github.com/motdotla/dotenv-expand/compare/v11.0.6...v11.0.7) (2024-11-13)
 
diff --git a/lib/main.js b/lib/main.js
index e57074e..a893f79 100644
--- a/lib/main.js
+++ b/lib/main.js
@@ -1,79 +1,91 @@
 '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)
-        }
-      }
+function expandValue (value, processEnv, runningParsed) {
+  const env = { ...runningParsed, ...processEnv } // process.env wins
 
-      if (parsed[key]) {
-        // avoid recursion from EXPAND_SELF=$EXPAND_SELF
-        if (parsed[key] !== value) {
-          return interpolate(parsed[key], processEnv, parsed)
-        }
-      }
+  const regex = /(?<!\\)\${([^{}]+)}|(?<!\\)\$([A-Za-z_][A-Za-z0-9_]*)/g
+
+  let result = value
+  let match
+  const seen = new Set() // self-referential checker
+
+  while ((match = regex.exec(result)) !== null) {
+    seen.add(result)
+
+    const [template, bracedExpression, unbracedExpression] = match
+    const expression = bracedExpression || unbracedExpression
+
+    // match the operators `:+`, `+`, `:-`, and `-`
+    const opRegex = /(:\+|\+|:-|-)/
+    // find first match
+    const opMatch = expression.match(opRegex)
+    const splitter = opMatch ? opMatch[0] : null
+
+    const r = expression.split(splitter)
+
+    let defaultValue
+    let value
+
+    const key = r.shift()
+
+    if ([':+', '+'].includes(splitter)) {
+      defaultValue = env[key] ? r.join(splitter) : ''
+      value = null
+    } else {
+      defaultValue = r.join(splitter)
+      value = env[key]
+    }
 
-      if (defaultValue) {
-        if (defaultValue.startsWith('$')) {
-          return interpolate(defaultValue, processEnv, parsed)
-        } else {
-          return defaultValue
-        }
+    if (value) {
+      // self-referential check
+      if (seen.has(value)) {
+        result = result.replace(template, defaultValue)
+      } else {
+        result = result.replace(template, value)
       }
+    } else {
+      result = result.replace(template, defaultValue)
+    }
 
-      return ''
+    // if the result equaled what was in process.env and runningParsed then stop expanding
+    if (result === processEnv[key] && result === runningParsed[key]) {
+      break
     }
-  })
+
+    regex.lastIndex = 0 // reset regex search position to re-evaluate after each replacement
+  }
+
+  return result
 }
 
 function expand (options) {
+  // for use with progressive expansion
+  const runningParsed = {}
+
   let processEnv = process.env
   if (options && options.processEnv != null) {
     processEnv = options.processEnv
   }
 
+  // dotenv.config() ran before this so the assumption is process.env has already been set
   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]
-      }
+    // short-circuit scenario: process.env was already set prior to the file value
+    if (processEnv[key] && processEnv[key] !== value) {
+      value = processEnv[key]
     } else {
-      // not inProcessEnv so assume interpolation for this .env key
-      value = interpolate(value, processEnv, options.parsed)
+      value = expandValue(value, processEnv, runningParsed)
     }
 
     options.parsed[key] = _resolveEscapeSequences(value)
+
+    // for use with progressive expansion
+    runningParsed[key] = _resolveEscapeSequences(value)
   }
 
   for (const processKey in options.parsed) {
diff --git a/tests/.env.test b/tests/.env.test
index b5a602f..687588d 100644
--- a/tests/.env.test
+++ b/tests/.env.test
@@ -79,3 +79,6 @@ PASSWORD_EXPAND=${PASSWORD}
 PASSWORD_EXPAND_SIMPLE=$PASSWORD
 PASSWORD_EXPAND_NESTED=${PASSWORD_EXPAND}
 PASSWORD_EXPAND_NESTED_NESTED=${PASSWORD_EXPAND_NESTED}
+
+USE_IF_SET=true
+ALTERNATE=${USE_IF_SET:+alternate}
diff --git a/tests/main.js b/tests/main.js
index f8f7755..13a5e19 100644
--- a/tests/main.js
+++ b/tests/main.js
@@ -404,8 +404,8 @@ t.test('should expand with default value correctly', ct => {
 
   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.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()
 })
@@ -454,7 +454,7 @@ 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.equal(parsed.TWO_DOLLAR_SIGNS, 'abcd$$1234')
 
   ct.end()
 })
@@ -535,7 +535,7 @@ t.test('expands recursively', ct => {
   ct.end()
 })
 
-t.test('expands recursively reverse order', ct => {
+t.test('CANNOT expand recursively reverse order (ORDER YOUR .env file for least surprise)', ct => {
   const dotenv = {
     parsed: {
       BACKEND_API_HEALTH_CHECK_URL: '${MOCK_SERVER_HOST}/ci-health-check',
@@ -546,8 +546,8 @@ t.test('expands recursively reverse order', ct => {
   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.equal(parsed.MOCK_SERVER_HOST, 'http://localhost:')
+  ct.equal(parsed.BACKEND_API_HEALTH_CHECK_URL, '/ci-health-check')
 
   ct.end()
 })
@@ -571,11 +571,30 @@ t.test('expands recursively but is smart enough to not attempt expansion of a pr
   const dotenv = require('dotenv').config({ path: 'tests/.env.test' })
   dotenvExpand.expand(dotenv)
 
+  ct.equal(process.env.PASSWORD, 'pas$word')
   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.equal(process.env.PASSWORD_EXPAND_NESTED_NESTED, 'pas$word')
+
+  ct.end()
+})
+
+t.test('expands alternate logic', ct => {
+  const dotenv = require('dotenv').config({ path: 'tests/.env.test' })
+  dotenvExpand.expand(dotenv)
+
+  ct.equal(process.env.ALTERNATE, 'alternate')
+
+  ct.end()
+})
+
+t.test('expands alternate logic when not set', ct => {
+  process.env.USE_IF_SET = ''
+  const dotenv = require('dotenv').config({ path: 'tests/.env.test' })
+  dotenvExpand.expand(dotenv)
+
+  ct.equal(process.env.ALTERNATE, '')
 
   ct.end()
 })

From a3b31a878c689f02dc7ea73c4dc0cd6b4a0493ae Mon Sep 17 00:00:00 2001
From: Scott Motte <mot@mot.la>
Date: Sat, 16 Nov 2024 11:54:52 -0800
Subject: [PATCH 4/4] README

---
 CHANGELOG.md |  3 ++-
 README.md    | 23 ++++-------------------
 2 files changed, 6 insertions(+), 20 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7e48d6e..a629578 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,12 +8,13 @@ All notable changes to this project will be documented in this file. See [standa
 
 ### Added
 
-* 🎉 support alternate value expansion ([#131](https://github.com/motdotla/dotenv-expand/pull/131))
+* 🎉 support alternate value expansion (see [usage](https://dotenvx.com/docs/env-file#interpolation)) ([#131](https://github.com/motdotla/dotenv-expand/pull/131))
 
 ### Changed
 
 * 🎉 Expansion logic rewritten to match [dotenvx's](https://github.com/dotenvx/dotenvx). (*note: I recommend dotenvx over dotenv-expand when you are ready. I'm putting all my effort there for a unified standard .env implementation that works everywhere and matches bash, docker-compose, and more. In some cases it slightly improves on them. This leads to more reliability for your secrets and config.) ([#131](https://github.com/motdotla/dotenv-expand/pull/131))
 * ⚠️ BREAKING: do NOT expand in reverse order. Instead, order your .env file keys from first to last as they depend on each other for expansion - principle of least surprise. ([#131](https://github.com/motdotla/dotenv-expand/pull/131))
+* ⚠️ BREAKING: do NOT attempt expansion of process.env. This has always been dangerous (unexpected side effects) and is now removed. process.env should not hold values you want to expand. Put expansion logic in your .env file. If you need this ability, use [dotenvx](https://github.com/dotenvx/dotenvx) by shipping an encrypted .env file with your code - allowing safe expansion at runtime. ([#131](https://github.com/motdotla/dotenv-expand/pull/131))
 
 ## [11.0.7](https://github.com/motdotla/dotenv-expand/compare/v11.0.6...v11.0.7) (2024-11-13)
 
diff --git a/README.md b/README.md
index 5d47e02..af26b73 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,5 @@
 <div align="center">
-🎉 announcing <a href="https://github.com/dotenvx/dotenvx">dotenvx</a>. <em><b>better expansion</b>, run anywhere, multi-environment, encrypted envs</em>.
+🎉 announcing <a href="https://github.com/dotenvx/dotenvx">dotenvx</a>. <em><b>expansion AND command substitution</b>, multi-environment, encrypted envs, and more</em>.
 </div>
 
 &nbsp;
@@ -166,28 +166,13 @@ console.log(process.env.HELLO) // undefined
 
 ### What rules does the expansion engine follow?
 
-The expansion engine roughly has the following rules:
-
-* `$KEY` will expand any env with the name `KEY`
-* `${KEY}` will expand any env with the name `KEY` 
-* `\$KEY` will escape the `$KEY` rather than expand
-* `${KEY:-default}` will first attempt to expand any env with the name `KEY`. If not one, then it will return `default`
-* `${KEY-default}` will first attempt to expand any env with the name `KEY`. If not one, then it will return `default`
-
-You can see a full list of rules [here](https://dotenvx.com/docs/env-file#interpolation).
+See a full list of rules [here](https://dotenvx.com/docs/env-file#interpolation).
 
 ### How can I avoid expanding pre-existing envs (already in my `process.env`, for example `pas$word`)?
 
-Modify your `dotenv.config` to write to an empty object and pass that to `dotenvExpand.processEnv`.
-
-```js
-const dotenv = require('dotenv')
-const dotenvExpand = require('dotenv-expand')
-
-const myEnv = dotenv.config({ processEnv: {} }) // prevent writing to `process.env`
+As of `v12.0.0` dotenv-expand no longer expands `process.env`.
 
-dotenvExpand.expand(myEnv)
-```
+If you need this ability, use [dotenvx](https://github.com/dotenvx/dotenvx) by shipping an encrypted .env file with your code - allowing safe expansion at runtime.
 
 ## Contributing Guide