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 //, 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 pages: name: Deploy to Pages runs-on: ubuntu-latest needs: build if: github.ref == 'refs/heads/main' && github.event_name != 'pull_request' 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 with: # Turn Pages on rather than requiring someone to click through # repository settings first. Needs the pages: write permission above. # If this still fails, Pages is unavailable for the repository — # private repos on the free plan cannot use it; delete this job. enablement: true - uses: actions/upload-pages-artifact@v3 with: path: public - id: deploy uses: actions/deploy-pages@v4