Skip to content

Build: migrate core-web from pnpm 10.17.1 to pnpm 12.4.2 #37553

Description

@oidacra

Description

core-web runs on pnpm 10.17.1. Since then pnpm has shipped two majors — 11.0.0 (2026-04-28) and 12.0.0 (2026-08-26, currently 12.4.2) — and both changed things that this repository depends on. The move is mostly mechanical, but it touches the same build and CI surface that PR #36169 (yarn 1 to pnpm 10 migration, merged 2026-06-16, 31 files) established, so that PR is the checklist for what has to be re-verified.

What actually changes

Area pnpm 10 (today) pnpm 11 / 12
Where settings live package.json#pnpm block (core-web/package.json:329-370) Not read any more. Everything moves to a new core-web/pnpm-workspace.yaml
.npmrc registry + engine-strict + strict-peer-dependencies + fetch-timeout Auth and registry only. The other three are silently ignored unless moved
Build scripts allowlist onlyBuiltDependencies (11 packages) onlyBuiltDependencies, neverBuiltDependencies, ignoredBuiltDependencies, onlyBuiltDependenciesFile removed; replaced by the allowBuilds map
Distribution Single ~17 MB JS package Native Rust binary shipped through @pnpm/exe.* optional dependencies (3.9 MB wrapper). Corepack runs no lifecycle scripts, so under corepack the binary is downloaded on first use and signature-verified
Supply-chain defaults Opt-in minimumReleaseAge: 1440, blockExoticSubdeps: true, strictDepBuilds: true on by default
verifyDepsBeforeRun false install (auto-installs before every pnpm run / pnpm exec)
engineStrict warns on incompatible packages under optional subtrees fails the install (pnpm 12) — and this repo sets engine-strict=true

Why now

  • Deterministic lockfile (pnpm 12). Dependency cycles are broken canonically during peer resolution, so the lockfile becomes a pure function of the dependency graph. On cycle-heavy workspaces peer resolution is 2-3x faster, uses ~25% less memory and produces a smaller lockfile. This repo's lock is 33,442 lines / 3,011 packages with Angular 22 + PrimeNG + NgRx peer chains, which is exactly that shape.
  • Install performance. SQLite-backed store index (store v11) instead of thousands of JSON files, bundled manifests, direct-to-CAS writes (~30k fewer rename syscalls per cold install), undici with Happy Eyeballs, If-Modified-Since metadata fetches, and hardlink-before-clone on Linux.
  • Supply-chain protection by default rather than as a follow-up nobody schedules.
  • New tooling we can use in CI: pnpm ci (clean + frozen install), pnpm sbom (CycloneDX 1.7 / SPDX 2.3), pnpm audit on GHSA identifiers with --fix=update.
  • Staying on 10 only makes the eventual jump larger; 11 alone would be a toll booth, since 12 keeps 11's configuration model.

Registry mirror finding

Verified against both registries while scoping this work:

Registry time (full doc) time (abbreviated) dist.attestations dist.signatures
registry.npmjs.org yes no yes yes
dotcms-npm.b-cdn.net no no yes yes

minimumReleaseAge needs publish timestamps, so it cannot be evaluated through our mirror. With the default minimumReleaseAgeIgnoreMissingTime: true the check is skipped silently; setting it to false would fail every install. Attestations and signatures do pass through, so trustPolicy works. This task therefore hardens what is verifiable and opens a separate infrastructure issue for the mirror.

Decisions already taken

Decision Choice
Target version Pin pnpm 12.4.2 (includes the executable-shim security fixes)
pnpm install strategy in Maven Decided by experiment in the POC: corepack (with download-on-first-use of the native binary) vs. npm-based install into installs/node. The outcome is documented in the PR
Supply-chain posture trustPolicy: no-downgrade, blockExoticSubdeps: true, strictDepBuilds: true. minimumReleaseAgeIgnoreMissingTime stays true, documented as a no-op until the mirror exposes time
verifyDepsBeforeRun warn — reports drift without injecting an install into a Maven or CI build

Acceptance Criteria

Configuration migration

  • core-web/pnpm-workspace.yaml exists and carries overrides, peerDependencyRules, ignoredOptionalDependencies, engineStrict, strictPeerDependencies and fetchTimeout; the pnpm block is gone from core-web/package.json
  • core-web/.npmrc contains only registry and auth settings
  • The 11 entries of onlyBuiltDependencies are expressed as an allowBuilds map, and canvas keeps its explicit entry alongside ignoredOptionalDependencies
  • core-web/package.json declares packageManager: pnpm@12.4.2, and nodejs-parent/pom.xml declares the same version, so the existing sync check in maven-job/action.yml:213 passes
  • pnpm install reports no unrecognized workspace settings (pnpm 12 fails with ERR_PNPM_UNRECOGNIZED_WORKSPACE_SETTINGS when the project pins its pnpm version, so a leftover or misspelled key is caught here)

Security posture

  • trustPolicy: no-downgrade, blockExoticSubdeps: true and strictDepBuilds: true are set explicitly in pnpm-workspace.yaml and a full install succeeds with them
  • minimumReleaseAgeIgnoreMissingTime is left at its default with a comment recording that our mirror strips time, so the maturity check does not run today
  • A follow-up issue is opened against the infrastructure owners of dotcms-npm.b-cdn.net asking for time to be preserved in package metadata, and is linked from this issue
  • verifyDepsBeforeRun: warn is set, and a pnpm exec nx invocation against a stale node_modules warns instead of installing

Build and CI

  • The pnpm installation strategy for Maven is chosen by running both options in the POC; the pom reflects the winner and the PR description records why, including whether a clean build reaches the network for the native binary
  • pnpm/action-setup is bumped to v6.1.0 (the first release supporting pnpm 12) and pinned by commit SHA in both maven-job/action.yml and deploy-javascript-sdk/action.yml
  • pnpm install --frozen-lockfile succeeds from a clean clone, and the install time is recorded against the pnpm 10 baseline
  • pnpm exec nx run-many -t build --exclude='tag:skip:build' builds every project
  • ./mvnw -DskipTests install -pl :dotcms-core-web --am -Dmaven.build.cache.enabled=false succeeds from a clean state
  • The dotCMS Docker image builds
  • dotcms-postman and apps/dotcms-ui-e2e still resolve ${node.install.dir}/pnpm and run their install goals
  • deploy-javascript-sdk is exercised in a dry run, since pnpm 11 replaced the npm-delegating publish flow with a native one

No regressions

  • pnpm-lock.yaml is regenerated and the diff is reviewed: version changes are explained (pnpm 12 re-keys walk-order-dependent peer variants of cyclic packages once) and no dependency is unintentionally upgraded
  • engineStrict does not newly fail the install; pnpm 12 turned the optional-subtree warning into an error, so any package it now rejects is triaged and resolved
  • The lockfile is a two-document YAML from pnpm 11 onward; anything in CI that parses it is confirmed to read both documents (a parser that stops at the first reports zero dependencies without failing)
  • Frontend tests and lint pass unchanged

Documentation

  • core-web/CLAUDE.md, .github/copilot-instructions.md and .github/instructions/frontend.instructions.md reflect the new version and the pnpm-workspace.yaml location for settings
  • The dead stylus: github:stylus/stylus#0.59.0 override is either removed or justified (stylus is an optional peer that is never installed, so the override resolves nothing today)

Priority

Medium

Additional Context

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions