The image build ran the tests without serve.ts present, so the server the tests spawn never started and the hook stalled until it timed out. The runtime stage would have failed on the same missing file immediately after. The test now bails the moment the process exits and reports its stderr, so this shows up as "serve.ts exited with 1 before listening" in 53ms rather than an unexplained hook timeout after five seconds. serve.ts is also in tsconfig's include now — it was outside it, so a type error in the file that serves the app would only have surfaced at runtime. Adding it immediately caught one in the test. Verified by replaying the build stage against exactly the copied file set. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
154 lines
4.6 KiB
YAML
154 lines
4.6 KiB
YAML
name: CI
|
|
|
|
on:
|
|
push:
|
|
branches: [main]
|
|
pull_request:
|
|
workflow_dispatch:
|
|
|
|
# A new push supersedes the one before it on the same ref.
|
|
concurrency:
|
|
group: ${{ github.workflow }}-${{ github.ref }}
|
|
cancel-in-progress: true
|
|
|
|
# Least privilege by default; jobs opt into more where they need it.
|
|
permissions:
|
|
contents: read
|
|
|
|
jobs:
|
|
check:
|
|
name: Typecheck, test, feed
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
|
|
- uses: oven-sh/setup-bun@v2
|
|
with:
|
|
bun-version: "1.3"
|
|
|
|
- run: bun install --frozen-lockfile
|
|
|
|
- name: Typecheck
|
|
run: bun run typecheck
|
|
|
|
- name: Test
|
|
# Offline by design: no fixture is re-fetched, so a red run always means
|
|
# the code changed, never that a source was unreachable.
|
|
run: bun test
|
|
|
|
- name: Build feed
|
|
run: bun run build:feed
|
|
|
|
- name: Feed sanity
|
|
# A source that quietly stops yielding events is the failure mode a
|
|
# parser-only pipeline is most prone to, and nothing else surfaces it.
|
|
run: |
|
|
bun -e '
|
|
const feed = await Bun.file("public/data/events.v1.json").json();
|
|
const games = new Set(feed.events.map((e) => e.game));
|
|
console.log(`${feed.events.length} events across ${games.size} games`);
|
|
if (feed.events.length < 20) {
|
|
throw new Error(`feed collapsed to ${feed.events.length} events`);
|
|
}
|
|
if (feed.events.some((e) => !e.startsAt)) {
|
|
throw new Error("events without a start date");
|
|
}
|
|
'
|
|
|
|
build:
|
|
name: Build site
|
|
runs-on: ubuntu-latest
|
|
needs: check
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- uses: oven-sh/setup-bun@v2
|
|
with:
|
|
bun-version: "1.3"
|
|
- run: bun install --frozen-lockfile
|
|
- name: Build
|
|
# Pages serves from /<repo>/, so the app is built with a matching base
|
|
# href. Built at the domain root it would 404 on every asset.
|
|
env:
|
|
BASE_PATH: /${{ github.event.repository.name }}/
|
|
run: bun run build
|
|
- uses: actions/upload-artifact@v4
|
|
with:
|
|
name: site
|
|
path: public/
|
|
retention-days: 7
|
|
|
|
image:
|
|
name: Container image
|
|
runs-on: ubuntu-latest
|
|
needs: check
|
|
# Pushing an image for every pull request fills the registry; do it where
|
|
# the artefact could actually be deployed.
|
|
if: github.ref == 'refs/heads/main' && github.event_name != 'pull_request'
|
|
permissions:
|
|
contents: read
|
|
packages: write
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
|
|
- uses: docker/setup-buildx-action@v3
|
|
|
|
- name: Lowercase image name
|
|
id: img
|
|
# GHCR rejects any uppercase in a repository name, and
|
|
# github.repository preserves the owner's casing verbatim.
|
|
run: echo "name=ghcr.io/${GITHUB_REPOSITORY,,}" >> "$GITHUB_OUTPUT"
|
|
|
|
- uses: docker/login-action@v3
|
|
with:
|
|
registry: ghcr.io
|
|
username: ${{ github.actor }}
|
|
password: ${{ secrets.GITHUB_TOKEN }}
|
|
|
|
- uses: docker/build-push-action@v6
|
|
with:
|
|
context: .
|
|
push: true
|
|
tags: |
|
|
${{ steps.img.outputs.name }}:latest
|
|
${{ steps.img.outputs.name }}:${{ github.sha }}
|
|
cache-from: type=gha
|
|
cache-to: type=gha,mode=max
|
|
|
|
# Deploying to Pages needs two one-time steps that CI cannot do for itself:
|
|
#
|
|
# 1. Settings → Pages → Source: "GitHub Actions".
|
|
# The default GITHUB_TOKEN cannot do this. Creating a Pages site needs
|
|
# `administration: write`, which is not a permission a workflow can grant
|
|
# GITHUB_TOKEN, so `configure-pages` with enablement: true fails with
|
|
# "Resource not accessible by integration".
|
|
# 2. Settings → Secrets and variables → Actions → Variables:
|
|
# set DEPLOY_PAGES to "true".
|
|
#
|
|
# Gated on that variable so the pipeline stays green for anyone who does not
|
|
# want Pages, rather than failing on every push forever.
|
|
pages:
|
|
name: Deploy to Pages
|
|
runs-on: ubuntu-latest
|
|
needs: build
|
|
if: >-
|
|
github.ref == 'refs/heads/main' &&
|
|
github.event_name != 'pull_request' &&
|
|
vars.DEPLOY_PAGES == 'true'
|
|
permissions:
|
|
pages: write
|
|
id-token: write
|
|
environment:
|
|
name: github-pages
|
|
url: ${{ steps.deploy.outputs.page_url }}
|
|
steps:
|
|
- uses: actions/download-artifact@v4
|
|
with:
|
|
name: site
|
|
path: public
|
|
- uses: actions/configure-pages@v5
|
|
- uses: actions/upload-pages-artifact@v3
|
|
with:
|
|
path: public
|
|
- id: deploy
|
|
uses: actions/deploy-pages@v4
|