mdn / mdn/fred

Ensure forward-compatibility with npm v12

Open
#1,868 1 comment 0 reactions 1 assignee View on GitHub

@caugner is already working on this.

Since Sep 8, 2026.

p1
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-platform optionalDependencies instead), 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-observatorynpm-publish.yml pins npm install -g npm@^11.8.0; needs a conscious bump.
  • mdn-http-observatorytest.yml has a node-version: [24, 26] matrix; the 26 leg flips to npm 12 once the backport lands.
  • webextensions-exampleslint.yml uses node-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 drop engines.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-scripts to CI npx/npm exec calls
  • webextensions-examples: replace node-version: "latest" with node-version-file
  • mdn-http-observatory: bump the pinned global npm, and confirm the Node 26 matrix leg passes on npm 12
  • content + translated-content*: replace npm adduser with npm login
  • Add a single scheduled matrix job in mdn/workflows that runs npm ci under npm@latest against the affected repos, so regressions surface early; remove it once we're on npm 12
  • Bump .nvmrc, engines.node, and packageManager everywhere 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.

  • allowScripts allowlists (--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-cli tarball install)
  • Unpinned npm/Node in CI:
    • mdn/webextensions-examples#651 replaces node-version: "latest" with a new .nvmrc
  • 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-scripts matrix under npm@latest

Two notes on the task list:

  • No --allow-scripts flags are needed for our CI npx/npm exec calls. Every one of them resolves the package from a local node_modules (the @mdn/rari and @mdn/fred calls all run with working-directory: mdn/content, where both are dependencies), so the root allowScripts applies. Confirmed under npm 12 for both a direct and a transitive local dependency; it only fails when there is no local tree.
  • dex needs three allowlists, not one. prepare runs npm run install:all, which installs deployer-js and cloud-function as separate projects that do not read the root package.json.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.