Skip to content

Commit bdcc10d

Browse files
feat: add support for optional env var replacements in .npmrc (#8359)
<!-- What / Why --> This solves problem described in #8335 in a backwards-compatible way. <!-- Describe the request in detail. What it does and why it's being changed. --> This PR adds possibility to have env var replacements in .npmrc configs written as `${VAR?}` which will cause them to get replaced with an empty string if the variable is not defined. Old behavior where undefined variables are left unreplaced is not changed. ## References <!-- Examples: Related to #0 Depends on #0 Blocked by #0 Fixes #0 Closes #0 --> Fixes #8335 --------- Co-authored-by: Michael Smith <[email protected]>
1 parent dd4cee9 commit bdcc10d

File tree

3 files changed

+24
-9
lines changed

3 files changed

+24
-9
lines changed

docs/lib/content/configuring-npm/npmrc.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,14 @@ The four relevant files are:
2525
* npm builtin config file (`/path/to/npm/npmrc`)
2626

2727
All npm config files are an ini-formatted list of `key = value` parameters.
28-
Environment variables can be replaced using `${VARIABLE_NAME}`. For
28+
Environment variables can be replaced using `${VARIABLE_NAME}`. By default
29+
if the variable is not defined, it is left unreplaced. By adding `?` after
30+
variable name they can be forced to evaluate to an empty string instead. For
2931
example:
3032

3133
```bash
3234
cache = ${HOME}/.npm-packages
35+
node-options = "${NODE_OPTIONS?} --use-system-ca"
3336
```
3437

3538
Each of these files is loaded, and config options are resolved in priority

workspaces/config/lib/env-replace.js

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
// replace any ${ENV} values with the appropriate environ.
2+
// optional "?" modifier can be used like this: ${ENV?} so in case of the variable being not defined, it evaluates into empty string.
23

3-
const envExpr = /(?<!\\)(\\*)\$\{([^${}]+)\}/g
4+
const envExpr = /(?<!\\)(\\*)\$\{([^${}?]+)(\?)?\}/g
45

5-
module.exports = (f, env) => f.replace(envExpr, (orig, esc, name) => {
6-
const val = env[name] !== undefined ? env[name] : `$\{${name}}`
6+
module.exports = (f, env) => f.replace(envExpr, (orig, esc, name, modifier) => {
7+
const fallback = modifier === '?' ? '' : `$\{${name}}`
8+
const val = env[name] !== undefined ? env[name] : fallback
79

810
// consume the escape chars that are relevant.
911
if (esc.length % 2) {

workspaces/config/test/env-replace.js

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,18 @@ const env = {
66
bar: 'baz',
77
}
88

9-
t.equal(envReplace('\\${foo}', env), '${foo}')
10-
t.equal(envReplace('\\\\${foo}', env), '\\bar')
11-
t.equal(envReplace('${baz}', env), '${baz}')
12-
t.equal(envReplace('\\${baz}', env), '${baz}')
13-
t.equal(envReplace('\\\\${baz}', env), '\\${baz}')
9+
t.equal(envReplace('${foo}', env), 'bar', 'replaces defined variable')
10+
t.equal(envReplace('${foo?}', env), 'bar', 'replaces defined variable with ? modifier')
11+
t.equal(envReplace('${foo}${bar}', env), 'barbaz', 'replaces multiple defined variables')
12+
t.equal(envReplace('${foo?}${baz?}', env), 'bar', 'replaces mixed defined/undefined variables with ? modifier')
13+
t.equal(envReplace('\\${foo}', env), '${foo}', 'escapes normal variable')
14+
t.equal(envReplace('\\\\${foo}', env), '\\bar', 'double escape allows replacement')
15+
t.equal(envReplace('\\\\\\${foo}', env), '\\${foo}', 'triple escape prevents replacement')
16+
t.equal(envReplace('${baz}', env), '${baz}', 'leaves undefined variable unreplaced')
17+
t.equal(envReplace('\\${baz}', env), '${baz}', 'escapes undefined variable')
18+
t.equal(envReplace('\\\\${baz}', env), '\\${baz}', 'double escape with undefined variable')
19+
t.equal(envReplace('\\${foo?}', env), '${foo?}', 'escapes optional variable')
20+
t.equal(envReplace('\\\\${foo?}', env), '\\bar', 'double escape allows optional replacement')
21+
t.equal(envReplace('${baz?}', env), '', 'replaces undefined variable with empty string when using ? modifier')
22+
t.equal(envReplace('\\${baz?}', env), '${baz?}', 'escapes undefined optional variable')
23+
t.equal(envReplace('\\\\${baz?}', env), '\\', 'double escape with undefined optional variable results in empty replacement')

0 commit comments

Comments
 (0)