Stage the ACannons browser build at /cannons

A third instance of the pattern notebooks-export/ and the reading payloads
already use: an artifact built somewhere with a toolchain this repo's
node:24-slim container does not have, dropped into a gitignored directory,
staged into public/ by a script, and picked up by auto-build.sh's signature on
the next tick. Nothing here needs a JDK, Go, or an opinion about the game --
cannons_reloaded's deploy/export-cannons.sh hands over a directory that is
already ready to serve.

Three things are load-bearing and easy to get wrong later:

The payload is at /cn/, not /cannons/. With build.format:'file' the page
builds to cannons.html, and try_files would then have a file and a directory
competing for the same URL. That is exactly why the notebook exports live at
/nb/ and not under /notebooks/.

nginx gets gzip_static for /cn/. nginx:alpine has `gzip on` commented out in
its base config, so without it the page ships 9MB instead of 3.3MB and nothing
anywhere reports that. It gets no-cache rather than an expiry, because
classes.js and cannons.wasm are stable filenames whose contents change on
every rebuild -- a long expiry would pair a stale client with a fresh server.

The page loads on click, not `loading="lazy"`. Lazy only defers until the
frame nears the viewport and this frame is above the fold, so a few MB would
download for anyone who opened the page to read the paragraph.

A missing export is a normal state throughout: copy-cannons.mjs stages
nothing, the page says so, and the sidebar link does not appear.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S4wdbGSVzvUDHBWRjSVxfr
This commit is contained in:
wes
2026-08-28 21:17:00 -04:00
co-authored by Claude Opus 5
parent 172f5c3999
commit de620bfaac
8 changed files with 257 additions and 1 deletions
+46
View File
@@ -0,0 +1,46 @@
// Stage the ACannons browser build produced by cannons_reloaded's
// deploy/export-cannons.sh into public/, where Astro picks it up as static
// files.
//
// The export arrives ready to serve: only what the embedded page loads, res/
// already dereferenced down to en_US, and .gz siblings alongside the two big
// files for nginx's gzip_static. So this is a plain recursive copy and
// deliberately knows nothing about the game -- everything that needs a JDK, a
// Go toolchain or an opinion about the client lives in the other repo.
//
// No export (the workflow has never run, or this is a fresh clone) is a normal
// state, not an error: the page renders a note instead of a frame.
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
const REPO = path.join(path.dirname(fileURLToPath(import.meta.url)), '..');
const SRC_DIR = path.join(REPO, 'cannons-export');
const OUT_DIR = path.join(REPO, 'public', 'cn');
fs.rmSync(OUT_DIR, { recursive: true, force: true });
if (!fs.existsSync(path.join(SRC_DIR, 'manifest.json'))) {
console.log('copy-cannons: no cannons-export/manifest.json — nothing to stage');
process.exit(0);
}
// The manifest is for the build, not the browser: it says which commit this
// is and what it weighs, and src/lib/cannons.mjs reads it from the export
// directly. Shipping it would just be a file nobody fetches.
let files = 0;
let bytes = 0;
for (const ent of fs.readdirSync(SRC_DIR, { withFileTypes: true, recursive: true })) {
if (ent.isDirectory()) continue;
const rel = path.relative(SRC_DIR, path.join(ent.parentPath, ent.name));
if (rel === 'manifest.json') continue;
const dest = path.join(OUT_DIR, rel);
fs.mkdirSync(path.dirname(dest), { recursive: true });
fs.copyFileSync(path.join(SRC_DIR, rel), dest);
files++;
bytes += fs.statSync(dest).size;
}
console.log(
`copy-cannons: ${files} files, ${(bytes / 1048576).toFixed(1)}MB -> ${OUT_DIR}`,
);