Ensure forward-compatibility with npm v12
@caugner is already working on this.
Since Sep 8, 2026.
- Dominant language
- JavaScript
- Stars
- 233
- Forks
- 61
- Avg merge
- 1d 1h
- Merged PRs (30d)
- 82
Description
Summary
npm v12 is out (12.0.2, already latest on the registry) and changes several npm install defaults that break our repos. npm has asked for it to be backported into the active LTS lines, Node 24 and Node 26 (nodejs/Release#1161), so pinning .nvmrc to Node 24 only protects us until that lands. Node 24.20.0 and 26.8.1 still bundle npm 11.19.0 today.
This issue tracks making the MDN repos forward-compatible with npm 12 while we stay on npm 11. Most of the work can land now: npm 11.16+ writes and understands allowScripts, but treats it as advisory.
Refs: GitHub changelog · npm/cli v12.0.0
All claims below were verified by running npm@12.0.2 against the real manifests and lockfiles.
1. Dependency install scripts are blocked by default
npm 12 won't run preinstall/install/postinstall from dependencies unless the root package.json has an allowScripts allowlist. Root-package scripts still run.
npm ci still succeeds — the binaries just never get fetched, so this surfaces later as confusing errors.
Packages that ship platform optionalDependencies (esbuild, unrs-resolver, @parcel/watcher, @swc/core) are fine. lefthook is degraded (binary resolves, git hooks not registered). The hard breaks have no fallback:
| Repo | Blocked | Hard breaks |
|---|---|---|
fred |
11 | @mdn/rari (x2), gifsicle, mozjpeg, pngquant-bin, edgedriver, geckodriver |
mdn-http-observatory |
6 | root postinstall, libpq, node_extra_ca_certs_mozilla_bundle |
content |
5 | @mdn/rari, gifsicle, mozjpeg, pngquant-bin |
mcp |
5 | @mdn/rari |
dex |
3 | @mdn/rari |
tree-sitter-mdn |
2 | root install: node-gyp-build, tree-sitter-cli |
express-locallibrary-tutorial |
2 | mongodb-memory-server |
browser-compat-data, bcd-utils, curriculum, triagebot, translated-content{,-de,-it}, todo-vue, client-toolchain-example |
1–2 | — |
@mdn/rari is the most consequential: its postinstall is the only thing that downloads bin/rari, so every consumer breaks.
Fix: on npm >= 11.16, run npm approve-scripts --all --no-allow-scripts-pin and commit the resulting package.json. We want name-only entries, not version pins — pinned entries silently stop matching after a Dependabot bump, which would mean a re-approval commit on every dependency update. Note npm approve-scripts is workspace-unaware.
npx/npm exec have no project package.json to read an allowlist from, so CI calls like npx @mdn/rari build … and npx --package=@mdn/fred fred-ssr need --allow-scripts.
[!NOTE]
Longer term, the packages we publish should stop relying on install scripts entirely (shipping per-platformoptionalDependenciesinstead), which would remove the need for any consumer to allowlist us. That's a significant piece of work and should be tracked separately.
2. engines.npm: "^11" + engine-strict=true is a hard failure
browser-compat-data is the only repo with an engines.npm constraint. Under npm 12:
npm error code EBADENGINE
npm error notsup Required: {"node":">=24","npm":"^11"}
npm error notsup Actual: {"node":"v24.20.0","npm":"12.0.2"}
Downstream consumers are unaffected, though: the published package is built from a generated build/package.json that carries only name and version, so engines never reaches the registry.
3. Unpinned npm/Node in CI
mdn-http-observatory—npm-publish.ymlpinsnpm install -g npm@^11.8.0; needs a conscious bump.mdn-http-observatory—test.ymlhas anode-version: [24, 26]matrix; the 26 leg flips to npm 12 once the backport lands.webextensions-examples—lint.ymlusesnode-version: "latest"(twice).
Everything else uses node-version-file: .nvmrc, which keeps our exposure small.
4. npm adduser was removed — and we document it
Referenced in content (files/en-us/webassembly/guides/rust_to_wasm/index.md, line 397, incl. a link to the npm-adduser docs page) and the ja/ko/ru/zh-cn/de translations. Should become npm login.
5. packageManager pins
15 manifests pin packageManager: "npm@11.x". Nothing enforces them today (no CI enables Corepack), but they need to move with the npm bump.
Verified as not a problem
Listed so nobody re-investigates: allow-git/allow-remote defaulting to none (no lockfile in the org has a git or remote-tarball resolution; tree-sitter-mdn's npm install file:$TARBALL is unaffected since allow-file keeps its default) · unknown configs/abbreviated flags now throwing (every config in our committed .npmrc files and every CI flag we use is still valid) · npm pkg no longer forcing JSON (fred's publish simulation passes --json explicitly) · npm view --json returning an array (our one npm view call doesn't pass --json) · npm shrinkwrap removed (no npm-shrinkwrap.json anywhere) · npm star/stars/unstar removed (unused) · root preinstall now running first (no repo has one) · npm init license default (no impact).
Tasks
-
browser-compat-data: widen or dropengines.npm: "^11" - Adopt
allowScripts(--no-allow-scripts-pin):fred,content,mcp,dex,mdn-http-observatory,browser-compat-data,bcd-utils,curriculum,triagebot,translated-content{,-de,-it},tree-sitter-mdn,express-locallibrary-tutorial,client-toolchain-example,todo-vue - Add
--allow-scriptsto CInpx/npm execcalls -
webextensions-examples: replacenode-version: "latest"withnode-version-file -
mdn-http-observatory: bump the pinned global npm, and confirm the Node 26 matrix leg passes on npm 12 -
content+translated-content*: replacenpm adduserwithnpm login - Add a single scheduled matrix job in
mdn/workflowsthat runsnpm ciundernpm@latestagainst the affected repos, so regressions surface early; remove it once we're on npm 12 - Bump
.nvmrc,engines.node, andpackageManagereverywhere when Node 26 goes LTS (October 2026)
Pull requests
All opened as drafts. Verified against npm@12.0.2 with npm ci --strict-allow-scripts.
allowScriptsallowlists (--no-allow-scripts-pin, so entries are name-only):- mdn/fred#1869
- mdn/content#45569
- mdn/translated-content#38370
- mdn/translated-content-de#290
- mdn/translated-content-it#18
- mdn/mcp#256
- mdn/dex#505
- mdn/bcd-utils#367
- mdn/curriculum#136
- mdn/triagebot#234
- mdn/tree-sitter-mdn#151
- mdn/express-locallibrary-tutorial#368
- mdn/client-toolchain-example#112
- mdn/todo-vue#519
- mdn/browser-compat-data#30455 (also drops
engines.npm) - mdn/mdn-http-observatory#609 (also bumps the pinned global npm and fixes the
test-clitarball install)
- Unpinned npm/Node in CI:
- mdn/webextensions-examples#651 replaces
node-version: "latest"with a new.nvmrc
- mdn/webextensions-examples#651 replaces
npm adduser->npm login:- mdn/content#45568
- mdn/translated-content#38369 (
ja,ko,ru,zh-cn) - mdn/translated-content-de#289
- Regression canary:
- mdn/workflows#201 adds a weekly
npm ci --strict-allow-scriptsmatrix undernpm@latest
- mdn/workflows#201 adds a weekly
Two notes on the task list:
- No
--allow-scriptsflags are needed for our CInpx/npm execcalls. Every one of them resolves the package from a localnode_modules(the@mdn/rariand@mdn/fredcalls all run withworking-directory: mdn/content, where both are dependencies), so the rootallowScriptsapplies. Confirmed under npm 12 for both a direct and a transitive local dependency; it only fails when there is no local tree. dexneeds three allowlists, not one.preparerunsnpm run install:all, which installsdeployer-jsandcloud-functionas separate projects that do not read the rootpackage.json.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.