#!/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 the one missing. stamped() { grep -q "$2" "$1" || { echo "assemble: $1 is missing the cache-bust stamp: $2" >&2 exit 1 } grep -n "$2" "$1" } 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}" # 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"