You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The owner has decided to adopt Bun and has reaffirmed it after being warned that it reverses this repo's own Critical Do-Not ("Do not use npm, npx, or bun in this repo — pnpm only") and their personal global preference file, which currently carves this repo out as pnpm. That reversal is settled; this task plans how to land it safely, not whether to land it. The real engineering content is that "adopt Bun" is three different changes with three very different risk profiles, and the owner's brief ("Bun runtime", listed under "workspace tooling") does not say which is meant:
Scope
What changes
Risk
S1 — package manager + script runner
bun install / bun run, bun.lock, workspaces move into root package.json, allowBuilds → trustedDependencies
Low. Node still executes everything
S2 — backend runtime
bun dist/server.js (or bun src/server.ts) replaces node dist/server.js and tsx
Depends on:#8, #15, 20, 21 — and notservices/pdf-service, which stays on npm (see below)
Checklist
Record the exact baseline first, on main: pnpm install wall-clock from a cold store, pnpm build wall-clock, and the exact passing test counts for backend and frontend. Every later comparison is against these numbers, not against memory
On branch spike/bun, convert the workspace: move packages: from pnpm-workspace.yaml into root package.json"workspaces": ["shared", "backend", "frontend"] (keeping services/pdf-service out, exactly as today), transcribe allowBuilds into "trustedDependencies"in full (a partial list silently disables the default allowlist), delete .npmrc, and replace "packageManager": "pnpm@…" with a .bun-version file — leave engines.node: 24.x alone, Node still runs the toolchain
bun install, then confirm bun.lock is text and committed, and that bun install --frozen-lockfile on a clean clone reproduces the tree without rewriting the lockfile
Rewrite the eleven root scripts from pnpm --filter X to the Bun equivalents. Consider dropping the concurrently dependency in favour of bun run --filter '*' dev, but only if it actually reproduces the current three-watcher behaviour
Run the four gates under Bun with everything still executing on Node: bun run typecheck, bun run lint, bun run test, bun run build. Test counts must match the Phase A baseline exactly — a drop is a silently skipped suite, not a win
Exercise the two native/foreign-toolchain paths explicitly, because they are the ones that break: bunx --bun prisma generate (note whether the npm-alongside-Bun requirement bites; if it does, document that the Prisma CLI runs through Node and move on — it is not a blocker for S1), and a sharp code path (run the RGB-conversion-touching backend tests, or resolve one location locally) to prove the N-API addon loads under a Bun-installed tree
Record install and build wall-clock under Bun and write both numbers into the issue. If the win is not material, that is worth knowing before the deploy work
With task 21's container in place, rebuild the image with Bun as the installer and deploy it to a Heroku staging app, not production. The lockfile swap does not merge until a real deploy of that image has served GET /api/health
If the container route is not chosen, evaluate a community Bun buildpack (jmlow/heroku-buildpack-bun, kolide/heroku-buildpack-bun, or the Fir CNB) as the cheap escape hatch — and record the trade-off honestly: an unmaintained third-party buildpack on the only live production host is a worse dependency than a Dockerfile the project controls
Update .github/workflows/ci-cd.yml: pnpm/action-setup → oven-sh/setup-bun with bun-version-file, cache-dependency-path → bun.lock, and every pnpm … step to its Bun form. Keep task 16's four gates (build, typecheck, lint, test) intact — this task must not quietly reduce CI coverage
Fix the services/pdf-service install leg while in this file. Its committed lockfile is package-lock.json, so task 16's planned pnpm --dir services/pdf-service install --frozen-lockfile cannot work as written — --frozen-lockfile wants a pnpm-lock.yaml that does not exist. Use npm ci in that directory, which matches what Vercel actually does for that project. This is a pre-existing bug in task 16, not something Bun introduces — flag it there too
Update README.md and RUNBOOK.md end to end: prerequisite list, install command, every pnpm invocation, and the CI/deploy description
Only after S1 is merged and stable: on a second spike branch, switch the backend start to Bun and run the backend suite under bun --bun. Keep it in its own commit so it can be reverted independently of S1
Evaluate whether engineType = "client" + runtime = "bun" + a driver adapter is required. If it is, stop and re-plan — that change belongs beside task 5's Neon datasource decision, not bolted onto a runtime swap, and it is a Prisma-behaviour change, not a tooling change
Abort conditions (any one is sufficient to ship S1 only and record why):sharp fails to load or produces different output; Prisma needs schema changes that contradict task 5's settled Neon configuration; any currently-passing backend test fails under bun --bun; idle RSS regresses (Render free is 512 MB); the Solar API pipeline wall-clock regresses
Whatever the outcome, write one line into docs/decisions.md at Gate 2 recording the adopted scope and the reason — this reverses a Critical Do-Not and must be legible to the next planning session
Tag the last pnpm commit pre-bun before merging, so restore is one command. Do not keep both lockfiles in the tree — two lockfiles drift and confuse both CI and any buildpack
Name the cheap-reversal points explicitly in the issue: (1) spike branch — free; (2) after S1 merge, before the deploy switch — git checkout pre-bun -- package.json pnpm-workspace.yaml .npmrc pnpm-lock.yaml && rm bun.lock && pnpm install --frozen-lockfile, one revert commit; (3) after the deploy/stack switch — reversal now also means reverting the Heroku stack and redeploying, so this is where rollback stops being cheap; (4) after S2 — cheap again only if the runtime switch stayed in its own commit
verify: bun install --frozen-lockfile on a fresh clone, then bun run build && bun run typecheck && bun run lint && bun run test all green, with test counts identical to the Phase A baseline
verify: the staging deploy serves GET /api/health, and a cached project loads end-to-end (workbench opens, a panel drag recomputes flux) with zero outbound calls to solar.googleapis.com — the project's core cost constraint must survive a toolchain change
verify: git grep -nE '\bpnpm\b' returns nothing in README.md, RUNBOOK.md, Procfile/heroku.yml, Dockerfile, or .github/workflows/ci-cd.yml
verify: the rollback is actually rehearsed on a scratch branch — check out the pre-bun files, pnpm install --frozen-lockfile, pnpm build succeeds. A documented rollback that has never been run is not a rollback
Priority: Medium · Phase 12 task 18 of the maintainer’s migration plan.
The owner has decided to adopt Bun and has reaffirmed it after being warned that it reverses this repo's own Critical Do-Not ("Do not use
npm,npx, orbunin this repo — pnpm only") and their personal global preference file, which currently carves this repo out as pnpm. That reversal is settled; this task plans how to land it safely, not whether to land it. The real engineering content is that "adopt Bun" is three different changes with three very different risk profiles, and the owner's brief ("Bun runtime", listed under "workspace tooling") does not say which is meant:bun install/bun run,bun.lock, workspaces move into rootpackage.json,allowBuilds→trustedDependenciesbun dist/server.js(orbun src/server.ts) replacesnode dist/server.jsandtsxsharpN-API addon, deploy targetbun testreplaces Vitest across ~260 testsScope:
package.json(root,backend/,frontend/,shared/),pnpm-workspace.yaml(delete),pnpm-lock.yaml(delete),bun.lock(new),.npmrc(delete),.bun-version(new),bunfig.toml(new, only if needed),Procfile/heroku.yml,Dockerfile(from task 21),.github/workflows/ci-cd.yml,README.md,RUNBOOK.mdDepends on: #8, #15, 20, 21 — and not
services/pdf-service, which stays on npm (see below)Checklist
main:pnpm installwall-clock from a cold store,pnpm buildwall-clock, and the exact passing test counts for backend and frontend. Every later comparison is against these numbers, not against memoryspike/bun, convert the workspace: movepackages:frompnpm-workspace.yamlinto rootpackage.json"workspaces": ["shared", "backend", "frontend"](keepingservices/pdf-serviceout, exactly as today), transcribeallowBuildsinto"trustedDependencies"in full (a partial list silently disables the default allowlist), delete.npmrc, and replace"packageManager": "pnpm@…"with a.bun-versionfile — leaveengines.node: 24.xalone, Node still runs the toolchainbun install, then confirmbun.lockis text and committed, and thatbun install --frozen-lockfileon a clean clone reproduces the tree without rewriting the lockfilepnpm --filter Xto the Bun equivalents. Consider dropping theconcurrentlydependency in favour ofbun run --filter '*' dev, but only if it actually reproduces the current three-watcher behaviourbun run typecheck,bun run lint,bun run test,bun run build. Test counts must match the Phase A baseline exactly — a drop is a silently skipped suite, not a winbunx --bun prisma generate(note whether the npm-alongside-Bun requirement bites; if it does, document that the Prisma CLI runs through Node and move on — it is not a blocker for S1), and asharpcode path (run the RGB-conversion-touching backend tests, or resolve one location locally) to prove the N-API addon loads under a Bun-installed treeGET /api/healthjmlow/heroku-buildpack-bun,kolide/heroku-buildpack-bun, or the Fir CNB) as the cheap escape hatch — and record the trade-off honestly: an unmaintained third-party buildpack on the only live production host is a worse dependency than a Dockerfile the project controls.github/workflows/ci-cd.yml:pnpm/action-setup→oven-sh/setup-bunwithbun-version-file,cache-dependency-path→bun.lock, and everypnpm …step to its Bun form. Keep task 16's four gates (build, typecheck, lint, test) intact — this task must not quietly reduce CI coverageservices/pdf-serviceinstall leg while in this file. Its committed lockfile ispackage-lock.json, so task 16's plannedpnpm --dir services/pdf-service install --frozen-lockfilecannot work as written —--frozen-lockfilewants apnpm-lock.yamlthat does not exist. Usenpm ciin that directory, which matches what Vercel actually does for that project. This is a pre-existing bug in task 16, not something Bun introduces — flag it there tooREADME.mdandRUNBOOK.mdend to end: prerequisite list, install command, everypnpminvocation, and the CI/deploy descriptionbun --bun. Keep it in its own commit so it can be reverted independently of S1engineType = "client"+runtime = "bun"+ a driver adapter is required. If it is, stop and re-plan — that change belongs beside task 5's Neon datasource decision, not bolted onto a runtime swap, and it is a Prisma-behaviour change, not a tooling changesharpfails to load or produces different output; Prisma needs schema changes that contradict task 5's settled Neon configuration; any currently-passing backend test fails underbun --bun; idle RSS regresses (Render free is 512 MB); the Solar API pipeline wall-clock regressesdocs/decisions.mdat Gate 2 recording the adopted scope and the reason — this reverses a Critical Do-Not and must be legible to the next planning sessionpre-bunbefore merging, so restore is one command. Do not keep both lockfiles in the tree — two lockfiles drift and confuse both CI and any buildpackgit checkout pre-bun -- package.json pnpm-workspace.yaml .npmrc pnpm-lock.yaml && rm bun.lock && pnpm install --frozen-lockfile, one revert commit; (3) after the deploy/stack switch — reversal now also means reverting the Heroku stack and redeploying, so this is where rollback stops being cheap; (4) after S2 — cheap again only if the runtime switch stayed in its own commitbun install --frozen-lockfileon a fresh clone, thenbun run build && bun run typecheck && bun run lint && bun run testall green, with test counts identical to the Phase A baselineGET /api/health, and a cached project loads end-to-end (workbench opens, a panel drag recomputes flux) with zero outbound calls tosolar.googleapis.com— the project's core cost constraint must survive a toolchain changegit grep -nE '\bpnpm\b'returns nothing inREADME.md,RUNBOOK.md,Procfile/heroku.yml,Dockerfile, or.github/workflows/ci-cd.ymlpre-bunfiles,pnpm install --frozen-lockfile,pnpm buildsucceeds. A documented rollback that has never been run is not a rollbackPriority: Medium · Phase 12 task 18 of the maintainer’s migration plan.