Skip to content

Commit

Permalink
Merge pull request #74 from FezVrasta/master
Browse files Browse the repository at this point in the history
fix: support special characters in default expansion
  • Loading branch information
motdotla committed Dec 16, 2022
2 parents 23b9cd7 + 4294f64 commit b38930f
Show file tree
Hide file tree
Showing 5 changed files with 1,780 additions and 2,187 deletions.
97 changes: 58 additions & 39 deletions lib/main.js
Original file line number Diff line number Diff line change
@@ -1,53 +1,72 @@
'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 (envValue, environment, config) {
const matches = envValue.match(/(.?\${*[\w]*(?::-[\w/]*)?}*)/g) || []

return matches.reduce(function (newEnv, match, index) {
const parts = /(.?)\${*([\w]*(?::-[\w/]*)?)?}*/g.exec(match)
if (!parts || parts.length === 0) {
return newEnv
}

const prefix = parts[1]

let value, replacePart

if (prefix === '\\') {
replacePart = parts[0]
value = replacePart.replace('\\$', '$')
} else {
const keyParts = parts[2].split(':-')
const key = keyParts[0]
replacePart = parts[0].substring(prefix.length)
// process.env value 'wins' over .env file's value
value = Object.prototype.hasOwnProperty.call(environment, key)
? environment[key]
: (config.parsed[key] || keyParts[1] || '')

// If the value is found, remove nested expansions.
if (keyParts.length > 1 && value) {
const replaceNested = matches[index + 1]
matches[index + 1] = ''

newEnv = newEnv.replace(replaceNested, '')
}
// Resolve recursive interpolations
value = _interpolate(value, environment, config)
}

return newEnv.replace(replacePart, value)
}, envValue)
// find the last unescaped dollar sign in the
// value so that we can evaluate it
const lastUnescapedDollarSignIndex = _searchLast(envValue, /(?!(?<=\\))\$/g)

// If we couldn't match any unescaped dollar sign
// let's return the string as is
if (lastUnescapedDollarSignIndex === -1) return envValue

// This is the right-most group of variables in the string
const rightMostGroup = envValue.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, variableName, defaultValue] = match

return _interpolate(
envValue.replace(
group,
environment[variableName] ||
defaultValue ||
config.parsed[variableName] ||
''
),
environment,
config
)
}

return envValue
}

function _resolveEscapeSequences (value) {
return value.replace(/\\\$/g, '$')
}

function expand (config) {
// if ignoring process.env, use a blank object
const environment = config.ignoreProcessEnv ? {} : process.env

for (const configKey in config.parsed) {
const value = Object.prototype.hasOwnProperty.call(environment, configKey) ? environment[configKey] : config.parsed[configKey]
const value = Object.prototype.hasOwnProperty.call(environment, configKey)
? environment[configKey]
: config.parsed[configKey]

config.parsed[configKey] = _interpolate(value, environment, config)
config.parsed[configKey] = _resolveEscapeSequences(
_interpolate(value, environment, config)
)
}

for (const processKey in config.parsed) {
Expand Down
Loading

0 comments on commit b38930f

Please sign in to comment.