Repository navigation
Framework: Update npm to v12 #82328
Description
Activity
- added[Type] Build ToolingIssues or PRs related to build toolingIssues or PRs related to build tooling
on Sep 2, 2026 Related: #74877
It's worth testing whether package managers like
nvmandfnmcan automatically pick up the required versions.It's worth testing whether package managers like
nvmandfnmcan automatically pick up the required versions.No, they don't. They only set the runtime, i.e. NodeJS and it sets the package manager version that comes bundled with it.
Reacted by Jarda SnajdrIt's worth testing whether package managers like nvm and fnm can automatically pick up the required versions.
There is the
corepacktool that used to be bundled with Node (but was removed). It's something like "nvm for package managers". Maintaining multiple versions, automatically switching according topackage.json. Not sure if it could be useful for us in any way.Yes, Corepack can help but then we have one more tool to explicitly install after we switch to Node 26.
And TIL that Corepack now supports
devEngines- added 5 commits that reference this issue
on Sep 9, 2026 I am planning to create draft PRs for both Gutenberg and
wordpress-developwith npm v12, and then someone like @desrosj can file a Systems request to have npm v12 installed on the build server.- added[Status] In ProgressTracking issues with work in progressTracking issues with work in progress
on Sep 10, 2026 - added a commit that references this issue
on Sep 10, 2026 34 remaining items
- added 9 commits that reference this issue
on Oct 5, 2026 Due to some concerns about release/older branchs, we have put the upgrade on hold - #82732 (comment).
Meanwhile, I am firing up some AI agents to see what needs to be done for older branches.
Here is what Claude Code (
opus-5.5) and Codex (gpt-5.6-sol) together came up with:Node 24, npm 11/12 and the supply chain policy on
wp/*release branchesGoal
Every supported
wp/X.Ybranch can ship a security release through trunk's Publish npm packages workflow (release_type: wp) without manual intervention. Each branch is considered done only after a release candidate (RC, see RC procedure) passes on it.Scope
- Branches:
wp/5.1towp/7.1. - Port: Node 24 runtime, npm 11.18 floor that also accepts npm 12, and the npm v12 supply chain defaults (
allow-git,allow-remote,allow-file,strict-allow-scriptswithallowScripts), as described in trunk's supply chain policy. - Not ported:
install-strategy = "linked",strict-peer-deps, isolated dependency moves, React 19, TypeScript 7, or any other trunk change that a security release does not need. - Lockfiles may be rewritten. They are always regenerated in place from the existing lockfile, never deleted and resolved from scratch.
Constraint: how trunk publishes a wp/X.Y branch
publish-npm-packages.ymlchecks out the release CLI from trunk and the publishing branch side by side. It installs Node fromcli/.nvmrcand npm from trunk'sdevEnginesviasetup-npm(currently^11.18.0, soon 12), then the CLI runs these in the branch checkout:npm cinpm exec --no -- lerna version <bump> --no-private --no-push --yesnpm exec --no -- lerna publish from-package --dist-tag wp-X.Y --git-head <sha> --yes --no-verify-access, which runs the rootprepublishOnly(the package build).
So every branch must install, build its packages and run its Lerna under trunk's Node and npm. The branch's own
.npmrcandpackage.jsonapply to steps 1 to 3.Verified facts
Checked locally with Node 24.21 and npm 11.18.0 unless noted.
# Fact Evidence F1 engine-strictplus an npm range that excludes 11 (<7on 5.8 to 6.3,>=8 <9on 6.4) failsnpm ciwithEBADENGINEMinimal reproduction F2 npm cion an unchanged v1 lockfile fails the "not in sync" checkwp/5.6andwp/6.3trialsF3 On wp/6.3,engine-strictalso fails on transitive engines, for example@es-joy/jsdoccomment@0.36.1(node ^14 || ... || ^19)wp/6.3trialF4 With engine-strictremoved andlegacy-peer-depsadded, npm 11 converts thewp/6.3v1 lockfile and resolves the tree. The run hit the 10 minute cap during install scripts, so the build is unverifiedwp/6.3trialF5 wp/6.4with only.nvmrcandengineschanged installs, runsbuild:packagesand passes theblocksandpost-dateunit tests (525 tests)wp/6.4trialF6 allow-file = nonedoes not blockfile:directory dependenciesMinimal reproduction F7 strict-allow-scriptsgates install scripts of non-workspacefile:dependencies. They match only when keyed by spec ("file:packages/a"or"a@file:packages/a"), not by name. Workspaces are exemptMinimal reproduction F8 Lerna below 3.21 rejects --no-private(yargs.strict()).@lerna/publishbelow roughly 3.13 also rejects--git-headPackage source of the locked Lerna versions F9 Every git and remote tarball dependency on 5.5 to 7.0 is declared only by packages/react-native-editor. No published package depends on@wordpress/react-native-*Branch lockfiles and packages/*/package.jsonF10 The August 2026 security release was published manually for wp/5.8towp/6.9. Thewp-5.2towp-5.7dist-tags were last updated in 2022npm dist-tagsandtimefor@wordpress/block-library, #81295, #81325F11 Without strict-allow-scripts, npm 11.18 ignoresallowScriptsand runs every install script,falseentries included. npm 12 blocks unlisted andfalsescripts by default, with only a warningMinimal reproduction on npm 11.18.0 and 12.2.0 Common target state
Applies to every branch unless the per-branch table says otherwise.
ID File Change C1 .nvmrc24.20(trunk value)C2 package.jsonenginesset to{ "node": ">=24.18.0", "npm": ">=11.18.0" }and the trunkdevEnginesblock (runtimenode>=24.18.0,packageManagernpm>=11.18.0,onFail: error). Mirrorenginesintopackages[""]of the lockfileC3 .npmrcAdd min-release-age = 1,strict-allow-scripts = true,allow-git = none,allow-remote = none,allow-file = none,legacy-peer-deps = true(if missing). Keep the existingsave-exactandprefer-dedupe. Setlockfile-version = 3. Do not addinstall-strategy,strict-peer-depsorengine-strictwhere F3 appliesC4 package.jsonallowScriptslists every dependency that ships an install script, allfalse, same policy as trunk. Non-workspace branches key local packages by spec (F7). Derive the list fromESTRICTALLOWSCRIPTSoutput, then review each entry.strict-allow-scriptsstays on every branch: without it npm 11 runs every script (F11), including native builds that fail on Node 24 (node-sass,nodegit)C5 package.json, lockfileRemove the React Native packages from the install (F9). Workspace branches (6.8 to 7.0): add "!packages/react-native-*"toworkspaces. Non-workspace branches (5.5 to 6.7): delete the@wordpress/react-native-{aztec,bridge,editor}file:entries from rootdependencies. Deletepatches/*.patchfiles whose target package is no longer installed (for examplereact-native-reanimatedon 6.4), and droppatch-xcode.jsfrompostinstallifreact-nativeleaves the tree, otherwisepatch-packagefails the install. Remove thetest:native*scripts and the native unit test job. After C6, assert the lockfile has nogit+,github:or non-registryhttps:sourcesC6 package-lock.jsonRegenerate in place with npm 11.18 on Node 24 ( npm installwith the existing lockfile present), producing lockfile v3. Review the diff: published packages' dependency versions must not move except for entries removed with C5 or pruned as extraneousC7 .github/Port .github/setup-npm/action.ymland the trunk.github/setup-node/action.yml(introducing it where the branch callsactions/setup-nodedirectly), with thenode_modulescache disabled: trunk's cache-hit path replaysnpm run postinstallandlerna run postinstall, which fails on branches without a rootpostinstall(5.2 to 5.5) and reaches the React Native packages C5 drops from the install. The npm download cache stays. Pin every action to the SHA trunk uses. This retiresactions/cachebelow 4.2 (6.7 and older) andupload-artifactanddownload-artifactv2/v3 (6.4 and older), which GitHub has shut down (cache, artifacts)C8 .github/workflows/Keep every workflow, including branch management ( cherry-pick-wp-release,sync-backport-changelog). Change only what C1 to C7 break: Node version source, setup actions, retired action versions, and the native jobs C5 removes (rnmobile-*, which hold every macOS runner, and native unit tests).unit-test.yml(JS and PHP),static-checks.ymland the build job ofbuild-plugin-zip.ymlmust pass for the RC.build-plugin-zip.ymlfirst exists on 5.7; on 5.1 to 5.6 the RC runsnpm run buildinsteadC9 Lerna Lerna 3.21 or newer (F8) Per-branch changes
Branch Lockfile Changes beyond C1 to C9 Status 7.1 v3 Already on Node 24.18, npm 11.16, min-release-age. Needs C1, C2 (npm floor 11.18,devEngines), C3, C4, C7. No React Native packages, so no C5Not run 7.0, 6.9 v3 All of C1 to C8. Publish path verified on Node 24 in #82122 before C3 to C5 Partly verified 6.8 v3 All of C1 to C8 Not run 6.5 to 6.7 v2 All of C1 to C8. C6 upgrades to v3 Not run 6.4 v2 All of C1 to C8. C6 upgrades to v3 F5 (before C3 to C5) 6.2, 6.3 v1 All of C1 to C8. Drop engine-strict(F3)F4 5.9 to 6.1 v1 As 6.3 Not run 5.8 v1 As 6.3. Webpack 4.46 needs NODE_OPTIONS=--openssl-legacy-providerfornpm run build(plugin build only, notbuild:packages)Not run 5.7 v1 As 5.8. nodegit(via@wordpress/env) is denied by C4, so confirm the PHP unit test job still startswp-envNot run 5.5, 5.6 v1 As 5.7, plus: replace node-sass4 withsassinbin/packages/build-worker.jsand rootdevDependencies(therenderlegacy API stays). Bump Lerna to 3.22.1 (C9)Not run 5.3, 5.4 v1 As 5.5, except no React Native packages (no C5). Only pull-request-automation.ymlexists, so C8 addsunit-test.ymlandstatic-checks.ymlfrom 5.5 adapted to C7Not run 5.1, 5.2 v1 As 5.3, plus: replace github:yoavf/webpack-rtl-plugin#developwithwebpack-rtl-plugin@2.0.0from the registry (or drop it as 5.3 did) and confirm the RTL CSS output is unchanged.node-sasslives inbin/packages/build.json 5.1 and 5.2, which have nobuild-worker.js. No.github/workflows, so the RC is the only checkNot run RC procedure
An RC is a full rehearsal of trunk's
npm-wpflow on the ported branch, without touching npm or the real remote.- Branch CI on the port PR is green for the C8 required workflows.
- Start a local Verdaccio registry and create a throwaway user. Write a temporary
.npmrcwithregistry=http://localhost:4873/and a registry-scoped_authToken, and export it asNPM_CONFIG_USERCONFIG. This mirrors whatactions/setup-nodewithregistry-urldoes in the workflow.npm whoamimust print the throwaway user. - Record the branch SHA, seed a local bare repository with
wp/X.Yat that SHA, clone it, and confirmoriginis thatfile:path. Add a throwaway commit touching one published package (for example itsREADME.md) solerna versionhas something to bump. - Guard before running anything: abort unless
npm config get registryis a loopback URL andgit -C <clone> remote get-url --push originis a local path. No real npm token or GitHub credential may be present in the environment. - Under trunk's Node and npm (same steps as
publish-npm-packages.yml), runnpm ciin a trunk checkout, thennpm exec --no release-cli -- npm-wp --wp-version X.Y --ci --repository-path <clone>. - Pass criteria:
npm ciin the clone succeeds with noEALLOW*orESTRICTALLOWSCRIPTS,prepublishOnlybuilds, exactly the set of packageslerna versionbumped lands in Verdaccio underwp-X.Y, and the CLI's publication verification passes. Then repeat steps 2 to 6 with npm 12 from a clean slate: new Verdaccio storage, a new bareoriginseeded from the same recorded branch SHA, and the identical throwaway commit. - Build parity: before step 2, record each package's
wp-X.Yversion and download its tarball withnpm pack <name>@wp-X.Y --registry=https://registry.npmjs.organd no user config, so the baseline can never resolve to Verdaccio. Diff each Verdaccio tarball against that baseline. Onlyversion,gitHead, changelog, the throwaway change and source changes merged since that release may differ. Any change inbuild,build-module,build-styleorbuild-typesis a regression from the Node 24 port to investigate.
Rollout order
- 7.1, 7.0, 6.9 (smallest change, establishes the template and RC tooling).
- 6.8 down to 6.4.
- 6.3 down to 5.8.
- 5.7 down to 5.1, best effort (see "Decisions" below).
Decisions
- 5.1 to 5.7 still need security releases. They are best effort: if a branch turns out too costly to port, skip it for now and record why in its tracking issue.
- The RC is the Verdaccio rehearsal only. No prerelease is published to npm.
- Branches:
Reviewing the plan with my local agents, it seems that the branch changes alone won't be enough to make the release flow pass. Here are some suggestions to the plan:
- Isolate npm settings.
npm execexports trunk's settings to the release CLI. I reproducedengine-strict=trueandinstall-strategy=linkedoverriding the branch's.npmrc, so droppingengine-strictwon't resolve F3. Could we invoke the CLI directly with Node or isolate the inherited settings? - Update the lockfile validator. On
wp/5.5–wp/6.3, it readspackageLock.dependencies, which v3 omits. I reproduced the crash. This check runs in the required lint workflow. - Resolve the
nodegitdependency. Onwp/5.7,wp-envimports it unconditionally. Denying its install scripts leaves its native binding unavailable, so the PHP test runner needs a compatible replacement. - Specify Verdaccio's upstream or seed data. With neither,
lerna publish from-packagealso selects unchanged packages, invalidating the "exactly the bumped packages" check.
- Isolate npm settings.
Let's also make sure to coordinate with @desrosj , I know he's been working on improving the setup on all the past release branches.
Thanks @ciampo, these match what I'm seeing while digging into porting 7.1, 7.0 and 6.9.
- npm settings: Confirmed. A rehearsal on
wp/7.1installed with trunk'sinstall-strategy = linkedand failed at thelerna versioncommit hook. I am fixing those issues in the release CLI rather than the workflow: commands run in the release branch drop thenpm_config_*values copied from the CLI's.npmrc, so user config (registry, auth) and explicit overrides still apply, and a trunk target sees identical config. This also covers localnpm execruns and the CLI's ownnpm exec -- lernacalls. PR coming. - Lockfile validator: Agreed. We should port it to read
packageson 5.5 to 6.3 rather than keep lockfile v2 there. nodegit: Agreed. It will need to be resolved when we reach 5.7, either by backporting thewp-envchange away fromnodegitor by running the PHP job with a newer published@wordpress/env.- Verdaccio: It proxies npmjs, so
from-packageskips versions already published. The gap I hit was elsewhere: the bare origin had no tags, so Lerna assumed every package changed. The rehearsal now seeds the tags a--depth=999fetch auto-follows.
- npm settings: Confirmed. A rehearsal on
While I understand the desire and advantage of upgrading the supported version of NPM, I don't think it's practical to use a version of NPM that is out of sync with the node version specified in the
.nvmrcfile.The long term development process in both the Gutenberg and WordPress-Develop repos has been to use the bundled version of NPM to allow development environments to justwork™️.
One of the main problems is that NVM has a bias towards prepending the directory to
$PATHafter runningnvm use. I use FNM which shares the same bias. Using a version of NPM that is out of sync will require manually changing the$PATHeach timenvm useis run.My personal workflow is to run
nvm use(well,fnm use) as a post-checkout hook so I can be sure I am using the correct version of node for the required branch.In order to prevent any supply chain issues making it in to the Gutenberg plugin, the GitHub actions could be configured to use NPM 12 while allowing developers to use the version distributed with node for the development workflow.
What problem does this address?
npm 12 shipped on 8 July 2026 as a security release. Three things
npm installdoes automatically today become opt-in: dependency lifecycle scripts (allowScripts), git dependencies (--allow-git), and remote URL dependencies (--allow-remote). See the announcement and RFC 54.We normally pick up an npm major by moving to the Node.js version that bundles it. That path is closed here. The Node.js Release WG decided not to land npm 12 on Node.js 26, 24 or 22. Node.js 26 becomes Active LTS on 28 October 2026 still bundling npm 11, and under the new annual release schedule the next major, Node.js 27, is Current on 22 April 2027 and LTS that October. Waiting for a bundled npm 12 keeps us on npm 11 for another year with the supply chain protections switched off.
Meanwhile
npm@latestalready resolves to 12.0.2, so contributors installing the latest npm are on a major we have never tested against. Related: #72143.What is your proposed solution?
Install npm 12 explicitly, decoupled from the bundled Node.js version. npm 12 requires Node.js
^22.22.2 || ^24.15.0 || >=26.0.0, so the 24.18 line we are moving to in #72973 already satisfies it.Steps
devEngines.packageManager.versionin the rootpackage.jsonis the single source of truth for the npm version CI and the release tooling install, so this is a one-line change plusengines.npm.What the new defaults hit on
trunkallowScripts:@parcel/watcher,@swc/core,core-js,core-js-pure,esbuild,fs-ext,fsevents,leveldown,nx,unrs-resolver. Several fetch or build native binaries, andfs-extandleveldownarenode-gypbuilds.--allow-gitand--allow-remotecan stay at their new defaults.packages/iconsgeneratessrc/libraryfrom apreparescript. Workspace packages are link dependencies and npm 12 blockspreparefor those, so this needs verifying before the bump.Thoughts?
CC: @aduth @desrosj @johnbillion @tyxla @jsnajdr @Mamaduka @ciampo @lancewillett @adimoldovan
Tasks
wordpress-developwordpress-develop