Skip to content

Stop reading the whole club to draw the organisers' dashboard #62

Stop reading the whole club to draw the organisers' dashboard

Stop reading the whole club to draw the organisers' dashboard #62

Workflow file for this run

# CI for a static site with no services to stand up.
#
# The ordering here is deliberate and load-bearing: `build` and `dev` share the
# .next directory, so running them concurrently deletes the chunks the dev server
# is serving. The page then returns 200 with no JavaScript, which looks like a
# pass to anything that only checks status codes. So the build finishes and is
# cleared before the dev server starts, and `smoke` runs first to prove the server
# is actually serving a hydrating page before `qa` reports on it.
name: CI
on:
push:
branches: [main]
pull_request:
jobs:
check:
runs-on: ubuntu-latest
defaults:
run:
working-directory: web
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
cache-dependency-path: web/package-lock.json
- run: npm ci
- name: Typecheck
run: npm run typecheck
- name: Lint
run: npm run lint
# Static and fast, so it runs before the build. Guards the categorical-colour
# constraints that a comment used to only assert — see content/programs.ts.
- name: Palette constraints
run: npm run palette
# firestore.rules hardcodes the join form's allowed values, because Firestore
# rules cannot import. This diffs the two copies and asserts the create-only
# boundary is still closed.
#
# It runs in CI because nothing else here would catch the drift: the smoke test
# submits no applications and the QA sweep reads pixels. The failure it prevents
# is a real applicant getting permission-denied on a path the form offers, with
# the page rendering perfectly. Needs no Firebase project and no network.
- name: Firestore rules match the form
run: npm run rules
- name: Build
run: npm run build
# Proves the site is genuinely static. If a route ever starts needing a
# server at request time this fails, which is the point — the whole
# zero-credentials setup depends on it staying static.
- name: Assert the build is fully prerendered
run: |
if grep -qE '^\S*λ|^\S*ƒ' .next/routes-manifest.json 2>/dev/null; then
echo "A dynamic route appeared; this site must stay fully static." >&2
exit 1
fi
# All three engines, not just Chromium. Chromium-only testing let a CSP
# directive ship that made the site render completely blank in Safari's
# engine — no CSS, no fonts, no JavaScript. Safari is the majority of mobile
# traffic in the audience this is built for, so "it works in Chrome" is not a
# result.
- name: Install browser engines for Playwright
run: npx playwright install --with-deps chromium webkit firefox
# Cleared so the dev server builds its own chunks rather than inheriting
# production ones.
- name: Clear build output before starting dev
run: rm -rf .next
- name: Start dev server
run: |
npx next dev -p 3000 > /tmp/dev.log 2>&1 &
for i in $(seq 1 60); do
if [ "$(curl -s -o /dev/null -w %{http_code} http://localhost:3000 || true)" = "200" ]; then
echo "up after ${i} tries"; exit 0
fi
sleep 2
done
echo "dev server never came up:" >&2; cat /tmp/dev.log >&2; exit 1
# Must precede qa: a page serving no client JS passes most of qa's checks.
# SITE_URL is set explicitly rather than relying on the default, so the port
# is stated in one place next to the server that binds it.
- name: Smoke (is client JS actually alive?)
run: npm run smoke
env:
SITE_URL: http://localhost:3000
- name: Accessibility and layout sweep
run: npm run qa
env:
SITE_URL: http://localhost:3000
# Feature-by-feature across engines: the frosted plate, container queries, the
# lh unit, color-mix, token alpha, the full-bleed band, fonts resolving rather
# than falling back, and hydration.
- name: Cross-engine audit
run: npm run browsers
env:
SITE_URL: http://localhost:3000
- name: Upload QA report on failure
if: failure()
uses: actions/upload-artifact@v4
with:
name: qa-report
path: web/study/qa/
if-no-files-found: ignore