Skip to content

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 modeBun 1.3.14pnpm
Override an npm-aliased dep (the h3-next alias behind the @nuxt/content prerender crash — T-CTJF)Silently ignores alias overridesoverrides targets aliases + nested selectors
Wrong dependency copy hoisted into a bundleFlat/hoisted; hit the h3 v2 pickupIsolated/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 500Isolated 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 failuresRun on Node; prebuilt binaries, no node:sqlite workaround

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.

Not yet planned — this is the backlog capture. When promoted, shape it as:

  1. A decision doc recording the pnpm choice + rationale and superseding D-0016 / D-0018 (which standardized Bun for tauri apps + sites).
  2. A read-only scoping pass that inventories every Bun touchpoint (below).
  3. 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).
  4. 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/ui ships 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:

LayerTouchpoints
Packageroot workspacespnpm-workspace.yaml; every bun.lock (root + standalone apps) → pnpm-lock.yaml; trustedDependenciesonlyBuiltDependencies / .npmrc; engines
Buildevery moon.yml task shelling bun run / bun install
SDLC substrateapps/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 / conventionsthe release workflow; D-0016 / D-0018
  • AC-1: A decision doc supersedes D-0016 / D-0018 with 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.)

  • Executing the migration — this task is the capture + planning trigger only.
  • none — but interacts with T-CTJF (the h3 workaround becomes removable under pnpm).
  • 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-sqlite3 native builds (worked around with node:sqlite), and @types/node phantom-hoist clashes. An alternative worth weighing before a full flip: keep Bun and make only the heavy Nuxt apps standalone pnpm workspaces (trading workspace:* linking for file: / published deps).
  • The SDF desktop dashboard (sdf-nuxt) hit the same class on 2026-07-30: its nuxt: ^4.4.5 pin floats to 4.5.0, which moved to unhead v3, 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’s injectHead().hooks.hookOnce binds the v3 head and sdf:dev 500s (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/ui unhead-v3 support. This migration is the structural prevention.

← Back to Tasks