Skip to content

Run the dev server and Vite with bun run --parallel - #38

Merged
firsttris merged 3 commits into
mainfrom
dev-parallel
Nov 4, 2026
Merged

firsttris merged 3 commits into
mainfrom
dev-parallel

Conversation

@firsttris

Copy link
Copy Markdown
Owner

Replaces the hand-written process supervisor scripts/dev.ts (#37: own process groups, signal forwarding, SIGSTOP/SIGCONT) with Bun's built-in bun run --parallel. The terminal already sends Ctrl+C, Ctrl+Z and hangup to the whole foreground process group.

New scripts in package.json:

  • dev: [ -f .env ] || { cp .env.example .env && echo '.env created from .env.example'; }; printf '\n Windfenster: http://localhost:5173\n\n' && bun run --parallel dev:server dev:web
  • dev:server: cargo run -p windfenster
  • dev:web: bun run --cwd web dev
  • test: bun run --filter @windfenster/web test (no tests left in scripts/)

scripts/dev.ts and scripts/dev.test.ts are deleted; docs/development.md is updated.

Verification

Tested in a real pty (interactive bash, Bun 1.4.4), with a stand-in cargo on PATH and a stand-in web dev script. Each stand-in is a Bun script that starts its own sleep child:

  • both start, with prefixed output; .env is created on the first run and the banner is printed
  • Ctrl+C: nothing is left running in the sandbox
  • Ctrl+Z: every process is T and the shell takes input; fg resumes all of them (S); Ctrl+C then stops them all
  • one script exits with 3: the other script is stopped (Signaled: SIGINT). Only the sleep grandchildren stay behind (the failing script's own, and the other script's own). bun run dev exits with 130, not 3.

bun run lint, bun run typecheck, bun run test and bun run build pass. The real server and real Vite were not started, and mkdocs build --strict was not run locally (mkdocs is not installed).


Generated by Claude Code

`bun run dev` started the Rust server and Vite through scripts/dev.ts, a hand-written supervisor:
own process groups, forwarded signals, SIGSTOP/SIGCONT for Ctrl+Z. That replaces what the terminal
already does: Ctrl+C, Ctrl+Z and hangup go to the whole foreground process group. Bun's built-in
`bun run --parallel` runs the scripts side by side with prefixed output and stops the others when
one fails.

The scripts are now `dev:server` (`cargo run -p windfenster`) and `dev:web` (the web app's
`bun run dev`); `dev` creates `.env` from `.env.example` on the first run, prints the banner and
runs both. scripts/dev.ts and its process tests go; `bun run test` runs the web app's tests.

Checked in a pty with stand-ins for cargo and the web app: Ctrl+C leaves nothing running, Ctrl+Z
stops all and the shell takes input, `fg` resumes, a failing script stops the other.

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: 2 belegte Findings

Geprüft gegen das alte scripts/dev.ts (origin/main), mit Stand-in-cargo und Stand-in-Web-App in einem Sandbox-Repo; Pty-Tests über Python pty.fork(). Die Prüfskripte liegen nicht im Repo (die PR entfernt die Script-Tests bewusst); Inhalt unten.

1. Fehlt .env und kann nicht angelegt werden, startet bun run dev trotzdem beide Prozesse

package.json, Script dev: [ -f .env ] || { cp … && echo …; }; printf … && bun run --parallel … – das ; nach der Gruppe wirft den Fehler von cp weg. Vorher brach copyFileSync in dev.ts mit Exit 1 ab, bevor irgendetwas startete.

Test (t1_env_abort.sh: Sandbox ohne .env und ohne .env.example, erwartet Exit ≠ 0 und keinen gestarteten Prozess):

cp: cannot stat '.env.example': No such file or directory
  Windfenster: http://localhost:5173
dev:server | server-started
dev:web    | web-started
FAIL: exit code 0, expected non-zero
FAIL: server/web started although .env could not be created

Altes dev.ts, gleiche Sandbox: error: script "dev" exited with code 1, nichts gestartet.

Fix: && statt ; nach der Gruppe (a || { b; } && c = (a || b) && c).

2. Unter bun run --parallel sehen cargo und Vite kein Terminal: Farben weg, Vite-Tastenkürzel weg

bun run --parallel leitet stdout der Scripts über eine Pipe (für das Präfix) und gibt ihnen /dev/null als stdin. Das alte dev.ts gab beiden das Terminal (stdio: inherit, Vite auch stdin). Folge: cargo-Fehlermeldungen und Vite-Ausgabe ohne Farben (cargo prüft isatty, Vites picocolors isTTY/FORCE_COLOR), und Vite bindet seine Tastenkürzel (h, r, o, q) nur bei stdin.isTTY – die fehlen jetzt.

Test (t2_color.py: bun run dev in einem Pty, beide Stand-ins melden isTTY und Farb-Variablen):

dev:server | server tty=false FORCE_COLOR=undefined CARGO_TERM_COLOR=undefined
dev:web    | web tty=false FORCE_COLOR=undefined CARGO_TERM_COLOR=undefined
FAIL: Vite sees no terminal and no FORCE_COLOR, its output loses its colours
FAIL: cargo sees no terminal and no CARGO_TERM_COLOR, its errors lose their colours

Fix (ohne eigenen Supervisor): dev setzt FORCE_COLOR=1 und CARGO_TERM_COLOR=always, wenn seine Ausgabe ein Terminal ist ([ -t 1 ]). Die Vite-Tastenkürzel lassen sich mit --parallel nicht zurückholen (stdin ist /dev/null); docs/development.md nennt das und verweist auf bun run dev:web.

Verworfen (Test grün)

  • Aufruf aus Unterordner (crates/x): Bun führt das Script im Repo-Root aus, .env landet dort, cargo läuft im Root, Vite in web/.
  • bun run dev in web/ startet wie vorher nur Vite (eigenes Script der Web-App).
  • Shell: bun run nutzt auf Linux die System-Shell (/usr/bin/bash -c bzw. sh), [ -f ], { …; }, || laufen.
  • Ctrl+C beendet beide samt Kindern, Ctrl+Z hält alle an, die Shell bekommt das Terminal zurück, fg setzt fort (Pty-Probe).
  • Server scheitert (Exit 101): Vite wird beendet, bun run dev endet mit 101.
  • CI-Workflows, Playwright-webServer (bun e2e/app/start.mjs, eigenes Vite), README, CLAUDE.md: kein Bezug auf scripts/dev.ts oder bun test ./scripts mehr.
  • Dass ein Prozess mit Exit 0 den anderen nicht beendet, ist so dokumentiert („when one of them fails“) – kein Finding.

Generated by Claude Code

The `;` after the .env step dropped the exit code of `cp`: without
.env.example (or with a failing copy) the server and Vite started anyway,
where scripts/dev.ts stopped with copyFileSync's error. `&&` chains the
step into the rest, as `(a || b) && c`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y3rdfbWb1xDUdPRng7TX9Z
--parallel pipes each script's output (for the name prefix) and gives it
/dev/null as input, so neither sees a terminal: cargo and Vite (picocolors
checks stdout.isTTY or FORCE_COLOR) dropped their colours, which
scripts/dev.ts kept by passing the terminal on. In a terminal `dev` now
sets FORCE_COLOR and CARGO_TERM_COLOR. Vite's keyboard shortcuts need a
terminal on stdin and stay with `bun run dev:web`; the docs say so.

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

Copy link
Copy Markdown
Owner Author

Fixes gepusht

  • 7018e29 Stop bun run dev when .env cannot be created (Finding 1): t1_env_abort.sh jetzt error: script "dev" exited with code 1 → PASS
  • 5321386 Keep cargo's and Vite's colours under bun run --parallel (Finding 2): t2_color.py jetzt tty=false FORCE_COLOR=1 CARGO_TERM_COLOR=always → PASS; ohne Terminal (Ausgabe in Pipe) bleibt FORCE_COLOR ungesetzt. Mit echtem Vite im Pty kommen die ANSI-Farben wieder an. Docs: Hinweis auf Vite-Tastenkürzel nur mit bun run dev:web.

Checks: bun run lint, typecheck, test, build grün. Rust nicht gebaut (Stand-in-cargo).

t1_env_abort.sh
#!/bin/sh
# usage: t1_env_abort.sh <package.json>  -- .env cannot be created: `bun run dev` must stop before starting anything
set -u
T=$(mktemp -d); mkdir -p "$T/web" "$T/bin"
cp "$1" "$T/package.json"   # no .env.example, no .env
echo '{"name":"@windfenster/web","private":true,"scripts":{"dev":"echo web-started"}}' > "$T/web/package.json"
printf '#!/bin/sh\necho server-started\n' > "$T/bin/cargo"; chmod +x "$T/bin/cargo"
out=$(cd "$T" && PATH="$T/bin:$PATH" bun run dev 2>&1); code=$?
echo "$out" | sed 's/^/  | /'
fail=0
[ "$code" -ne 0 ] || { echo "FAIL: exit code $code, expected non-zero"; fail=1; }
echo "$out" | grep -q 'server-started\|web-started' && { echo "FAIL: server/web started although .env could not be created"; fail=1; }
[ $fail -eq 0 ] && echo PASS
exit $fail
t2_color.py
# usage: python3 -I t2_color.py <package.json>  -- under a terminal, `bun run dev` must keep colours for cargo and Vite
import os, pty, sys, tempfile, json
pkg = sys.argv[1]
T = tempfile.mkdtemp(); os.makedirs(f"{T}/web"); os.makedirs(f"{T}/bin")
open(f"{T}/package.json", "w").write(open(pkg).read()); open(f"{T}/.env", "w").write("")
probe = f"{T}/probe.ts"
open(probe, "w").write("console.log(`${process.argv[2]} tty=${!!process.stdout.isTTY} FORCE_COLOR=${process.env.FORCE_COLOR} CARGO_TERM_COLOR=${process.env.CARGO_TERM_COLOR}`);\n")
json.dump({"name": "@windfenster/web", "private": True, "scripts": {"dev": f"bun {probe} web"}}, open(f"{T}/web/package.json", "w"))
open(f"{T}/bin/cargo", "w").write(f"#!/bin/sh\nexec bun {probe} server\n"); os.chmod(f"{T}/bin/cargo", 0o755)
pid, fd = pty.fork()
if pid == 0:
    os.chdir(T); os.environ["PATH"] = f"{T}/bin:" + os.environ["PATH"]; os.environ.pop("FORCE_COLOR", None); os.environ.pop("CARGO_TERM_COLOR", None)
    os.execvp("bun", ["bun", "run", "dev"])
out = b""
while True:
    try: d = os.read(fd, 4096)
    except OSError: break
    if not d: break
    out += d
lines = [l for l in out.decode(errors="replace").splitlines() if "tty=" in l]
for l in lines: print("  |", l)
web = next((l for l in lines if " web tty" in l), ""); srv = next((l for l in lines if " server tty" in l), "")
ok = True
if not ("tty=true" in web or "FORCE_COLOR=1" in web): print("FAIL: Vite sees no terminal and no FORCE_COLOR, its output loses its colours"); ok = False
if not ("tty=true" in srv or "CARGO_TERM_COLOR=always" in srv): print("FAIL: cargo sees no terminal and no CARGO_TERM_COLOR, its errors lose their colours"); ok = False
print("PASS" if ok else ""); sys.exit(0 if ok else 1)

Generated by Claude Code

Copy link
Copy Markdown
Owner Author

TDD-Review abgeschlossen: 2 belegte Findings gefixt (.env-Fehler bricht ab: 7018e29; Farben unter --parallel: 5321386). CI grün. Merge per Squash.


Generated by Claude Code

@firsttris
firsttris merged commit 0a0cbd2 into main Nov 4, 2026
7 checks passed
@firsttris
firsttris deleted the dev-parallel branch November 4, 2026 07:05
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