Skip to content

CI

CI #925

Workflow file for this run

name: CI
on:
push:
branches: [master]
pull_request:
branches: [master]
schedule:
# Run daily at 2 AM UTC to refresh data
- cron: '0 2 * * *'
workflow_dispatch:
# Allow manual triggering
permissions:
contents: read
# Cancel in-progress runs for the same ref on PR and push events.
# Schedule and workflow_dispatch runs each get a unique group via run_id
# so they are never cancelled mid-flight (data-refresh is long-running).
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}-${{ (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') && github.run_id || 'default' }}
cancel-in-progress: ${{ github.event_name != 'schedule' && github.event_name != 'workflow_dispatch' }}
jobs:
detect-change-scope:
name: Detect non-runtime-only changes
runs-on: ubuntu-latest
outputs:
lightweight_only: ${{ steps.changed-files.outputs.lightweight_only }}
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Detect non-runtime-only changes
id: changed-files
shell: bash
run: |
if [[ "${{ github.event_name }}" == "schedule" || "${{ github.event_name }}" == "workflow_dispatch" ]]; then
echo "lightweight_only=false" >> "$GITHUB_OUTPUT"
exit 0
fi
if [[ "${{ github.event_name }}" == "pull_request" ]]; then
base_sha="${{ github.event.pull_request.base.sha }}"
head_sha="${{ github.event.pull_request.head.sha }}"
else
base_sha="${{ github.event.before }}"
head_sha="${{ github.sha }}"
fi
if [[ -z "$base_sha" || "$base_sha" =~ ^0+$ ]]; then
echo "lightweight_only=false" >> "$GITHUB_OUTPUT"
exit 0
fi
changed_files="$(git diff --name-only "$base_sha" "$head_sha")"
if [[ -z "$changed_files" ]]; then
echo "lightweight_only=false" >> "$GITHUB_OUTPUT"
exit 0
fi
# Safe-to-skip patterns for heavyweight CI jobs.
# Keep this list narrow and repo-specific. Any path not matched here
# will run full CI gates.
lightweight_patterns='(^docs/|\.md$|^LICENSE$|^\.github/|^\.vscode/|^\.editorconfig$|^\.gitignore$|^\.prettier.*$|^biome\.jsonc$)'
non_runtime_count="$(printf '%s\n' "$changed_files" | grep -Evc "$lightweight_patterns" || true)"
if [[ "$non_runtime_count" == "0" ]]; then
echo "lightweight_only=true" >> "$GITHUB_OUTPUT"
else
echo "lightweight_only=false" >> "$GITHUB_OUTPUT"
fi
test:
name: Test Suite
runs-on: ubuntu-latest
needs: detect-change-scope
if: needs.detect-change-scope.outputs.lightweight_only != 'true'
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js and pnpm
uses: ./.github/actions/setup-node-pnpm
- name: Run non-browser tests with coverage for PR/push
if: github.event_name == 'pull_request' || github.event_name == 'push'
run: pnpm run test:coverage
- name: Run non-browser tests for schedule/manual runs
if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
run: pnpm run test:run
- name: Upload coverage artifacts
if: github.event_name == 'pull_request' || github.event_name == 'push'
uses: actions/upload-artifact@v4
with:
name: coverage-${{ github.sha }}
path: coverage/
retention-days: 7
browser-test:
name: Browser Test Suite
runs-on: ubuntu-latest
needs: [detect-change-scope, test]
if: needs.detect-change-scope.outputs.lightweight_only != 'true' && (github.event_name == 'pull_request' || github.event_name == 'push')
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js and pnpm
uses: ./.github/actions/setup-node-pnpm
- name: Get Playwright version
id: playwright-version
shell: bash
run: echo "version=$(pnpm exec playwright --version | awk '{print $2}')" >> "$GITHUB_OUTPUT"
- name: Cache Playwright browsers
id: playwright-cache
uses: actions/cache@v4
with:
path: ~/.cache/ms-playwright
key: playwright-${{ runner.os }}-${{ steps.playwright-version.outputs.version }}
- name: Install Playwright browsers
if: steps.playwright-cache.outputs.cache-hit != 'true'
run: pnpm exec playwright install --with-deps chromium
- name: Install Playwright system dependencies
if: steps.playwright-cache.outputs.cache-hit == 'true'
run: pnpm exec playwright install-deps chromium
- name: Run browser-only test suite
run: pnpm run test:run:browser
type-check:
name: Type Check
runs-on: ubuntu-latest
needs: detect-change-scope
if: needs.detect-change-scope.outputs.lightweight_only != 'true'
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js and pnpm
uses: ./.github/actions/setup-node-pnpm
- name: Run type-check
run: pnpm run type-check
code-quality:
name: Code Quality
runs-on: ubuntu-latest
needs: detect-change-scope
if: needs.detect-change-scope.outputs.lightweight_only != 'true'
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js and pnpm
uses: ./.github/actions/setup-node-pnpm
- name: Run full validation
run: pnpm run validate
release-gate:
name: Release Gate
runs-on: ubuntu-latest
needs: [detect-change-scope, test, browser-test, type-check, code-quality]
if: always()
steps:
- name: Enforce required checks
run: |
if [[ "${{ needs.detect-change-scope.outputs.lightweight_only }}" == "true" ]]; then
echo "Non-runtime-only change detected; skipping heavy CI gates"
exit 0
fi
if [[ "${{ needs.test.result }}" != "success" ]]; then
echo "Test Suite must pass before release gating"
exit 1
fi
if [[ "${{ needs.browser-test.result }}" != "success" && "${{ needs.browser-test.result }}" != "skipped" ]]; then
echo "Browser Test Suite must pass before release gating"
exit 1
fi
if [[ "${{ needs.code-quality.result }}" != "success" ]]; then
echo "Code Quality must pass before release gating"
exit 1
fi
if [[ "${{ needs.type-check.result }}" != "success" ]]; then
echo "Type Check must pass before release gating"
exit 1
fi
echo "Required release checks passed"
data-refresh:
name: Data Download & Validation
runs-on: ubuntu-latest
needs: [detect-change-scope, test, type-check, code-quality]
if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
permissions:
contents: write
pull-requests: write
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js and pnpm
uses: ./.github/actions/setup-node-pnpm
- name: Download fresh Pokemon data
run: |
pnpm run scrape
- name: Run data integrity tests on fresh data
run: |
echo "🧪 Validating data integrity..."
pnpm vitest run tests/data-integrity.test.ts
- name: Display data summary
run: |
echo "📊 Data Summary:"
echo "Pokemon data files:"
find data -name "*.json" -type f | sort
echo ""
echo "File sizes:"
find data -name "*.json" -type f -exec du -h {} \;
- name: Upload data artifacts
uses: actions/upload-artifact@v4
with:
name: pokemon-data
path: |
data/**/*.json
retention-days: 30
- name: Check for data changes
id: data-changes
run: |
if [[ -n $(git status --porcelain data/) ]]; then
echo "changes=true" >> $GITHUB_OUTPUT
echo "Data files have been updated"
else
echo "changes=false" >> $GITHUB_OUTPUT
echo "No changes to data files"
fi
- name: Generate data update PR body
if: steps.data-changes.outputs.changes == 'true' && github.event_name == 'schedule'
run: pnpm run data:pr-body -- "$RUNNER_TEMP/data-refresh-pr-body.md"
- name: Create Pull Request with updated data
if: steps.data-changes.outputs.changes == 'true' && github.event_name == 'schedule'
uses: peter-evans/create-pull-request@c5a7806660adbe173f04e3e038b0ccdcd758773c # v6
with:
token: ${{ secrets.GITHUB_TOKEN }}
commit-message: 'chore: update Pokemon data files'
title: '🤖 Auto-update Pokemon data'
body-path: ${{ runner.temp }}/data-refresh-pr-body.md
branch: auto-update-data
delete-branch: true