3bb484b1f3
`stamped` exited on the first failure, so breaking two substitutions named
app.js and never reached worker.js -- and the comment ("names itself when it is
the one missing") read as though it did not. It fails safe, which is why it was
filed rather than fixed in PR #47; the code is fixed rather than the comment
because the diagnostic is paid for in wasm-pack rebuilds, the slowest loop in
this repo.
Each stamp is still verified on its own -- the property PR #24's finding bought
-- but `stamped` records and a gate after the four calls fails the build.
Proved by injection both ways: two broken sedi patterns now name both files and
exit 1, and deleting the gate prints both failures while exiting 0, which is
the check-that-does-not-check the comment now warns about.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH
121 lines
5.7 KiB
Bash
Executable File
121 lines
5.7 KiB
Bash
Executable File
#!/bin/sh
|
|
# Assemble the static site into `site/`: the landing page from `docs/` at the
|
|
# root, and the browser app from `web/` under `app/`.
|
|
#
|
|
# `web/` is the *source* directory: it also holds build.sh, assemble.sh and
|
|
# .gitignore, which have no business being served. This copies what ships.
|
|
#
|
|
# Extracted from the CI workflow so that the Pages job and the browser harness
|
|
# assemble the same tree — a harness that runs against a different arrangement
|
|
# of files is testing something nobody deploys (improvise-vvl3). Same reason
|
|
# `web/build.sh` exists rather than two copies of the wasm-pack invocation.
|
|
#
|
|
# Usage: web/assemble.sh [OUT_DIR] [CACHE_BUST_TOKEN]
|
|
#
|
|
# The token cache-busts the two generated bundles. Pages serves everything
|
|
# `max-age=600` and the wasm-pack filenames carry no content hash, so for ten
|
|
# minutes after a deploy a returning visitor runs the PREVIOUS build — which
|
|
# during improvise-9x6 debugging made a shipped fix look like it had not
|
|
# shipped, twice. CI passes the commit SHA; a local run defaults to `dev`.
|
|
#
|
|
# Both halves are stamped, because a fresh glue file does NOT pull a fresh
|
|
# .wasm: the glue locates it with `new URL('..._bg.wasm', import.meta.url)`,
|
|
# and a URL constructed against a base does not inherit the base's query
|
|
# string. Stamping only the import specifier would refresh the wrapper and
|
|
# keep the stale binary — the failure mode this is here to end.
|
|
set -eu
|
|
|
|
root=$(cd "$(dirname "$0")/.." && pwd)
|
|
out=${1:-"$root/site"}
|
|
token=${2:-dev}
|
|
|
|
test -d "$root/web/pkg" || { echo "web/pkg missing — run web/build.sh first" >&2; exit 1; }
|
|
test -d "$root/web/worker-pkg" || { echo "web/worker-pkg missing — run web/build.sh first" >&2; exit 1; }
|
|
|
|
rm -rf "$out"
|
|
mkdir -p "$out"
|
|
|
|
# The landing page is the front door and the app is what it sends you to
|
|
# (user ruling, improvise-kh8). So `docs/` lands at the ROOT and the app moves
|
|
# under `app/` whole — every path in app.js and worker.js is relative, so the
|
|
# bundles travel with it and the generated glue needs no editing.
|
|
cp -f "$root/docs/index.html" "$out/index.html"
|
|
cp -rf "$root/docs/casts" "$out/casts"
|
|
|
|
app="$out/app"
|
|
mkdir -p "$app"
|
|
# shell.js and serial.js are imported by app.js and worker.js: leaving either
|
|
# out is a page that fails to load its own module graph, not a degraded one.
|
|
cp -f "$root/web/index.html" "$root/web/app.js" "$root/web/shell.js" \
|
|
"$root/web/serial.js" "$root/web/style.css" "$root/web/worker.js" "$app/"
|
|
# The document the page opens on a browser that has never seen one.
|
|
# web/worker.js fetches it by the ?seed= URL and hands the bytes to Rust,
|
|
# which writes them through the VFS — so leaving it out is an empty grid, not
|
|
# a 404 the page could report.
|
|
cp -f "$root/web/demo.improv" "$app/"
|
|
|
|
cp -rf "$root/web/pkg" "$app/pkg"
|
|
cp -rf "$root/web/worker-pkg" "$app/worker-pkg"
|
|
rm -f "$app/pkg/.gitignore" "$app/pkg/package.json"
|
|
rm -f "$app/worker-pkg/.gitignore" "$app/worker-pkg/package.json"
|
|
|
|
# BSD sed (macOS) needs an argument to -i; GNU sed must not be given one.
|
|
sedi() { sed "$1" "$2" > "$2.tmp" && mv -f "$2.tmp" "$2"; }
|
|
|
|
sedi "s|\./pkg/improvise_wasm_client\.js|&?v=${token}|" "$app/app.js"
|
|
sedi "s|\./worker-pkg/improvise_worker_server\.js|&?v=${token}|" "$app/worker.js"
|
|
sedi "s|'improvise_wasm_client_bg\.wasm'|'improvise_wasm_client_bg.wasm?v=${token}'|" \
|
|
"$app/pkg/improvise_wasm_client.js"
|
|
sedi "s|'improvise_worker_server_bg\.wasm'|'improvise_worker_server_bg.wasm?v=${token}'|" \
|
|
"$app/worker-pkg/improvise_worker_server.js"
|
|
|
|
# One `grep` over all four files succeeds when ANY one of them matches, so
|
|
# three failed substitutions could still produce a "successful" assembly under
|
|
# `set -e` (found by Copilot on PR #24). These checks are the only guard
|
|
# against the generated glue changing shape, so each stamped reference is
|
|
# verified on its own and names itself when it is missing.
|
|
#
|
|
# `stamped` RECORDS rather than exits, and the gate below is what fails the
|
|
# build. Exiting on the first failure named one file and never reached the
|
|
# rest, so a shape change that broke two stamps cost two wasm-pack rebuilds to
|
|
# diagnose -- the slowest loop in this repo (improvise-x4wv). The gate is the
|
|
# whole guard: without it every failure prints and the assembly succeeds, which
|
|
# is the check-that-does-not-check this whole file is full of fixes for.
|
|
missing=""
|
|
stamped() {
|
|
if grep -q "$2" "$1"; then
|
|
grep -n "$2" "$1"
|
|
else
|
|
echo "assemble: $1 is missing the cache-bust stamp: $2" >&2
|
|
missing="${missing} $1"
|
|
fi
|
|
}
|
|
stamped "$app/app.js" "pkg/improvise_wasm_client.js?v=${token}"
|
|
stamped "$app/worker.js" "worker-pkg/improvise_worker_server.js?v=${token}"
|
|
stamped "$app/pkg/improvise_wasm_client.js" "improvise_wasm_client_bg.wasm?v=${token}"
|
|
stamped "$app/worker-pkg/improvise_worker_server.js" "improvise_worker_server_bg.wasm?v=${token}"
|
|
test -z "$missing" || {
|
|
echo "assemble: refusing to ship; unstamped:${missing}" >&2
|
|
exit 1
|
|
}
|
|
|
|
# Every relative import the two entry points make must have been copied. The
|
|
# cp list above is a second answer to "what ships", and a missing sibling is a
|
|
# 404 at module-load time, which the page cannot report.
|
|
for entry in app.js worker.js; do
|
|
deps=$(sed -n 's|.*from "\./\([A-Za-z0-9_.-]*\.js\)".*|\1|p' "$app/$entry")
|
|
for dep in $deps; do
|
|
test -f "$app/$dep" || {
|
|
echo "assemble: $entry imports ./$dep, which was not copied" >&2
|
|
exit 1
|
|
}
|
|
done
|
|
done
|
|
|
|
# The prose site at `docs/`, generated from context/ by the same script the
|
|
# harness serves. It runs LAST because its link check verifies the links that
|
|
# leave its own tree (`../index.html`, `../app/`) against the filesystem, which
|
|
# means their targets must already be here. It exits non-zero on a break, so a
|
|
# dead cross-reference fails the assembly rather than shipping.
|
|
node "$root/scripts/build-doc-site.mjs" "$out/docs" "$token"
|