Skip to content

Commit b38930f

Browse files
authored
Merge pull request #74 from FezVrasta/master
fix: support special characters in default expansion
2 parents 23b9cd7 + 4294f64 commit b38930f

File tree

5 files changed

+1780
-2187
lines changed

5 files changed

+1780
-2187
lines changed

lib/main.js

Lines changed: 58 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,72 @@
11
'use strict'
22

3+
// like String.prototype.search but returns the last index
4+
function _searchLast (str, rgx) {
5+
const matches = Array.from(str.matchAll(rgx))
6+
return matches.length > 0 ? matches.slice(-1)[0].index : -1
7+
}
8+
39
function _interpolate (envValue, environment, config) {
4-
const matches = envValue.match(/(.?\${*[\w]*(?::-[\w/]*)?}*)/g) || []
5-
6-
return matches.reduce(function (newEnv, match, index) {
7-
const parts = /(.?)\${*([\w]*(?::-[\w/]*)?)?}*/g.exec(match)
8-
if (!parts || parts.length === 0) {
9-
return newEnv
10-
}
11-
12-
const prefix = parts[1]
13-
14-
let value, replacePart
15-
16-
if (prefix === '\\') {
17-
replacePart = parts[0]
18-
value = replacePart.replace('\\$', '$')
19-
} else {
20-
const keyParts = parts[2].split(':-')
21-
const key = keyParts[0]
22-
replacePart = parts[0].substring(prefix.length)
23-
// process.env value 'wins' over .env file's value
24-
value = Object.prototype.hasOwnProperty.call(environment, key)
25-
? environment[key]
26-
: (config.parsed[key] || keyParts[1] || '')
27-
28-
// If the value is found, remove nested expansions.
29-
if (keyParts.length > 1 && value) {
30-
const replaceNested = matches[index + 1]
31-
matches[index + 1] = ''
32-
33-
newEnv = newEnv.replace(replaceNested, '')
34-
}
35-
// Resolve recursive interpolations
36-
value = _interpolate(value, environment, config)
37-
}
38-
39-
return newEnv.replace(replacePart, value)
40-
}, envValue)
10+
// find the last unescaped dollar sign in the
11+
// value so that we can evaluate it
12+
const lastUnescapedDollarSignIndex = _searchLast(envValue, /(?!(?<=\\))\$/g)
13+
14+
// If we couldn't match any unescaped dollar sign
15+
// let's return the string as is
16+
if (lastUnescapedDollarSignIndex === -1) return envValue
17+
18+
// This is the right-most group of variables in the string
19+
const rightMostGroup = envValue.slice(lastUnescapedDollarSignIndex)
20+
21+
/**
22+
* This finds the inner most variable/group divided
23+
* by variable name and default value (if present)
24+
* (
25+
* (?!(?<=\\))\$ // only match dollar signs that are not escaped
26+
* {? // optional opening curly brace
27+
* ([\w]+) // match the variable name
28+
* (?::-([^}\\]*))? // match an optional default value
29+
* }? // optional closing curly brace
30+
* )
31+
*/
32+
const matchGroup = /((?!(?<=\\))\${?([\w]+)(?::-([^}\\]*))?}?)/
33+
const match = rightMostGroup.match(matchGroup)
34+
35+
if (match != null) {
36+
const [, group, variableName, defaultValue] = match
37+
38+
return _interpolate(
39+
envValue.replace(
40+
group,
41+
environment[variableName] ||
42+
defaultValue ||
43+
config.parsed[variableName] ||
44+
''
45+
),
46+
environment,
47+
config
48+
)
49+
}
50+
51+
return envValue
52+
}
53+
54+
function _resolveEscapeSequences (value) {
55+
return value.replace(/\\\$/g, '$')
4156
}
4257

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

4762
for (const configKey in config.parsed) {
48-
const value = Object.prototype.hasOwnProperty.call(environment, configKey) ? environment[configKey] : config.parsed[configKey]
63+
const value = Object.prototype.hasOwnProperty.call(environment, configKey)
64+
? environment[configKey]
65+
: config.parsed[configKey]
4966

50-
config.parsed[configKey] = _interpolate(value, environment, config)
67+
config.parsed[configKey] = _resolveEscapeSequences(
68+
_interpolate(value, environment, config)
69+
)
5170
}
5271

5372
for (const processKey in config.parsed) {

0 commit comments

Comments
 (0)