T-UWRG-migrate-monorepo-bun-to-pnpm
Status: planning/backlog · Impact: high · Complexity: large
Move the monorepo’s package manager from Bun to pnpm. Owner decision 2026-07-19:
pnpm is more mature for the failure modes the augmented_books adoption surfaced.
This is a milestone-sized effort captured at planning/backlog — it needs a
decision doc and a sequenced plan before pickup, not ad-hoc execution.
The whole monorepo is on Bun: a root Bun workspace (package.json workspaces +
bun.lock), standalone Bun workspaces for tauri apps and sites (D-0016 /
D-0018), moon tasks that shell bun run / bun install, and an SDLC
substrate whose CLI is Bun-run and whose git tooling is Bun-specific.
Why pnpm (the concrete wins over Bun 1.3.14, from the adoption):
| Failure mode | Bun 1.3.14 | pnpm |
|---|---|---|
Override an npm-aliased dep (the h3-next alias behind the @nuxt/content prerender crash — T-CTJF) | Silently ignores alias overrides | overrides targets aliases + nested selectors |
| Wrong dependency copy hoisted into a bundle | Flat/hoisted; hit the h3 v2 pickup | Isolated/symlinked node_modules prevents it structurally |
A ^-pinned Nuxt floating to a minor that bumps a shared transitive’s major (nuxt ^4.4.5 → 4.5.0 → unhead v3, while @nuxt/ui still needs v2) | Flat store keeps unhead v2 and v3; @nuxt/ui’s injectHead() binds the v3 head → sdf:dev 500 | Isolated node_modules scopes each consumer’s unhead; strict peers surface the v2/v3 conflict at install |
Native modules (better-sqlite3) | Can’t load at runtime; trust triggers node-gyp CI failures | Run on Node; prebuilt binaries, no node:sqlite workaround |
Proposed
Section titled “Proposed”The monorepo installs and builds with pnpm; the Nuxt/nitro ecosystem friction
(h3 alias override, native modules, phantom hoisting) is gone; the per-app
workarounds added during the adoption (learn_web’s nitro.prerender.ignore,
the node:sqlite connectors) can be revisited.
Approach
Section titled “Approach”Not yet planned — this is the backlog capture. When promoted, shape it as:
- A decision doc recording the pnpm choice + rationale and superseding
D-0016/D-0018(which standardized Bun for tauri apps + sites). - A read-only scoping pass that inventories every Bun touchpoint (below).
- A milestone sequencing the work: leaf packages/apps first, the SDLC substrate + git tooling + CI last (they run task-work itself, so they are load-bearing and migrate under their own feet).
- A Nuxt version alignment: converge every app on one latest mutually
working nuxt/@nuxt/ui set and pin it. As of 2026-07-30 that’s nuxt 4.4.x
(4.4.8) + @nuxt/ui 4.10.0 (both on unhead v2) — nuxt 4.5.x (unhead v3) is
blocked fleet-wide until
@nuxt/uiships an unhead-v3 release, since its latest (4.10.0) still declares@unhead/vue ^2. This alignment is separable from the package-manager migration and could ship sooner; the migration then keeps the alignment honest (isolated store surfaces any re-drift at install).
Surface to migrate:
| Layer | Touchpoints |
|---|---|
| Package | root workspaces → pnpm-workspace.yaml; every bun.lock (root + standalone apps) → pnpm-lock.yaml; trustedDependencies → onlyBuiltDependencies / .npmrc; engines |
| Build | every moon.yml task shelling bun run / bun install |
| SDLC substrate | apps/sdlc CLI (Bun-run); quality_checks (bunx tsc, bun test); git tooling (arm-worktree’s bun install --frozen-lockfile --filter, worktree_init, commit_worktree_init) |
| CI / conventions | the release workflow; D-0016 / D-0018 |
Acceptance criteria
Section titled “Acceptance criteria”- AC-1: A decision doc supersedes
D-0016/D-0018with the pnpm choice and rationale. - AC-2: The full Bun-touchpoint inventory + sequenced migration plan exists (milestone).
(Real ACs are defined when this is promoted out of backlog.)
Out of scope
Section titled “Out of scope”- Executing the migration — this task is the capture + planning trigger only.
Dependencies
Section titled “Dependencies”- none — but interacts with
T-CTJF(the h3 workaround becomes removable under pnpm).
Discovery context
Section titled “Discovery context”- The augmented_books adoption (Wave 1–2, 2026-07-19) repeatedly hit
Bun-vs-Nuxt-ecosystem friction: the h3 v1/v2 alias-override wall (
T-CTJF),better-sqlite3native builds (worked around withnode:sqlite), and@types/nodephantom-hoist clashes. An alternative worth weighing before a full flip: keep Bun and make only the heavy Nuxt apps standalone pnpm workspaces (tradingworkspace:*linking forfile:/ published deps). - The SDF desktop dashboard (
sdf-nuxt) hit the same class on 2026-07-30: itsnuxt: ^4.4.5pin floats to 4.5.0, which moved tounheadv3, while@nuxt/ui(4.9.0/4.10.0) still declares@unhead/vue ^2. Bun’s flat store keeps both unhead majors, so@nuxt/ui’sinjectHead().hooks.hookOncebinds the v3 head andsdf:dev500s (injectHead().hooks.hookOnce is not a function). Direct fix: converge on the latest working set (nuxt 4.4.x + @nuxt/ui 4.10.0) fleet-wide — nuxt 4.5.x waits on@nuxt/uiunhead-v3 support. This migration is the structural prevention.