Skip to content

Migrate from Yarn to pnpm and bump to Node 26 - #289

Merged
kaareal merged 8 commits into
masterfrom
migrate-yarn-to-pnpm
Jun 18, 2026
Merged

Migrate from Yarn to pnpm and bump to Node 26#289
kaareal merged 8 commits into
masterfrom
migrate-yarn-to-pnpm

Conversation

@kaareal

@kaareal kaareal commented Jun 8, 2026

Copy link
Copy Markdown
Collaborator

Summary

Migrates all three packages (services/api, services/web, services/web/serve) from Yarn classic (1.22.22) to pnpm 11.5.2, and bumps Node to 26.3.0 everywhere it's pinned.

Why Node 26 (and not the current LTS, 24)?

  • Node 26 enters Active LTS in October 2026 — only a few months out — so adopting it now means we don't bump the runtime again right after this migration.
  • Corepack is being removed from Node (unbundled as of Node 25+, absent in 26), and this PR's package-manager bootstrap is deliberately corepack-free (npm i -g pnpm in Docker, pnpm/action-setup in CI). Staying on the current LTS (Node 24, which still ships corepack) would just mean redoing that bootstrap once we inevitably move to 26 and corepack disappears.
  • Doing the runtime bump and the Yarn→pnpm migration once, together, avoids repeating the package-manager plumbing work later down the line.

What changed

  • Lockfiles: yarn.lockpnpm-lock.yaml (3 files). pnpm settings live in a per-package pnpm-workspace.yaml with nodeLinker: hoisted (Yarn-like flat layout, Docker/CI-safe).
  • Supply-chain hardening (per pnpm docs):
    • minimumReleaseAge: 10080 — refuse versions published < 7 days ago.
    • blockExoticSubdeps: true — block git/tarball transitive deps.
    • allowBuilds: — explicit allowlist for the only deps permitted to run install scripts (pnpm blocks the rest by default).
  • No corepack (Node 26 dropped it): CI uses pnpm/action-setup; Docker uses npm i -g pnpm@11.5.2.
  • Docker: Node 26 base images; production api/cli/jobs images install --prod (pnpm ignores NODE_ENV) so devDependencies are omitted; web build sets CI=true to keep the nested serve install frozen.
  • Docker image size: api and web prod images are multi-stage so pnpm/npm and their caches stay out of the runtime image; also fixed a pnpm index dir (/root/.cache/pnpm) that was baking ~200MB into every image. api 832MB → 512MB, web (prod) 411MB → 370MB.
  • CI: pnpm caching keyed on each service's lockfile(s); workflows aligned with pnpm's recommended setup (SHA-pinned pnpm/action-setup, checkout@v6 / setup-node@v6).
  • Worktrees / multi-agent dev: pnpm's shared content-addressable store makes git-worktree workflows (e.g. parallel coding agents, each in its own worktree) fast and disk-cheap out of the box — entirely opt-in, you only get it if you choose to use worktrees. pnpm's further enableGlobalVirtualStore is described in the root README but left disabled: it conflicts with the committed nodeLinker: hoisted that keeps Docker images self-contained (and the committed setting wins over global config, so it can't be toggled on per-machine).
  • Docs/cleanup: removed all yarn references, switched READMEs to pnpm (incl. resolutions → pnpm overrides).

Verification

  • pnpm install --frozen-lockfile passes for all three packages.
  • api: 398/398 tests pass (mongodb-memory-server builds correctly); web pnpm build succeeds; both lints pass.
  • Verified prod Docker images build, omit devDependencies, and run without pnpm; dev compose builds keep pnpm + devDependencies for hot-reload.

kaareal added 7 commits June 8, 2026 08:40
Switch all three packages (api, web, web/serve) from Yarn classic to
pnpm 11.5.2, and bump Node to 26.3.0 everywhere.

- Replace yarn.lock with pnpm-lock.yaml; consolidate pnpm settings into
  per-package pnpm-workspace.yaml (nodeLinker: hoisted).
- Supply-chain hardening: minimumReleaseAge (7d), blockExoticSubdeps, and
  an explicit allowBuilds allowlist for install scripts.
- No corepack (dropped in Node 26): pnpm/action-setup in CI, npm i -g in Docker.
- Dockerfiles: Node 26, pnpm install; production api images install --prod
  (pnpm ignores NODE_ENV) so devDependencies are omitted.
- CI: pnpm caching keyed on the lockfiles.
- Remove all yarn references; switch READMEs to pnpm; document the optional
  enableGlobalVirtualStore worktree opt-in.
devDependencies are only needed for host-side tests/lint (the dev compose
container runs node --watch, no dev-only tooling), so drop the NODE_ENV-gated
conditional and just install --prod in all three api images.
Bump to actions/checkout@v6 and actions/setup-node@v6, and SHA-pin
pnpm/action-setup to v6.0.5, per https://pnpm.io/continuous-integration.
Keep the exact pnpm version pin (11.5.2, matching packageManager),
explicit --frozen-lockfile, and the cache-dependency-path our subdir
layout needs.
pnpm v11 writes a ~200MB index/metadata cache to /root/.cache/pnpm
(separate from the content store at /root/.local/share/pnpm/store).
Only the store was cache-mounted, so the cache got baked into every
image as dead weight. Mounting it too drops the api image 832MB -> 556MB.
Build deps in a deps stage; the final stage copies only node_modules +
source and runs node src/index.js directly, so pnpm/npm and their caches
are no longer in the runtime image (556MB -> 512MB). Switch the
docker-compose dev command to scripts/start-dev (node --watch) so the dev
container no longer needs pnpm either.
Move npm install -g pnpm from the base stage into the build stage so the
production stage (FROM base, runs node serve/static.js) no longer inherits
pnpm or its npm cache. The dev target still has pnpm. Web prod 411MB -> 370MB.
…ted hoisted

The committed nodeLinker: hoisted overrides a globally-set
enableGlobalVirtualStore, so 'pnpm config set --global ...' has no effect.
Document GVS as not-enabled (conflicts with hoisted) instead of a working opt-in.
Comment thread services/api/package.json
"license": "MIT",
"engines": {
"node": ">=20"
"node": ">=24"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not 26?

@kaareal
kaareal merged commit 4b7c334 into master Jun 18, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants