#!/usr/bin/env bash # # Pilot check: go-tell consuming the shared contract. # # go-tell is the hard case. Before the migration it had no CSS custom properties at # all: 340 hardcoded hex values and 153 per-palette override rules, with cyberpunk # baked into the un-scoped base rules. Its stylesheet now reads from the contract, so # this check exists to prove the rewrite works rather than to assert that it was typed: # # - The four palettes go-tell's own rules key on are all published. # - Switching a palette through the shipped control moves the rendered page. # - The terminal palette's global `font-family: ... !important` reaches the # application but not into the controls' shadow roots. # - The appearance axis writes data-mode, which is what gives go-tell the light # mode it never had. # # It does not boot go-tell: the application needs Postgres and a session cookie to # reach its authenticated routes, and none of that is what is being tested. What is # tested is the built shell and the built stylesheet. set -euo pipefail ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" TELL="${GO_TELL_DIR:-/home/dnoulet/go/go-tell}" WORK="$(mktemp -d)" PORT="${PORT:-8396}" THEME_PORT="${THEME_PORT:-8395}" 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 rm -rf "$WORK" } trap cleanup EXIT [[ -d "$TELL/ui/app" ]] || { echo "go-tell not found at $TELL; set GO_TELL_DIR" >&2; exit 1; } [[ -n "$CHROME" ]] || { echo "no chromium found; set CHROME" >&2; exit 1; } echo "==> building the service, the SDK and go-tell's UI" (cd "$ROOT" && go build -o "$WORK/go-bananas" ./cmd/go-bananas) (cd "$ROOT/sdk" && npm run build >/dev/null) (cd "$TELL/ui/app" && npm run build >/dev/null) DIST="$TELL/ui/dist" [[ -d "$DIST" ]] || { echo "expected a build at $DIST" >&2; exit 1; } mkdir -p "$WORK/site" "$WORK/site/assets" cp "$ROOT"/sdk/dist/*.js "$WORK/site/assets/" cp "$DIST/index.html" "$WORK/site/index.html" cp "$DIST"/assets/* "$WORK/site/" 2>/dev/null || true cp "$ROOT/hack/pilot-go-tell.js" "$WORK/site/pilot.js" # The contract is added here rather than served through go-tell's Go handler, because # this check serves the built shell with a plain static server. The mechanism itself is # covered where it lives, by `go test ./internal/web/ -run Theme` in go-tell; this check # is about whether the stylesheet and the controls work together. What is added below is # what applyThemeOrigin adds when CMS_THEME_BASE_URL is set. echo "==> assembling go-tell's built shell" SHELL="$WORK/site/index.html" SITE_ASSETS="$WORK/site" THEME_ORIGIN="http://localhost:$THEME_PORT" \ python3 <<'PY' import os, re path = os.environ['SHELL'] origin = os.environ['THEME_ORIGIN'] s = open(path).read() # The shell references its own bundle by absolute path; the check serves those files # beside the shell rather than under /assets. s = re.sub(r'(src|href)="/assets/', r'\1="/', s) # What go-tell's handler adds when the theme origin is configured: a stylesheet and the # control module, inside the head so the stylesheet parses before the body renders. tag = ( '\n ' '\n \n' ) if '' in s: s = s.replace('', tag + ' ', 1) # Transitions are disabled for the check. go-tell transitions `background` over # 300ms, and a headless run under --virtual-time-budget fast-forwards timers without # advancing the style clock, so a transition never leaves its start value: the # untransitioned --bn-bg already reports the new palette while the computed background # still reports the old one. That is an artefact of the harness, not of the # application, and reading through it is the only way to make the assertions # deterministic. s = s.replace( '', '' ' ', 1, ) # The reporter, appended to the copy. go-tell's own source is never edited. s = s.replace('', ' \n') open(path, 'w').write(s) PY echo "==> starting the theme service on :$THEME_PORT" DATABASE_PATH="$WORK/tell.db" \ BANANAS_ENVIRONMENT=development \ BANANAS_AUTH_MODE=dev \ BANANAS_PUBLIC_BASE_URL="http://localhost:$THEME_PORT" \ 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-tell's shell 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=15000 \ --dump-dom "http://localhost:$PORT/index.html" \ >"$WORK/dom.html" 2>"$WORK/chrome.log" result="$(python3 - "$WORK/dom.html" <<'PYEOF' import re, sys dom = open(sys.argv[1]).read() m = re.search(r'id="pilot"[^>]*>(.*?)', dom, re.S) print(m.group(1).strip() if m else "") PYEOF )" if [[ -z "$result" ]]; then echo "PILOT FAIL: the page recorded nothing, so the check did not run." >&2 echo "--- browser console ---" >&2 grep -iE 'CONSOLE.*(error|Uncaught|violates|refused)' "$WORK/chrome.log" | head -5 >&2 || true echo "--- dom size ---" >&2 wc -c <"$WORK/dom.html" >&2 || true exit 1 fi echo echo "$result" [[ "$result" == PILOT\ PASS* ]]