go-bananas/hack/check-pilot-go-loose.sh

153 lines
6.2 KiB
Bash
Raw Normal View History

2026-10-07 15:11:35 +02:00
#!/usr/bin/env bash
#
# Pilot check: go-loose consuming the shared contract.
#
# This is the test that decides whether the consolidation is real. It loads go-loose's
# *actual* index.html and its *actual* hand-written style.css, with only the theme
# origin substituted exactly as go-loose's server does it, and then asks the question
# that matters:
#
# Do the components shipped by go-bananas change the attributes that go-loose's own
# CSS already keys on, so go-loose is themed without a single edit to its stylesheet?
#
# If that is true, adoption is one <link> and one <script> per page, which is what
# ADR 0005 claims. If it is false, the contract is decoration.
#
# It does not boot go-loose: that needs a database and a first-run installer, and
# neither is what is being tested. What is being tested is the page and the stylesheet.
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
LOOSE="${GO_LOOSE_DIR:-/home/dnoulet/go/go-loose}"
WORK="$(mktemp -d)"
PORT="${PORT:-8398}"
THEME_PORT="${THEME_PORT:-8397}"
CHROME="${CHROME:-$(command -v chromium || command -v chromium-browser || command -v google-chrome || true)}"
cleanup() {
for pid in "${SERVER_PID:-}" "${THEME_PID:-}"; do
[[ -n "$pid" ]] && kill "$pid" 2>/dev/null || true
done
echo "WORK=$WORK" >/tmp/pilot-work-dir; rm -rf "$WORK"
}
trap cleanup EXIT
[[ -d "$LOOSE/web" ]] || { echo "go-loose not found at $LOOSE; set GO_LOOSE_DIR" >&2; exit 1; }
[[ -n "$CHROME" ]] || { echo "no chromium found; set CHROME" >&2; exit 1; }
echo "==> building go-bananas and the SDK"
# The SDK is built first, then the server that embeds it. The other order compiles a
# binary containing whatever sdk/dist happened to contain when the pilot last ran,
# which 404s /rt/v1/components.js — and the check used not to notice, because it loaded
# the module from its own copy instead of from the service.
(cd "$ROOT/sdk" && npm run build >/dev/null)
(cd "$ROOT" && go build -o "$WORK/go-bananas" ./cmd/go-bananas)
mkdir -p "$WORK/site" "$WORK/site/assets"
# go-loose's own files with the theme origin substituted the way its server does it.
# This is the only transformation: nothing in its stylesheet or markup is edited.
echo "==> assembling go-loose's page"
THEME_ORIGIN="http://localhost:$THEME_PORT"
export LOOSE_WEB="$LOOSE/web" SITE="$WORK/site" THEME_ORIGIN
python3 - <<'PY'
import os, re
src = os.environ['LOOSE_WEB']
out = os.environ['SITE']
origin = os.environ['THEME_ORIGIN']
html = open(os.path.join(src, 'index.html')).read().replace('__GO_LOOSE_THEME_ORIGIN__', origin)
# Nothing is rewritten to a local copy any more. The module used to be pointed at a
# copy under /assets/ so that the check would not depend on CORS being configured, and
# that is exactly what hid the bug this now covers: the service sent no CORS headers,
# so in a real deployment the ES module could not be fetched and the control rendered
# empty, while the pilot passed against its own copy. A module <script> is always
# fetched under CORS rules, so the only way to test the deployed path is to load it
# from the service.
# The reporter is appended to the *copy*. go-loose's source is never edited.
if 'id="pilot"' not in html:
html = html.replace('</body>', ' <script type="module" src="/pilot.js"></script>\n</body>')
open(os.path.join(out, 'index.html'), 'w').write(html)
# go-loose serves its own files under /assets/, so they belong in site/assets/ rather
# than at the site root. Getting this wrong 404s every one of them while leaving the
# page looking merely unstyled.
assets = os.path.join(out, 'assets')
os.makedirs(assets, exist_ok=True)
for name in ('style.css', 'app.js', 'account.js', 'roles.js', 'profile.js'):
p = os.path.join(src, name)
if os.path.exists(p):
open(os.path.join(assets, name), 'w').write(open(p).read())
PY
sed "s#from '/rt/v1/components.js'#from '$THEME_ORIGIN/rt/v1/components.js'#" \
"$ROOT/hack/pilot-go-loose.js" > "$WORK/site/pilot.js"
echo "==> starting the theme service on :$THEME_PORT"
# The selector fetches contract.json, and this page is on a different origin from the
# service. A cross-origin module fetch and a JSON fetch both need the consuming
# origin in CORS_ALLOWED_ORIGINS; a same-origin deployment needs nothing here. That is
# the one setting adopting the contract requires, and setting it here is what proves it
# is sufficient.
DATABASE_PATH="$WORK/pilot.db" \
BANANAS_ENVIRONMENT=development \
BANANAS_AUTH_MODE=dev \
BANANAS_PUBLIC_BASE_URL="$THEME_ORIGIN" \
BANANAS_HTTP_ADDR=":$THEME_PORT" \
BANANAS_SDK_DIR="$WORK/site/assets" \
BANANAS_CORS_ALLOWED_ORIGINS="http://localhost:$PORT" \
"$WORK/go-bananas" >"$WORK/theme.log" 2>&1 &
THEME_PID=$!
for _ in $(seq 1 40); do
curl -fsS -m 2 "http://localhost:$THEME_PORT/healthz" >/dev/null 2>&1 && break
sleep 0.25
done
echo "==> serving go-loose's page on :$PORT"
(cd "$WORK/site" && exec python3 -m http.server "$PORT") >"$WORK/site.log" 2>&1 &
SERVER_PID=$!
for _ in $(seq 1 40); do
curl -fsS -m 2 "http://localhost:$PORT/index.html" >/dev/null 2>&1 && break
sleep 0.25
done
echo "==> driving the page"
"$CHROME" \
--headless --no-sandbox --disable-gpu \
--virtual-time-budget=12000 \
--dump-dom "http://localhost:$PORT/index.html" \
>"$WORK/dom.html" 2>"$WORK/chrome.log"
result="$(python3 - "$WORK/dom.html" <<'PY'
import re, sys
dom = open(sys.argv[1]).read()
m = re.search(r'id="pilot"[^>]*>(.*?)</pre>', dom, re.S)
print(m.group(1).strip() if m else "")
PY
)"
if [[ -z "$result" ]]; then
echo "PILOT FAIL: the page recorded nothing, so the check did not run." >&2
echo "the reporter most likely threw; diagnostics follow." >&2
echo "--- browser console ---" >&2
grep -iE 'CONSOLE.*(error|Uncaught|violates|refused)' "$WORK/chrome.log" | head -5 >&2 || true
echo "--- dom size / page head ---" >&2
wc -c <"$WORK/dom.html" >&2 || true
head -c 300 "$WORK/dom.html" >&2 || true
echo >&2
echo "--- static server 404s ---" >&2
grep -oE 'GET /[^"]*" 404' "$WORK/site.log" | head -5 >&2 || true
echo "--- pilot element ---" >&2
grep -o 'id="pilot"[^<]*' "$WORK/dom.html" | head -2 >&2 || true
echo "--- theme service log (last 5) ---" >&2
tail -5 "$WORK/theme.log" >&2 || true
fi
echo
echo "$result"
[[ "$result" == PILOT\ PASS* ]]