name: CI # Two gate levels keep report PRs cheap: # - Report-only PRs (from compat-report.yml — changes limited to # src/content/compat/**, public/screenshots/** or public/data/**) run only # npm ci + typecheck + report validation. Report merges don't rebuild the # site (deploy.yml republishes with fresh runtime data), so a full build # and test suite add nothing on the PR branch. # - Everything else (code, scripts, games database, pushes to main) runs the # full pipeline: typecheck, tests, report validation and the production # build (prebuild also regenerates the GitHub snapshot + compat JSONs). on: push: branches: [main] pull_request: permissions: contents: read issues: write pull-requests: write actions: write concurrency: group: ci-${{ github.workflow }}-${{ github.ref }} cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: # Full history so report-only PRs can be detected by diffing against # the PR base (same allowlist as deploy.yml's content-only scope). fetch-depth: 0 - uses: actions/setup-node@v4 with: node-version: 22 cache: npm - name: Detect change scope id: scope shell: bash env: EVENT_NAME: ${{ github.event_name }} BASE: ${{ github.event.pull_request.base.sha }} HEAD_SHA: ${{ github.event.pull_request.head.sha }} run: | # Pushes to main are always the full pipeline. if [ "$EVENT_NAME" = "push" ]; then echo "scope=full" >> "$GITHUB_OUTPUT" exit 0 fi if [ -z "$BASE" ] || [ -z "$HEAD_SHA" ]; then echo "::warning::Missing PR base or head SHA; treating as full." echo "scope=full" >> "$GITHUB_OUTPUT" exit 0 fi CHANGED="$(git diff --name-only "$BASE" "$HEAD_SHA" 2>/dev/null || true)" if [ -z "$CHANGED" ]; then echo "scope=full" >> "$GITHUB_OUTPUT" exit 0 fi # Report-only = every changed file is a compat report, a screenshot # or generated data (mirrors deploy.yml). Anything else → full. if printf '%s\n' "$CHANGED" | grep -vqE '^(src/content/compat/|public/screenshots/|public/data/)'; then echo "scope=full" >> "$GITHUB_OUTPUT" else echo "scope=report" >> "$GITHUB_OUTPUT" fi - run: npm ci - run: npm run typecheck # Report validation is cheap and dependency-free — gate it on every run. - name: Validate compatibility reports run: node scripts/validate-compat.mjs - name: Validate compatibility exports & sitemap run: | node scripts/export-compat-json.mjs node scripts/export-site-compat-json.mjs node scripts/generate-sitemap.mjs - name: Test if: steps.scope.outputs.scope != 'report' env: CHECK_SEED_SYNC: ${{ github.head_ref == 'data/games-refresh' }} run: npm test # prebuild validates compat reports and regenerates the GitHub snapshot, # the GUI + site compat JSON and the sitemap (GITHUB_TOKEN avoids rate # limits on the API fetches). Skipped for report-only PRs — report merges # don't rebuild the site, so the full build adds nothing on the branch. - name: Build if: steps.scope.outputs.scope != 'report' env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: npm run build - name: Reopen mirror issue if tests failed on a compat PR if: failure() && github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository && startsWith(github.head_ref, 'compat/') env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GH_REPO: ${{ github.repository }} PR_NUMBER: ${{ github.event.pull_request.number }} PR_BODY: ${{ github.event.pull_request.body }} run: | gh pr merge "$PR_NUMBER" --disable-auto || true ISSUE_NUM="$(printf '%s' "$PR_BODY" | sed -n 's/.*.*/\1/p' | head -1)" if [ -n "$ISSUE_NUM" ]; then gh issue reopen "$ISSUE_NUM" || true gh issue comment "$ISSUE_NUM" -b "⚠️ Checks failed on report PR #$PR_NUMBER. Auto-merge disabled; please fix the report or tests." || true fi auto-deploy: needs: [test] if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository && (startsWith(github.head_ref, 'compat/') || github.head_ref == 'data/games-refresh') runs-on: ubuntu-latest steps: - name: Trigger deploy if auto-merged env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GH_REPO: ${{ github.repository }} PR_NUMBER: ${{ github.event.pull_request.number }} run: | for i in {1..36}; do STATE="$(gh pr view "$PR_NUMBER" --json state --jq .state 2>/dev/null || true)" if [ "$STATE" = "MERGED" ]; then echo "PR #$PR_NUMBER merged successfully." MERGE_SHA="$(gh pr view "$PR_NUMBER" --json mergeCommit --jq '.mergeCommit.oid // empty' 2>/dev/null || true)" if [ -n "$MERGE_SHA" ]; then EXISTING="$(gh run list --workflow=deploy.yml --commit="$MERGE_SHA" --json databaseId -q '.[0].databaseId // empty' 2>/dev/null || true)" if [ -n "$EXISTING" ]; then echo "Deploy already queued or running for commit $MERGE_SHA. Skipping manual dispatch." exit 0 fi fi echo "Dispatching deploy.yml for main..." gh workflow run deploy.yml --ref main exit 0 fi sleep 5 done echo "PR #$PR_NUMBER was not merged within 180s."