Skip to content

Migrate client and client-v3 from npm to pnpm - #1422

Merged
Tim020 merged 4 commits into
devfrom
feature/pnpm-migration
Sep 25, 2026
Merged

Tim020 merged 4 commits into
devfrom
feature/pnpm-migration

Conversation

@Tim020

@Tim020 Tim020 commented Sep 23, 2026

Copy link
Copy Markdown
Contributor

Summary

Switches client/ and client-v3/ from npm to pnpm 12.5.1 (each keeps its own independent pnpm-lock.yaml — no shared workspace). Electron's removal (#1416) cleared the way for this: Electron Forge needed pnpm's node-linker=hoisted, which would have meant giving up pnpm's strict node_modules for at least one project. With Electron gone, both remaining projects use pnpm's default strict linker.

Why

Recurring npm lockfile problems recorded against this repo:

  • npm only records the optional platform/CPU package variants it actually installed locally, so a lockfile regenerated on macOS silently drops the Linux CI variants (npm/cli#7961, #8320).
  • Separately, targeted npm install runs have been observed to non-deterministically drop unrelated optional dependency entries from the lockfile.

pnpm resolves from registry metadata rather than local node_modules, so the lockfile is platform-independent by construction. Verified by spot-checking the generated lockfile for @rolldown/binding-* entries across all target platforms, not just darwin-arm64 (the machine that generated it).

What changed

  • packageManager: "pnpm@12.5.1" pinned in both package.json; engines.npm removed.
  • pnpm-workspace.yaml added per project (pnpm 10+ moved overrides/onlyBuiltDependencies out of package.json's pnpm field into this file — a real breaking change I hit while doing this migration). Each allowlists only the install/postinstall scripts actually needed, with a comment on each explaining what it does:
    • @parcel/watcher (both) — native file-watcher binding used by chokidar for HMR/watch mode
    • vue-demi (client-v3) — selects the Vue 2 vs 3 interop shim; functionally required
    • esbuild (client) — downloads its native binary; required to run at all
    • bootstrap-vue, core-js (both) — harmless donation-message no-ops, allowed for completeness
  • client/.npmrc: dropped legacy-peer-deps=true (npm-specific). pnpm warns rather than hard-fails on the pre-existing @vue/test-utils@2.5.1 peer mismatch against vue@2.7.14 — pnpm peers check shows the warning, but it's cosmetic; the full test suite already exercises this pairing and passes.
  • CI (nodelint.yml, client-test.yml, playwright-test.yml, build-server.yml) and both Dockerfile frontend build stages switched to pnpm/action-setup@v4 + actions/setup-node (cache: pnpm) + pnpm install --frozen-lockfile. client-test.yml also picked up a pre-existing actions/checkout@v3 → v4 bump for consistency (unrelated drift, fixed while touching the file). dependabot.yml needs no change — its npm ecosystem key already covers pnpm-lock.yaml.
  • scripts/update-version.sh, README.md, client-v3/README.md, documentation/development.md updated to reference pnpm. Also fixed two pre-existing doc inaccuracies noticed while editing: the root README's "Client" build instructions pointed at client while describing client-v3's output path, and development.md's "Project Structure" section had dropped client-v3 entirely (introduced by the Electron-removal pass in Remove the Electron desktop app (web-only again) #1416, which turned "three main components: server/client/electron" into "two: server/client" without noticing client-v3 was the actual third).

Testing

Both projects, from a clean pnpm install --frozen-lockfile:

  • typecheck, ci-lint, full unit test suite, and production build all pass.
  • The full 221-test Playwright suite against client-v3 passes cleanly (confirmed twice — once with a temporary, since-reverted port/tmpfile override to rule out interference from an unrelated concurrent E2E run on the dev machine, and once with no override needed at all).

Not covered here

  • client/ still carries an @vue/test-utils peer-dependency warning (see above) — pre-existing, not introduced by this change, and not a functional problem.
  • PR Make E2E test server port and cleanup safe for parallel runs #1420 (E2E parallel-isolation) is still open; this PR doesn't depend on it and doesn't touch any of the same files.

🤖 Generated with Claude Code

@Tim020 Tim020 added the claude Issues created by Claude label Sep 23, 2026
@github-actions github-actions Bot added github GitHub actions related issue or pull request client Pull requests changing front end code xlarge-diff client-v3 labels Sep 23, 2026
@github-actions

github-actions Bot commented Sep 23, 2026 •

Copy link
Copy Markdown

Client V3 Test Results

209 tests   209 ✅  0s ⏱️
 14 suites    0 💤
  1 files      0 ❌

Results for commit 0bbb13b.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Sep 23, 2026 •

Copy link
Copy Markdown

Client Test Results

95 tests   95 ✅  0s ⏱️
 6 suites   0 💤
 1 files     0 ❌

Results for commit 0bbb13b.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Sep 23, 2026 •

Copy link
Copy Markdown

Playwright E2E Results (chromium)

222 tests   222 ✅  2m 9s ⏱️
 14 suites    0 💤
  1 files      0 ❌

Results for commit 0bbb13b.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Sep 23, 2026 •

Copy link
Copy Markdown

Playwright E2E Results (firefox)

222 tests   222 ✅  2m 10s ⏱️
 14 suites    0 💤
  1 files      0 ❌

Results for commit 0bbb13b.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Sep 23, 2026 •

Copy link
Copy Markdown

Python Test Results

  1 files    1 suites   46s ⏱️
934 tests 934 ✅ 0 💤 0 ❌
939 runs  939 ✅ 0 💤 0 ❌

Results for commit 0bbb13b.

♻️ This comment has been updated with latest results.

@Tim020
Tim020 force-pushed the feature/pnpm-migration branch from 4bd1bd8 to 9bdaaa6 Compare September 24, 2026 00:27
Tim020 and others added 2 commits September 24, 2026 19:47
Replaces npm with pnpm 12.5.1 as the package manager for both `client/`
and `client-v3/` (each keeps its own independent pnpm-lock.yaml — no
shared workspace). This directly addresses the recurring npm lockfile
problems recorded against this repo: npm only records the optional
platform/CPU package variants it actually installed locally (npm/cli
#7961/#8320), so a lockfile regenerated on macOS silently drops the
Linux CI variants, and separately, targeted `npm install` runs have
been observed to non-deterministically drop unrelated optional
dependency entries. pnpm resolves from registry metadata rather than
local `node_modules`, so the lockfile is platform-independent by
construction — verified by spot-checking the generated pnpm-lock.yaml
for @rolldown/binding-* entries across all target platforms, not just
darwin-arm64 (the machine that generated it).

Electron is gone (#1416), which removes the one blocker to a clean pnpm
migration: Electron Forge needs pnpm's `node-linker=hoisted`, giving up
its strict node_modules; with Electron gone, both remaining projects use
pnpm's default strict linker.

- `packageManager: "pnpm@12.5.1"` pinned in both package.json files;
  `engines.npm` removed (meaningless under pnpm).
- `pnpm-workspace.yaml` added per project (pnpm 10+ moved `overrides`
  and `onlyBuiltDependencies` out of package.json's `pnpm` field into
  this file). Each allowlists the install/postinstall scripts actually
  needed: `@parcel/watcher` (native file-watcher binding used by
  chokidar for HMR/watch mode), `vue-demi` (client-v3 only — selects
  the Vue 2 vs 3 interop shim, functionally required), `esbuild`
  (client only — downloads its native binary, required to run at all),
  `bootstrap-vue`/`core-js` (both harmless donation-message no-ops,
  allowed for completeness).
- `client/.npmrc`: dropped `legacy-peer-deps=true` (npm-specific, no
  pnpm equivalent needed — pnpm warns rather than hard-fails on the
  pre-existing `@vue/test-utils@2.5.1` peer mismatch against vue@2.7.14;
  `pnpm peers check` shows the warning, but it's cosmetic: the full
  test suite already exercises this pairing and passes).
- CI (`nodelint.yml`, `client-test.yml`, `playwright-test.yml`,
  `build-server.yml`) and the two Dockerfile frontend build stages
  switched from `actions/setup-node` + `npm ci` to
  `pnpm/action-setup@v4` + `actions/setup-node` (cache: pnpm) +
  `pnpm install --frozen-lockfile`. `client-test.yml` also picked up a
  pre-existing actions/checkout@v3 → v4 bump for consistency with the
  rest of the CI (unrelated drift, fixed while touching the file).
  `dependabot.yml` needs no change: its `npm` ecosystem key already
  covers pnpm-lock.yaml.
- `scripts/update-version.sh`, `README.md`, `client-v3/README.md` and
  `documentation/development.md` updated to reference pnpm instead of
  npm. Also fixed two pre-existing doc inaccuracies noticed while
  editing: the root README's "Client" build instructions pointed at
  `client` while describing client-v3's output path, and
  `development.md`'s "Project Structure" section had dropped `client-v3`
  entirely (introduced by the Electron-removal pass in #1416, which
  turned "three main components: server/client/electron" into "two:
  server/client" without noticing client-v3 was the actual third).

Testing (both projects): typecheck, ci-lint, full unit test suite, and
production build all pass under pnpm. The full 221-test Playwright
suite against client-v3 also passes cleanly under pnpm (confirmed with
a temporary, since-reverted port/tmpfile override to rule out
interference from an unrelated concurrent E2E run on this machine —
PR #1420 will fix that class of collision generally, but it hasn't
merged yet).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Every job broke with 'No pnpm version is specified' — pnpm/action-setup
resolves package_json_file relative to GITHUB_WORKSPACE (repo root) by
default, not the job's defaults.run.working-directory (that setting only
applies to run: steps, not uses: steps), and there's no root package.json.
Pass package_json_file explicitly per job.

Also fixes the SonarCloud Quality Gate failure (C Security Rating on New
Code, githubactions:S7637 x8): pin pnpm/action-setup to the exact commit
the v4 tag currently resolves to, rather than a mutable major-version tag.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@Tim020
Tim020 force-pushed the feature/pnpm-migration branch from 9bdaaa6 to b3fd9bc Compare September 24, 2026 18:50
@Tim020
Tim020 marked this pull request as ready for review September 24, 2026 18:55

@Tim020 Tim020 left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Review round 1

Reviewed head: b3fd9bc1899809e0a2444389ab7cb569c6837ac6

Verdict: changes needed. The migration itself is solid: CI is fully green (unit, lint, typecheck, Playwright on both browsers, Docker, and server builds on all 3 OSes), and pnpm install --frozen-lockfile works cleanly with pnpm 12.5.1. One real regression needs fixing before merge: scripts/update-version.sh breaks under pnpm 12. Engine-strict enforcement is also silently lost.

Previous round follow-up

First round, so there are no previous threads.

SonarQube

Quality gate passed. Data came from the anonymous SonarCloud API and matches the analysed commit for this head SHA.

  • All 5 conditions pass: new reliability, security and maintainability ratings are all A; new duplication 0.0% (limit 3%); hotspots reviewed 100%.
  • 0 issues and 0 security hotspots.
  • Coverage isn't a gate condition here, and none was computed because there are no new coverable lines.

Nothing needed to pass.

New findings

  • Critical: 0
  • Important: 3
    • update-version.sh fails on the second bump (reproduced).
    • engine-strict is no longer honoured.
    • There are no corepack enable instructions.
  • Suggestion: 3
    • The onlyBuiltDependencies blocks do nothing.
    • The Dockerfile duplicates the pnpm version.
    • A devEngines guard would stop accidental npm install.

Findings that couldn't be anchored to diff lines:

  • Suggestion: some npm/npx references are left over outside the diff hunks:

    • the lint and ci-lint scripts in both package.json files (npm run format && ...);
    • client/scripts/eslint-filter.sh:83;
    • client/README.md (still npm ci / npm run ...);
    • client-v3/e2e/README.md;
    • comments in client-v3/e2e/global-setup.ts:34, client-v3/src/js/yjs/draftWriterFixture.test.ts:19 and server/test/utils/script/test_draft_writer_interop.py:11.

    The scripts still work because npm ships with Node, but mixing package managers is confusing, and npm run skips pnpm's .npmrc/workspace settings.

  • Note: the root CLAUDE.md is gitignored, so this PR can't change it, but the local copy still documents npm ci / npm 11.x throughout. Worth updating locally after merge.

  • Note: Dependabot's npm ecosystem does cover pnpm, but its support for pnpm 12 / lockfile v9 hasn't been verified here. Watch its first run after merge.

Strengths

  • pnpm/action-setup is pinned by SHA and placed before setup-node with cache: pnpm in every workflow. package_json_file keeps the version in one place.
  • The glob override moved correctly to pnpm-workspace.yaml; the lockfile resolves only glob@13.0.6.
  • The allowBuilds entries each come with a comment explaining why the build is allowed.
  • Dockerfile layer order is right: manifest, lockfile and workspace yaml are copied before install, then the source.
  • documentation/development.md now correctly describes the three components and the ui-old/ output path. The old README text wrongly said client builds to server/static/.
  • client-v3's prebuild hook still runs under pnpm 12 (verified).

🤖 Generated with Claude Code

Comment thread scripts/update-version.sh Outdated
Comment thread client/.npmrc Outdated
Comment thread README.md Outdated
Comment thread client/pnpm-workspace.yaml Outdated
Comment thread Dockerfile Outdated
Comment thread client-v3/package.json
Each finding was reproduced before fixing.

- scripts/update-version.sh: pnpm 12's `pnpm version` is not a passthrough
  to `npm version`; it has a clean-working-tree check, so the second bump
  failed (ERR_PNPM_UNCLEAN_WORKING_TREE) and left a half-applied version
  bump. Add --no-git-checks. Verified end to end on a dirty tree.
- Engine enforcement: pnpm 12 silently ignores `engine-strict` in
  .npmrc (an install with an impossible dependency engine succeeded).
  Set `engineStrict: true` in both pnpm-workspace.yaml files, delete both
  .npmrc files and the Dockerfile COPY for client/.npmrc. Verified it now
  rejects Node 24.12.0 for nopt@10.0.1, as npm's engine-strict used to.
- pnpm-workspace.yaml: drop `onlyBuiltDependencies` (a no-op in pnpm 12;
  only `allowBuilds` counts) and set core-js/bootstrap-vue to false
  (their postinstalls only print donation messages).
- Dockerfile: plain `corepack enable`; the version comes from
  `packageManager` only. Built both frontend stages locally and confirmed
  `pnpm --version` is 12.5.1 in each.
- Add devEngines.packageManager (pnpm, onFail: error) to both
  package.json files so `npm install` is refused (EBADDEVENGINES) and
  cannot create a package-lock.json; ignore package-lock.json too. This
  also makes any `npm run` inside a script fail, so convert the
  lint/ci-lint scripts and eslint-filter.sh to pnpm.
- Docs: add `corepack enable` guidance (Node 24 ships corepack switched
  off) to README.md, client/README.md, client-v3/README.md and
  documentation/development.md; convert remaining npm/npx references in
  client/README.md, client-v3/e2e/README.md and code comments.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@github-actions github-actions Bot added server Pull requests changing back end code git labels Sep 25, 2026
@Tim020

Tim020 commented Sep 25, 2026

Copy link
Copy Markdown
Contributor Author

Review round 1: response

All three Important findings and all three Suggestions are fixed in 65d2c7d, and each inline thread has a reply. I reproduced every claim before changing anything (details in the threads); the two that mattered most were real regressions from my original change (pnpm version half-applying a bump, and engine-strict being silently ignored).

Unanchored findings

  • Leftover npm/npx references: all converted.
    • lint / ci-lint scripts in both package.json files now use pnpm run. With the new devEngines guard, npm refuses npm run inside these scripts too, so leaving them would have broken linting rather than just being untidy.
    • client/scripts/eslint-filter.sh, client/README.md, client-v3/e2e/README.md.
    • Comments in client-v3/e2e/global-setup.ts, draftWriterFixture.test.ts and test_draft_writer_interop.py (npx vitest is now pnpm exec vitest).
  • Local CLAUDE.md: updated locally (npm to pnpm throughout, plus a note on corepack enable, devEngines and where pnpm settings live). It's gitignored, so it isn't part of this diff.
  • Dependabot on pnpm 12 / lockfile v9: not verified, agreed. I'll watch its first run after merge rather than guess.

Verification of the fix commit

  • Both projects: frozen install, typecheck, ci-lint (running the new nested pnpm run scripts), unit tests (209 + 95) and build.
  • Full Playwright suite: 222/222 passing.
  • scripts/update-version.sh run end to end on a dirty tree, then reverted.
  • Both Dockerfile frontend stages built locally, each resolving pnpm 12.5.1 from packageManager.

One thing I noticed but did not change: pnpm 12.6.0 is already out, and this PR pins 12.5.1. That seems like a separate bump rather than something to fold into this PR.

🤖 Generated with Claude Code

@Tim020 Tim020 left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Review round 2

Reviewed head: 65d2c7decb55d0e6612282c95d99f4002119f306. The incremental diff is b3fd9bc..65d2c7d, a fast-forward with no rebase.

Verdict: ready to merge. Every round 1 finding is fixed and verified against the code, CI is fully green on 65d2c7d (unit, lint, typecheck, Playwright 222/222 on both browsers, docker-build-and-run, server builds on 3 OSes), and the one new item below is an optional Suggestion.

Previous round follow-up

SonarQube

Quality gate passed. Data came from the anonymous SonarCloud API, and the analysed commit matches 65d2c7d.

  • All 5 conditions pass: new reliability, security and maintainability ratings are all A; new duplication 0.0% (limit 3%); hotspots reviewed 100%.
  • 0 issues and 0 security hotspots.

Nothing needed to pass.

New findings (in the round 2 changes)

  • Critical: 0
  • Important: 0
  • Suggestion: 1. engines.node (>=24.0.0) is lower than the real floor the dependencies need (24.15.0), and with engineStrict that gap now shows up as a confusing transitive-dependency error. This is not a regression; see the inline comment.

Unanchored notes:

  • Dependabot on pnpm 12 / lockfile v9 is still unverified; you've said you'll watch its first run after merge.
  • A pnpm 12.6.0 bump is reasonably left for a separate PR.

Strengths

  • Each fix was reproduced before it was changed and verified afterwards: engine enforcement was confirmed back in the real project, both Docker stages were built locally, and update-version.sh was run end to end on a dirty tree. The replies record that evidence, which made this round quick to verify.
  • The allowBuilds comments now say, for every entry, why it runs or is skipped.
  • documentation/development.md explains why npm install is refused on purpose, which heads off confusion for new contributors.

🤖 Generated with Claude Code

Comment thread client/pnpm-workspace.yaml
Review round 2 suggestion. With engineStrict now working, the declared
`>=24.0.0` was lower than what the install needs: abbrev@5.0.0 and
nopt@10.0.1 (in both lockfiles) require ^24.15.0 on the 24 line, the
highest floor of any dependency.

Note: pnpm 12 does not enforce the root project's own `engines`, even
with engineStrict (checked with an impossible `>=99`), so this is
accurate documentation rather than a friendlier failure; someone on
Node 24.12 still gets the transitive-dependency error.

Also update "Node 24.x" in the READMEs and development docs.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@sonarqubecloud

Copy link
Copy Markdown

@Tim020
Tim020 merged commit 84e6493 into dev Sep 25, 2026
28 checks passed
@Tim020
Tim020 deleted the feature/pnpm-migration branch September 25, 2026 11:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

claude Issues created by Claude client Pull requests changing front end code client-v3 git github GitHub actions related issue or pull request server Pull requests changing back end code xlarge-diff

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant