Public: anyone can read this; the crew can change it.

forked from trevato/cs

No generated file lives in git: the bundles are built at boot into FORGE_APP_DATA, and static/ leaves the repository #247

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

Pushed. Final state: `bun run test` (typecheck + 1113 tests) passes, `static/` is gone from git, and a cold boot builds and serves.

shipped production is serving it deploy ae07c2d

opened this

Pushed. Final state: bun run test (typecheck + 1113 tests) passes, static/ is gone from git, and a cold boot builds and serves.

---

static/ leaves the repository and the bundles are generated where generated files belong. src/bundles.ts is the new module — one concept, where a deployment's built files go (FORGE_APP_DATA/static) and what a failed build says — and src/server.ts calls it with a top-level await before Bun.serve binds, then resolves the scripts, the assets, the held-and-gzipped map and /example's measured sizes from that directory. A build that cannot write throws naming the directory and the process stops on it, exactly as a missing bundle did before (#198); findScripts keeps its refusal underneath, now unreachable on purpose.

Nothing a browser sees changed: same content-addressed names, public, max-age=31536000, immutable, the #236 gzip weighed once at start-up, the same content types, the same <link rel="preload"> in the head. The sizes are identical before and after, because the same tree builds the same files — app 95,598 raw / 37,788 gzip-9 (ceiling 98,000 / 39,000), range 34,810 / 14,569 (37,000 / 15,500), example 21,115 / 9,189 (23,500 / 10,200), the weights asset 11,120 / 8,251 (12,300 / 9,100). Note that app.ea37d3064a.js is what this tree builds and none of the three app.*.js main carries is; after this there is nothing to be stale.

Boot, measured on this box, process start to first response over HTTP, cold: 103 / 103 / 109 ms before, 139 / 140 / 132 ms after into an empty data directory and 126 / 137 / 128 ms into a populated one. The in-process build is 26–60 ms of that (the boot log prints it: 4 files built into …/static, 158.8 kB raw, in 26.1 ms). Building on first request with a lock was the alternative and is not worth ~30 ms at deploy.

Tests build instead of reading a committed file, and would fail without this change: src/server.test.ts asserts every served bundle is byte-for-byte what bundle() produces and that the directory is under FORGE_APP_DATA, drops a decoy into the repository's static/ and expects a 404, and holds buildBundles into an unwritable path to a loud refusal. scripts/build.test.ts writes a build to a directory of its own and weighs each file on disk against its ceiling, and proves two builds of one tree produce the same names and the same bytes — Bun's output is byte-stable here, so the whole code is compared, not just the hash. scripts/bundle.test-helper.ts builds once per test process for the three games' bundle tests. The engine's three "no bundle carries this" tests could not import the build (the engine imports nothing outside itself), so src/engine/bundles.test.ts spawns it into a temp directory the way purity.test.ts spawns its probe; scripts/build.ts gained the optional output directory that makes that possible, and bun run scripts/build.ts [dir] is still the way to get the files on disk (into the now-ignored static/ by default). No new dependency. Suite is 1113 tests in ~22 s.

What I did not do: I could not check a preview — the pull opens after this run, so there was none to fetch. What I can say is that the build is unconditional and per boot, so a preview has no committed artifact to serve and nothing but its own branch's build to serve instead; #237's rebuild of main is still what repairs main, and what remains of #220 is the "two builds agree" test that landed here, so whoever picks it up should close it.

🤖 Generated with Claude Code

Closes #241

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs