Skip to content

[BUG] Impossible to reliably passthrough NODE_OPTIONS in .npmrc #8335

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
2 tasks done
aczekajski opened this issue May 29, 2025 · 5 comments
Open
2 tasks done

[BUG] Impossible to reliably passthrough NODE_OPTIONS in .npmrc #8335

aczekajski opened this issue May 29, 2025 · 5 comments
Labels
Enhancement new feature or improvement semver:major backwards-incompatible breaking changes

Comments

@aczekajski
Copy link

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

  1. Add this script to package.json: "foo": "node -e 'console.log(process.env.NODE_OPTIONS);'"
  2. Add this line in .npmrc: node-options = "${NODE_OPTIONS} --max-old-space-size=1"
  3. 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
  4. unset NODE_OPTIONS to make sure it is not set to any value, not even an empty string
  5. npm run foo
  6. 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\(...)
@aczekajski aczekajski added Bug thing that needs fixing Needs Triage needs review for next steps labels May 29, 2025
@alexsch01
Copy link
Contributor

alexsch01 commented 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

@milaninfy milaninfy added Enhancement new feature or improvement semver:major backwards-incompatible breaking changes and removed Bug thing that needs fixing Needs Triage needs review for next steps labels May 29, 2025
@Blue-smoke007
Copy link

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
Copy link
Author

aczekajski commented 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:

  • when FOO is undefined
    • ${FOO} evals to ${FOO} (unchanged behavior)
    • ${FOO?} evals to empty string (new behavior)
  • when FOO is set to bar
    • ${FOO} evals to bar (unchanged behavior)
    • ${FOO?} evals to bar (unchanged behavior)

@Blue-smoke007
Copy link

Blue-smoke007 commented May 30, 2025 via email

@kitkat23-n
Copy link

Existuje pro to nějaký existující problém?

  • Prohledal jsem existující problémy

Tento problém existuje v nejnovější verzi npm.

  • Používám nejnovější npm

Aktuální chování

Pokud přidám následující řádek do .npmrc:

node-options = "${NODE_OPTIONS} --use-system-ca"

Funguje to správně, pokud byl NODE_OPTIONS předtím nastaven na některé příznaky uzlů v mém prostředí. Například, pokud to udělám

export NODE_OPTIONS=--inspect
npm run foo

Pak uzel spuštěný skriptem "foo" přijímá --inspect --use-system-cajako volby uzlu a je to v pořádku.

Pokud ale NODE_OPTIONSproměnná prostředí (env var) není v danou chvíli nastavena na žádnou hodnotu npm run foo, uzel je přijme ${NODE_OPTIONS} --use-system-cajako možnosti a uzel je všechny možnosti odmítne ( --use-system-cataké je ignorován).

V důsledku toho, že npm ponechává substituce proměnných nedotčené, pokud proměnná není nastavena, neexistuje způsob, jak spolehlivě rozšířit možnosti uzlu pomocí .npmrc namísto jejich nahrazení.

Očekávané chování

Buď:

  • ${FOO}Vzor substituce var by měl být eval na prázdný řetězec, pokud není definován FOO.
  • Nebo by měla být možná možnost zadat výchozí hodnoty způsobem podobným bash. ${FOO:-''}Lze tedy použít pouze prázdný řetězec, pokud proměnná není vůbec definována.

Kroky k reprodukci

  1. Přidejte tento skript do souboru package.json:"foo": "node -e 'console.log(process.env.NODE_OPTIONS);'"
  2. Přidejte tento řádek do souboru .npmrc:node-options = "${NODE_OPTIONS} --max-old-space-size=1"
  3. spustit NODE_OPTIONS='' npm run foo- jak se očekávalo, uzel se nespustí, protože mu dojde přidělený 1 MB paměti, což znamená, že příznaky byly předány v pořádku
  4. unset NODE_OPTIONSaby se ujistil, že není nastaven na žádnou hodnotu, ani na prázdný řetězec
  5. npm run foo
  6. Vidíte ${NODE_OPTIONS} --max-old-space-size=1vytištěné a Node dokončí v pořádku, což znamená, že NODE_OPTIONS byly ignorovány kvůli nesprávnému řetězci vyplývajícímu z nesubstituované proměnné.

Prostředí

  • npm: 11.4.1
  • Node.js: 22.15.1
  • Název operačního systému: Windows
  • Název modelu systému:
  • konfigurace npm:

; "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(...)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Enhancement new feature or improvement semver:major backwards-incompatible breaking changes
Projects
None yet
Development

No branches or pull requests

5 participants