|
1 | 1 | 'use strict'
|
2 | 2 |
|
| 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 | + |
3 | 9 | 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, '$') |
41 | 56 | }
|
42 | 57 |
|
43 | 58 | function expand (config) {
|
44 | 59 | // if ignoring process.env, use a blank object
|
45 | 60 | const environment = config.ignoreProcessEnv ? {} : process.env
|
46 | 61 |
|
47 | 62 | 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] |
49 | 66 |
|
50 |
| - config.parsed[configKey] = _interpolate(value, environment, config) |
| 67 | + config.parsed[configKey] = _resolveEscapeSequences( |
| 68 | + _interpolate(value, environment, config) |
| 69 | + ) |
51 | 70 | }
|
52 | 71 |
|
53 | 72 | for (const processKey in config.parsed) {
|
|
0 commit comments