Open
Description
Is there an existing issue for this?
- I have searched the existing issues
This issue exists in the latest npm version
- I am using the latest npm
Current Behavior
If I add following line to .npmrc
:
node-options = "${NODE_OPTIONS} --use-system-ca"
it correctly works if the NODE_OPTIONS was set before to some node flags in my env. For example, if I do
export NODE_OPTIONS=--inspect
npm run foo
then the Node being run by the "foo" script, receives --inspect --use-system-ca
as node options and it's fine.
But, if the NODE_OPTIONS
env var is not set to anything when i do npm run foo
, then the Node receives ${NODE_OPTIONS} --use-system-ca
as options and Node rejects all the options (--use-system-ca
is ignored as well).
As a result of npm leaving var substitutions untouched if the var is not set, there is no way to reliably extend the node options via .npmrc instead of replacing them.
Expected Behavior
Either:
${FOO}
var substitution pattern should eval to empty string if FOO is not defined- or possibility to provide defaults in a bash-like way should be possible. So
${FOO:-''}
can be used to just have an empty string if the var is not defined at all
Steps To Reproduce
- Add this script to package.json:
"foo": "node -e 'console.log(process.env.NODE_OPTIONS);'"
- Add this line in .npmrc:
node-options = "${NODE_OPTIONS} --max-old-space-size=1"
- run
NODE_OPTIONS='' npm run foo
- as expected the Node will fail to run because it runs out of the assigned 1mb of memory, meaning the flags were passed just fine unset NODE_OPTIONS
to make sure it is not set to any value, not even an empty stringnpm run foo
- You can see
${NODE_OPTIONS} --max-old-space-size=1
printed and Node finishes just fine which means that the NODE_OPTIONS were ignored because of the incorrect string resulting from unsubstituted variable
Environment
- npm: 11.4.1
- Node.js: 22.15.1
- OS Name: Windows
- System Model Name:
- npm config:
; "user" config from C:\Users\(...)\.npmrc
email = (protected)
script-shell = "C:\\Program Files\\Git\\usr\\bin\\bash.exe"
; "project" config from C:\(...)\.npmrc
fetch-retries = 4
legacy-peer-deps = true
node-options = "${NODE_OPTIONS} --max-old-space-size=1"
save-exact = true
save-prefix = ""
; node bin location = C:\Users\(...)\AppData\Local\fnm_multishells\32040_1748498818910\node.exe
; node version = v22.15.1
; npm local prefix = C:\(...)
; npm version = 10.9.2
; cwd = C:\(...)
; HOME = C:\Users\(...)
Activity
alexsch01 commentedon May 29, 2025
Note to maintainers: this is the same behavior in npm 10.8.2
EDIT: I do agree with the author of the issue
Blue-smoke007 commentedon May 29, 2025
I am writing to express my interest in addressing the issue involving environment variable substitution in .npmrc—specifically, how ${NODE_OPTIONS} behaves when the variable is undefined. This behavior currently causes Node.js to fail or ignore critical flags due to incorrect string substitution, which leads to unreliable configurations for developers. The Problem When node-options = "${NODE_OPTIONS} --max-old-space-size=1" is used in .npmrc, it works as expected only if NODE_OPTIONS is already set. However, if it's not defined at all, the placeholder is passed literally as ${NODE_OPTIONS}, causing Node.js to reject or ignore all flags. This behavior breaks consistency and creates potential runtime issues.
aczekajski commentedon May 30, 2025
The entire replacement boils down to https://github.com/npm/cli/blob/latest/workspaces/config/lib/env-replace.js file which has a simple code for replacing env vars after
ini
package parses the config.I reckoned that existing projects might rely on the variables not being replaced if the value is not defined so silently changing the code to evaluate to empty string if the env var is undefined might not be a good idea.
Then I considered what would it take to implement
${FOO:default}
syntax and quickly relized that it falls into a parsing and escaping hell (what if someone wants to default to string that contains}
or\\
or\\}
etc). Having defaults would be nice but solving the original problem doesn't need it.So after all, since the syntax is custom anyway, I though that simple and reliable solution would be to introduce a
?
modifier. It could be put after the var name, like this:${FOO?}
and signals that the var is optional and can be evaluated to empty string.Summary of the idea:
FOO
is undefined${FOO}
evals to${FOO}
(unchanged behavior)${FOO?}
evals to empty string (new behavior)FOO
is set tobar
${FOO}
evals tobar
(unchanged behavior)${FOO?}
evals tobar
(unchanged behavior)Blue-smoke007 commentedon May 30, 2025
kitkat23-n commentedon Jun 1, 2025