Skip to content

Move the web UI from pnpm and Node to Bun - #28

Merged
firsttris merged 2 commits into
mainfrom
bun-migration
Nov 4, 2026
Merged

firsttris merged 2 commits into
mainfrom
bun-migration

Conversation

@firsttris

Copy link
Copy Markdown
Owner

The owner decided on Bun everywhere (tech-stack.md in firsttris/workflows, "Tooling"), so the web UI follows haul and windfenster and the CLAUDE.md deviation "no Bun, pnpm only for the web UI's build and tests" goes.

Changes

  • web/package.json: packageManager: bun@1.4.4; e2e, e2e:stack and screenshots run the Playwright runner on Bun (bun --bun playwright test); the package already has "type": "module". The web servers of both Playwright configs are the Rust examples, no Vite.
  • web/bun.lock converted from pnpm-lock.yaml by bun install: the same 355 package versions (compared name@version for name@version), pnpm-lock.yaml removed.
  • web/bunfig.toml: linker = "isolated", as pnpm installed before; project.inlang loads its plugin from node_modules/@inlang/plugin-message-format, a direct dependency, which the isolated linker links (Paraglide compiles all messages). minimumReleaseAge of one day, pnpm 12's default, as in haul. bun pm untrusted finds no package with install scripts, so no trustedDependencies (pnpm had no onlyBuiltDependencies either).
  • CI and screenshots: oven-sh/setup-bun@v2 with bun-version-file: web/package.json and bun install --frozen-lockfile instead of pnpm/action-setup and setup-node; in the Playwright image Bun of packageManager from npm (the image has no jq or unzip); the shared playwright-image action reads web/bun.lock. The path filters name no lockfile and stay.
  • Dockerfile: the web stage on oven/bun:1.4.4-slim, still --platform=$BUILDPLATFORM.
  • Renovate: a Bun group (the image's oven/bun and packageManager in web/package.json) instead of the Node group and its node-version managers; the @types/node minor/patch rule only carved the types out of the Node group and duplicated the shared default, so it is gone.
  • CLAUDE.md (deviation removed, Checks with bun run), README, docs/development.md and comments: bun install, bun run ….

Checks (local, Bun 1.4.4)

  • bun install --frozen-lockfile: 250 packages, lockfile unchanged
  • bun run lint: 65 files, no findings
  • bun run typecheck: green
  • bun run test: 5 files, 12 tests passed
  • bun run build: green; also without Node on PATH, as in the oven/bun stage
  • bun run e2e against the demo server (cargo build -p spielstand-server --example demo): 21 passed
  • --list on Bun and on Node (same node_modules): e2e 21 tests in 3 files, screenshots 8 in 1, stack 5 in 1, the same on both

Not run locally: bun run e2e:stack and bun run screenshots (listed only), the Docker image build (no Docker daemon here), the CI workflows themselves and the Renovate config validator.


Generated by Claude Code

The owner decided on Bun everywhere, as tech-stack.md in firsttris/workflows asks and as haul and
windfenster already did, so the deviation "no Bun, pnpm only for the web UI" goes from CLAUDE.md.

- web/package.json: packageManager bun@1.4.4; the Playwright runner on Bun
  (`bun --bun playwright test` for e2e, e2e:stack and screenshots; the package is already
  "type": "module")
- web/bun.lock converted from pnpm-lock.yaml by `bun install`, with the same 355 package
  versions; pnpm-lock.yaml removed
- web/bunfig.toml: the isolated linker, as pnpm installed before (project.inlang loads its plugin
  from node_modules/@inlang/plugin-message-format, a direct dependency), and minimumReleaseAge of
  one day, pnpm 12's default; no package has install scripts, so no trustedDependencies
- CI and screenshots: oven-sh/setup-bun with bun-version-file instead of pnpm/action-setup and
  setup-node; in the Playwright image Bun of packageManager from npm; the playwright-image action
  reads web/bun.lock
- Dockerfile: the web stage on oven/bun:1.4.4-slim, still on $BUILDPLATFORM
- Renovate: a Bun group (image and packageManager) instead of the Node group and node-version
  managers
- CLAUDE.md, README and docs/development.md: bun install, bun run

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y3rdfbWb1xDUdPRng7TX9Z

Copy link
Copy Markdown
Owner Author

TDD review: one finding

1. Screenshots workflow installs the latest Bun, not the one of packageManager

File: .github/workflows/screenshots.yml, line 63 (job screenshots)

- run: npm install --global "bun@$(node -p "require('./package.json').packageManager.split('@')[1]")"

The step has no working-directory: web and the job has no defaults.run.working-directory (only the next step sets it), so it runs in the repository root, which has no package.json. node -p fails with Cannot find module './package.json', the command substitution yields an empty string, and the step goes on with npm install --global "bun@", which is the registry's latest, with exit code 0. The same line in ci.yml (job e2e) works because that job sets defaults.run.working-directory: web.

Today latest happens to be 1.4.4, so the job is green. But after every Bun release, Renovate waits three days (minimumReleaseAge) before it raises the pin. For those days, and longer whenever a Bun PR is held back, the screenshots are taken with a different Bun than bun.lock and CI were tested with.

Test (scratch script, not committed): it takes every workflow step that runs npm install --global, runs it with bash -e in the directory GitHub would use (the step's or the job's working-directory, else the repository root) with a private npm prefix, and compares bun --version with packageManager. A third argument sets a pin other than latest in a copy of the repository, which is the state during the days Renovate waits.

$ python3 tests/bun_in_image.py wt tmp 1.4.3
ok   ci.yml e2e: exit 0, bun 1.4.3, want 1.4.3
FAIL screenshots.yml screenshots: exit 0, bun 1.4.4, want 1.4.3
  node:internal/modules/cjs/loader:1433
EXIT 1

Fix: add working-directory: web to that step, the same as the step after it.

Checked and found fine (no failing test)

  • Docker web stage (oven/bun:1.4.4-slim, no cache mount): Docker Hub has the tag for amd64 and arm64, so --platform=$BUILDPLATFORM also works on the native ARM runner. There is no Docker daemon here, so I reproduced the stage without Docker: only package.json, bun.lock and bunfig.toml copied, bun install --frozen-lockfile with an empty HOME, then the context minus .dockerignore and bun run build with no node on PATH. Both passed.
  • Renovate: renovate-config-validator passes. A local renovate --platform=local lookup extracts bun 1.4.4 from web/package.json (regex manager, npm datasource) and oven/bun 1.4.4-slim from the Dockerfile. The npm manager skips packageManager (unknown-engines), so there is no duplicate.
  • firsttris/workflows/actions/playwright-image with lockfile: web/bun.lock reads 1.64.1.
  • bump-version.yml with extra-files: web/package.json (its Python run on a copy): it changes Cargo.toml, Cargo.lock and web/package.json, and bun install --frozen-lockfile passes afterwards. The bun.lock root entry carries no version.
  • minimumReleaseAge = 86400 (seconds) is not above Renovate's 3 days or the 1 day for security fixes. A frozen install of the current lockfile passes.
  • setup-bun bun-version-file: web/package.json reads packageManager (src/utils.ts). The e2e job's npm install gives the pinned Bun.
  • bun run e2e (bun --bun playwright test) against the demo: 21 passed. e2e:stack fails here at chain.spec.ts:7 in the same way under the Node runner, so that comes from this environment, not from Bun.
  • No pnpm/npx/node-version left in the scripts, install/ or the workflows.

Generated by Claude Code

The step that installs Bun with npm ran in the repository root, which has
no package.json: `node -p` failed, the substitution was empty and
`npm install --global "bun@"` took the registry's latest with exit code 0.
While Renovate waits three days after a Bun release, the screenshots
would run on a different Bun than bun.lock and CI. The step now runs in
web/, as the next one does and the e2e job of ci.yml does through its
defaults.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y3rdfbWb1xDUdPRng7TX9Z

Copy link
Copy Markdown
Owner Author

Fix for finding 1 pushed as 8c97829 (working-directory: web on the npm step in screenshots.yml). The test is green now, both for the current pin and for a pin below latest (1.4.3). In web/, lint, typecheck, test (12 passed) and build are green.


Generated by Claude Code

Copy link
Copy Markdown
Owner Author

TDD-Review abgeschlossen: 1 belegtes Finding (Screenshots-Job installierte das neueste statt des gepinnten Bun) gefixt in 8c97829. CI grün: web, rust, playwright-image, e2e, docker, build. Merge per Squash.


Generated by Claude Code

@firsttris
firsttris merged commit e2b9c31 into main Nov 4, 2026
7 checks passed
@firsttris
firsttris deleted the bun-migration branch November 4, 2026 08:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants