The font-inlining guard only caught a RENAME. It asked "did /fonts/<listed family>.woff2 disappear?", so an ADDITION walked straight past it — and an addition is the likelier accident: someone doing ordinary app work adds a display font or a decorative background to the shared theme, has no reason to open a viewer build config, and ships a keepsake that reaches for /fonts/Playfair.woff2 on the guest's own disk. font-display: swap hides it, so the artifact looks right to everyone who happens to have the file locally and renders in Times New Roman for the couple. It now asserts the invariant instead of a list: nothing in the emitted keepsake may reference an external URL. Self-maintaining, and it covers fonts, images and stylesheets alike. In writeBundle rather than generateBundle — generateBundle runs more than once and the stylesheet is not inlined on the earlier pass, so asserting there fails a perfectly good build. viewer-no-broken-tiles gets a positive anchor. Its "nothing is broken" check filters img elements, so a viewer that rendered NOTHING yields [] and passes: the one spec whose whole subject is that the images resolve was the one that would have stayed green through a total viewer regression. Everything else it checks comes from the backend and the classic head script, neither of which needs the viewer bundle to have run. And a CI job, because neither of the above fires on its own: no workflow, Dockerfile or script built this viewer, so the guard could sit disarmed indefinitely, and the committed artifact — compiled into the binary with include_dir! — could drift from its source with nothing to say so.
168 lines
6.0 KiB
YAML
168 lines
6.0 KiB
YAML
# The checks that were only ever running on a laptop.
|
|
#
|
|
# Before this file, CI ran Playwright (chromium-desktop) and the dependency audit — and nothing
|
|
# else. `cargo test` (40 tests), the frontend vitest suite (5 files, including the offline
|
|
# upload-queue and auth-token logic), svelte-check, and the e2e typecheck were all green on a
|
|
# developer's machine and gated NOTHING. A check that only ever runs locally is not running.
|
|
#
|
|
# In .github/workflows/ because Gitea Actions scans it too (see audit.yml). Rust is installed
|
|
# explicitly: the common Gitea runner images ship Node and Docker, not cargo.
|
|
name: Checks
|
|
|
|
on:
|
|
pull_request:
|
|
push:
|
|
branches: [main]
|
|
|
|
jobs:
|
|
backend:
|
|
name: Backend — cargo test + clippy + fmt
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 20
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
|
|
- name: Install Rust toolchain
|
|
run: |
|
|
if ! command -v cargo > /dev/null; then
|
|
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal
|
|
echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
|
|
fi
|
|
rustup component add clippy rustfmt
|
|
|
|
- uses: actions/cache@v4
|
|
with:
|
|
path: |
|
|
~/.cargo/registry
|
|
~/.cargo/git
|
|
backend/target
|
|
key: cargo-${{ hashFiles('backend/Cargo.lock') }}
|
|
restore-keys: cargo-
|
|
|
|
# SQLx runs its queries against a live database at TEST time (see backend/tests/), so the
|
|
# DB-backed tests need one. The pure unit tests don't care, but starting it unconditionally
|
|
# keeps the job simple and honest about what it covers.
|
|
- name: Start Postgres
|
|
run: |
|
|
docker run -d --name ci-pg -p 5432:5432 \
|
|
-e POSTGRES_PASSWORD=postgres -e POSTGRES_DB=eventsnap_ci \
|
|
postgres:16-alpine
|
|
for _ in $(seq 1 30); do
|
|
docker exec ci-pg pg_isready -U postgres > /dev/null 2>&1 && break
|
|
sleep 1
|
|
done
|
|
|
|
- name: Test
|
|
working-directory: ./backend
|
|
env:
|
|
DATABASE_URL: postgres://postgres:postgres@localhost:5432/eventsnap_ci
|
|
# `#[sqlx::test]` creates a fresh database per test and opens its own pool; run at unbounded
|
|
# parallelism against a default `max_connections=100` Postgres, a full suite can exhaust the
|
|
# server's connection slots and fail with PoolTimedOut — a pure infra flake, not a real
|
|
# failure. Cap the concurrency so the gate stays trustworthy on a loaded runner.
|
|
run: cargo test --all-features -- --test-threads=8
|
|
|
|
- name: Clippy
|
|
working-directory: ./backend
|
|
run: cargo clippy --all-targets -- -D warnings
|
|
|
|
- name: Format
|
|
working-directory: ./backend
|
|
run: cargo fmt --check
|
|
|
|
frontend:
|
|
name: Frontend — vitest + svelte-check
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 15
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- uses: actions/setup-node@v4
|
|
with:
|
|
node-version: '22'
|
|
cache: 'npm'
|
|
cache-dependency-path: 'frontend/package-lock.json'
|
|
|
|
- name: Install deps
|
|
working-directory: ./frontend
|
|
run: npm ci || npm install
|
|
|
|
- name: Unit tests
|
|
working-directory: ./frontend
|
|
run: npm run test:unit
|
|
|
|
- name: svelte-check
|
|
working-directory: ./frontend
|
|
run: npx svelte-check --threshold error
|
|
|
|
- name: ESLint
|
|
working-directory: ./frontend
|
|
run: npm run lint
|
|
|
|
- name: Prettier
|
|
working-directory: ./frontend
|
|
run: npm run format:check
|
|
|
|
export-viewer:
|
|
name: Keepsake viewer — builds, self-contained, committed artifact in sync
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 15
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- uses: actions/setup-node@v4
|
|
with:
|
|
node-version: '22'
|
|
cache: 'npm'
|
|
cache-dependency-path: 'frontend/export-viewer/package-lock.json'
|
|
|
|
- name: Install deps
|
|
working-directory: ./frontend/export-viewer
|
|
run: npm ci || npm install
|
|
|
|
# Two things nothing else in CI covered, both of which ship a broken keepsake silently.
|
|
#
|
|
# 1. The build's own self-contained guard (`inlineThemeFonts`) is the only thing standing
|
|
# between an added theme asset and a viewer that reaches for files on the guest's disk.
|
|
# It is a build-time `this.error`, so it only fires when somebody runs this build — and
|
|
# no workflow, Dockerfile or script did. It could sit disarmed indefinitely.
|
|
#
|
|
# 2. `backend/static/export-viewer/index.html` is COMMITTED and compiled into the binary with
|
|
# `include_dir!`. A viewer source change merged without a manual rebuild ships the stale
|
|
# artifact, and nothing anywhere would say so. `git diff --exit-code` is the check.
|
|
- name: Build the standalone viewer
|
|
working-directory: ./frontend/export-viewer
|
|
run: npm run build
|
|
|
|
- name: Committed artifact matches a clean rebuild
|
|
run: |
|
|
if ! git diff --exit-code -- backend/static/export-viewer/; then
|
|
echo "::error::backend/static/export-viewer/ is out of date with frontend/export-viewer/."
|
|
echo "Run 'npm run build' in frontend/export-viewer and commit the result."
|
|
exit 1
|
|
fi
|
|
|
|
e2e-typecheck:
|
|
name: E2E — typecheck + lint
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 10
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- uses: actions/setup-node@v4
|
|
with:
|
|
node-version: '22'
|
|
- name: Install deps
|
|
working-directory: ./e2e
|
|
run: npm install
|
|
# Playwright TRANSPILES specs without typechecking them, so a type error in a spec is
|
|
# invisible until the assertion it guards silently does the wrong thing at runtime.
|
|
- name: tsc --noEmit
|
|
working-directory: ./e2e
|
|
run: npx tsc --noEmit
|
|
|
|
- name: ESLint
|
|
working-directory: ./e2e
|
|
run: npm run lint
|
|
|
|
- name: Prettier
|
|
working-directory: ./e2e
|
|
run: npm run format:check
|