chore: move the workspace from bun to pnpm and upgrade Nx to 23 - #45
Conversation
bun was not buying us anything here, so the package manager is now pnpm
11.26.0, pinned via `packageManager` and installed through Corepack.
The lockfile could not be carried over. `pnpm import` only preserves
direct dependency resolutions, so regenerating re-resolved every caret
range to today's newest match -- 494 packages moved. Two of those bumps
broke the workspace and are pinned to what bun.lock held:
- better-auth 1.6.22. 1.7.x drops the `verifyAccessToken` export that
mcp-bearer.guard.ts imports -- the same breakage a6a925a fixed.
- oxlint 1.71.0. 1.83 adds React rules that fail on 25 pre-existing
violations in web, ui-kit and meeting-bot.
pnpm settings live in pnpm-workspace.yaml, replacing .npmrc's
legacy-peer-deps. `nodeLinker: hoisted` keeps bun's flat layout, which
apps/meeting-bot and apps/backend-e2e depend on -- neither is a
workspace member, so both eat from the root install.
Internal @zuko/* deps now use the `workspace:*` protocol. That is the
pnpm convention, and it lets @nx/js:prune-lockfile rewrite them to
`file:` paths itself, so the fix-lockfile-symlinks sed workaround is
gone.
Nx 23.2.1 comes along because Nx 22 cannot parse a pnpm 12 lockfile.
`nx migrate` also wanted TypeScript 6, Vite 8, @vitejs/plugin-react 6
and webpack-cli 7; Nx 23 requires none of them, so those ranges are
unchanged and left for their own upgrade. Of 41 migrations, 33 applied;
the six skipped were TypeScript 6 and Vite 8 preparation, an AI-prompt
migration for a React 19 upgrade already done, and the interactive
analytics prompt this workspace opts out of. The Vitest 4 migration
found one real hit: the harness CI gate used poolOptions.forks
min/maxForks, which Vitest 4 flattened to a top-level maxWorkers, so
that serialization had stopped taking effect.
Nx 23's prune-lockfile emits a dist/pnpm-workspace.yaml carrying the
workspace's build-script approvals, so the backend image inherits those
instead of allowing every build script. It is declared in the target's
outputs, otherwise a cache replay ships a dist with no approvals and
`prisma generate` fails in the image.
Verified: clean install, --frozen-lockfile, nx affected, typecheck,
build, lint, knip across 11 projects, fmt:check, 857 unit tests, the
prune pipeline fresh and cache-replayed, and the backend image path
simulated end to end (install, prisma generate, Nest boots). The
Playwright e2e suite was not run locally.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
setupSprite clones the repo into the sprite and ran `bun install`. After the pnpm migration that clone carries a pnpm-lock.yaml and a `packageManager: pnpm` field and no bun.lock, so the next provision would have installed from nothing deterministic -- a path no build or test in this repo exercises, since it only runs against a live sprite. Corepack resolves pnpm from the cloned repo's `packageManager` field, so nothing needs baking into the sprite image. `corepack enable` symlinks into Node's bin directory, which an unprivileged sprite user cannot write, hence the user-local fallback. Each exec is its own shell, so startServer prepends the same prelude. The install is now `--frozen-lockfile`: a fresh clone of a committed lockfile should fail loudly on drift rather than silently resolve something new. @langchain/langgraph-cli is not a workspace dependency, so `bunx` was fetching it on demand -- that is `pnpm dlx`, not `pnpm exec`. Also converts the two app READMEs the earlier docs pass missed (apps/harness, apps/web-e2e), and rewords the shiki/Turbopack note in next.config.ts, which blamed bun's node_modules layout for a workaround that is still needed under pnpm's hoisted layout. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Follow-up in cffa850 — two misses in the first pass, both real:
Also reworded the shiki/Turbopack note in |
Switches the workspace from bun to pnpm, and upgrades Nx along with it.
Package manager
pnpm 11.26.0, pinned via
packageManagerand installed through Corepack..npmrcis gone; pnpm settings now live inpnpm-workspace.yaml, each with a comment explaining why:nodeLinker: hoistedapps/meeting-botandapps/backend-e2eare not workspace members and resolve from the root install.autoInstallPeers: false+strictPeerDependencies: falselegacy-peer-deps=true. Auto-installing peers also pulled@better-auth/core1.7.x alongside 1.6.22 and blew up resolution.verifyDepsBeforeRun: falsepnpm exec nx …shells out to a full install.allowBuilds@scarf/scarfis denied on purpose — analytics, not a build.overridesoverrides+ a duplicateresolutionsinpackage.json.Internal
@zuko/*deps now useworkspace:*. Besides being the pnpm convention,@nx/js:prune-lockfilerewrites that protocol tofile:paths itself — so thefix-lockfile-symlinkssed workaround is deleted.The bun lockfile could not be carried over.
pnpm importonly preserves direct dependency resolutions; regenerating re-resolved every caret range to today's newest match. 494 packages moved. Two broke the workspace and are pinned back to whatbun.lockheld:better-auth→1.6.22(was^1.6.22). 1.7.x drops theverifyAccessTokenexportmcp-bearer.guard.tsimports — the same breakage a6a925a fixed.oxlint→1.71.0(was^1.62.0). 1.83 adds React rules that fail on 25 pre-existing violations inweb,ui-kitandmeeting-bot.Both are one-line reverts when you want those upgrades. The other 492 moves are untested beyond the checks below.
Nx 22.4.3 → 23.2.1
Nx 22 cannot parse a pnpm 12 lockfile, which is what capped us at pnpm 11.
nx migratealso wanted TypeScript 6, Vite 8,@vitejs/plugin-react6 and webpack-cli 7. Nx 23 requires none of them (vite^5–^8, webpack-cli^5–^7, no TS peer), so those ranges are unchanged and left for their own upgrade.33 of 41 migrations applied. Six skipped as inapplicable: two TypeScript 6 tsconfig preps, the Vite 8
rollupOptions→rolldownOptionsrename, two "write an AI prompt file" migrations for Vite 8 and a React 18→19 upgrade already done, and the interactive analytics prompt (nx.jsonsetsneverConnectToCloud: true).The Vitest 4 migration found one real hit:
apps/harness/vitest.config.mtsusedpoolOptions.forks.{min,max}Forks, which Vitest 4 flattened to a top-levelmaxWorkers. Vitest 4 was already installed, so that CI serialization gate had silently stopped taking effect.Backend image
Nx 23's
prune-lockfileemits adist/pnpm-workspace.yamlcarrying the workspace's build-script approvals, so the image inherits exactly those rather than allowing every build script. It's declared in the target'soutputs— Nx's own migration skipped that because our outputs list was hand-edited, and without it a cache replay ships a dist with no approvals andprisma generatefails in the image.Verification
Clean install,
--frozen-lockfile,nx affected, typecheck, build, lint, knip across all 11 projects,fmt:check, 857 unit tests, the prune pipeline both fresh and cache-replayed, and the backend image path simulated end to end — install →prisma generate→ Nest boots.Not run: the Playwright e2e suite (needs a chromium download and the full stack).
Follow-ups, deliberately not in this PR
@nx/vitest:testis deprecated and removed in Nx 24;nx g @nx/vitest:convert-to-inferredrewrites every project's test target and risks dropping per-project options like the harness'sparallelism: false.@berenddeboer/nx-knip@1.1.0declares@nx/devkitpeers of^20 || ^21 || ^22. Knip runs fine on 23, but it's unsupported until they widen the range.apps/meeting-botandapps/backend-e2eto the workspace would letnodeLinker: hoistedgo and restore pnpm's phantom-dependency protection.🤖 Generated with Claude Code