Release: observability, error handling, and admin fixes #173
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # CI pipeline — runs on every PR to main and dev | |
| # Validates code quality, types, build, tests, and database integrity | |
| # | |
| # Minute-saving strategy: | |
| # - Path-based filtering skips expensive jobs when irrelevant files change | |
| # - E2E only runs on PRs to main (most expensive job at ~6 min) | |
| # - DB jobs only run when migration files change | |
| # - Schema drift detection moved to deploy-dev (informational only) | |
| # - Skipped jobs satisfy required status checks in GitHub rulesets | |
| # - npm ci with cache — reproducible installs, no lockfile deletion | |
| name: CI | |
| on: | |
| pull_request: | |
| branches: [main, dev] | |
| concurrency: | |
| group: ci-${{ github.head_ref || github.ref_name }} | |
| cancel-in-progress: true | |
| env: | |
| # Turborepo remote cache (optional — works without these) | |
| TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} | |
| TURBO_TEAM: ${{ vars.TURBO_TEAM }} | |
| # Filter to only packages affected since the PR base branch | |
| TURBO_FILTER: --filter=...[origin/${{ github.base_ref }}] | |
| jobs: | |
| # ─── Detect what changed ───────────────────────────────────── | |
| changes: | |
| name: Detect Changes | |
| runs-on: ubuntu-latest | |
| outputs: | |
| src: ${{ steps.filter.outputs.src }} | |
| migrations: ${{ steps.filter.outputs.migrations }} | |
| web: ${{ steps.filter.outputs.web }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: dorny/paths-filter@v3 | |
| id: filter | |
| with: | |
| filters: | | |
| src: | |
| - 'apps/**/*.{ts,tsx,js,jsx,css}' | |
| - 'packages/**/*.{ts,tsx,js,jsx}' | |
| - '!packages/db/types/database.ts' | |
| - '**/package.json' | |
| - '**/tsconfig*.json' | |
| - 'turbo.json' | |
| migrations: | |
| - 'packages/db/supabase/migrations/**' | |
| - 'packages/db/supabase/seed.sql' | |
| web: | |
| - 'apps/web/**' | |
| - 'packages/**' | |
| # ─── Lint & Type-check (parallel) ───────────────────────────── | |
| lint: | |
| name: Lint | |
| needs: [changes] | |
| if: needs.changes.outputs.src == 'true' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: 24 | |
| cache: npm | |
| - name: Install dependencies | |
| run: npm ci | |
| - name: Turbo lint | |
| run: npx turbo run lint ${{ env.TURBO_FILTER }} | |
| - name: Summary | |
| if: always() | |
| run: echo "### Lint ${{ job.status == 'success' && '✅' || '❌' }}" >> "$GITHUB_STEP_SUMMARY" | |
| typecheck: | |
| name: Type-check | |
| needs: [changes] | |
| if: needs.changes.outputs.src == 'true' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: 24 | |
| cache: npm | |
| - name: Install dependencies | |
| run: npm ci | |
| - name: Turbo check-types | |
| run: npx turbo run check-types ${{ env.TURBO_FILTER }} | |
| - name: Summary | |
| if: always() | |
| run: echo "### Type-check ${{ job.status == 'success' && '✅' || '❌' }}" >> "$GITHUB_STEP_SUMMARY" | |
| # ─── Build & Test (parallel, after lint+typecheck) ──────────── | |
| build: | |
| name: Build | |
| needs: [changes, lint, typecheck] | |
| if: needs.changes.outputs.src == 'true' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: 24 | |
| cache: npm | |
| - name: Install dependencies | |
| run: npm ci | |
| - name: Turbo build | |
| run: npx turbo run build ${{ env.TURBO_FILTER }} | |
| - name: Summary | |
| if: always() | |
| run: echo "### Build ${{ job.status == 'success' && '✅' || '❌' }}" >> "$GITHUB_STEP_SUMMARY" | |
| test: | |
| name: Test | |
| needs: [changes, lint, typecheck] | |
| if: needs.changes.outputs.src == 'true' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: 24 | |
| cache: npm | |
| - name: Install dependencies | |
| run: npm ci | |
| - uses: supabase/setup-cli@v1 | |
| with: | |
| version: latest | |
| - name: Start local Supabase (db + API only) | |
| run: supabase start -x realtime,storage,imgproxy,inbucket,studio,edge-runtime,logflare,vector,supavisor | |
| working-directory: packages/db | |
| - name: Reset DB (migrations + seed) | |
| run: supabase db reset | |
| working-directory: packages/db | |
| - name: Turbo test | |
| run: npx turbo run test ${{ env.TURBO_FILTER }} | |
| - name: Stop local Supabase | |
| if: always() | |
| run: supabase stop | |
| working-directory: packages/db | |
| - name: Summary | |
| if: always() | |
| run: echo "### Test ${{ job.status == 'success' && '✅' || '❌' }}" >> "$GITHUB_STEP_SUMMARY" | |
| # ─── E2E (main PRs only — most expensive job) ──────────────── | |
| e2e: | |
| name: E2E | |
| needs: [changes, lint, typecheck] | |
| if: github.base_ref == 'main' && needs.changes.outputs.web == 'true' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: 24 | |
| cache: npm | |
| - name: Install dependencies | |
| run: npm ci | |
| - uses: supabase/setup-cli@v1 | |
| with: | |
| version: latest | |
| - name: Start local Supabase | |
| run: supabase start -x imgproxy,inbucket,studio,edge-runtime,logflare,vector,supavisor | |
| working-directory: packages/db | |
| - name: Export Supabase keys | |
| id: supabase-keys | |
| run: | | |
| eval "$(supabase status -o env)" | |
| echo "anon_key=${ANON_KEY}" >> "$GITHUB_OUTPUT" | |
| echo "service_role_key=${SERVICE_ROLE_KEY}" >> "$GITHUB_OUTPUT" | |
| working-directory: packages/db | |
| - name: Reset DB (migrations + seed) | |
| run: supabase db reset | |
| working-directory: packages/db | |
| - name: Wait for Supabase services to be ready | |
| run: | | |
| for i in $(seq 1 30); do | |
| if curl -sf http://127.0.0.1:54321/rest/v1/ -H "apikey: $SUPABASE_ANON_KEY" > /dev/null 2>&1; then | |
| echo "Supabase is ready" | |
| exit 0 | |
| fi | |
| echo "Waiting for Supabase... ($i/30)" | |
| sleep 2 | |
| done | |
| echo "Supabase failed to start" | |
| exit 1 | |
| env: | |
| SUPABASE_ANON_KEY: ${{ steps.supabase-keys.outputs.anon_key }} | |
| - name: Install Playwright browsers (chromium) | |
| run: npx playwright install --with-deps chromium | |
| working-directory: apps/web | |
| - name: Build web app for E2E | |
| run: npx turbo run build --filter=web | |
| env: | |
| NEXT_PUBLIC_SUPABASE_URL: http://127.0.0.1:54321 | |
| NEXT_PUBLIC_SUPABASE_PUB_KEY: ${{ steps.supabase-keys.outputs.anon_key }} | |
| SUPABASE_SECRET_KEY: ${{ steps.supabase-keys.outputs.service_role_key }} | |
| - name: Run Playwright tests | |
| run: npx playwright test | |
| working-directory: apps/web | |
| env: | |
| NEXT_PUBLIC_SUPABASE_URL: http://127.0.0.1:54321 | |
| NEXT_PUBLIC_SUPABASE_PUB_KEY: ${{ steps.supabase-keys.outputs.anon_key }} | |
| SUPABASE_SECRET_KEY: ${{ steps.supabase-keys.outputs.service_role_key }} | |
| - name: Upload Playwright report | |
| if: failure() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: playwright-report | |
| path: apps/web/playwright-report/ | |
| retention-days: 7 | |
| - name: Stop local Supabase | |
| if: always() | |
| run: supabase stop | |
| working-directory: packages/db | |
| - name: Summary | |
| if: always() | |
| run: echo "### E2E ${{ job.status == 'success' && '✅' || '❌' }}" >> "$GITHUB_STEP_SUMMARY" | |
| # ─── Supabase database checks (only when migrations change) ── | |
| db-lint: | |
| name: DB Lint | |
| needs: [changes] | |
| if: needs.changes.outputs.migrations == 'true' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: supabase/setup-cli@v1 | |
| with: | |
| version: latest | |
| - name: Link to dev project | |
| run: supabase link --project-ref "${{ secrets.SUPABASE_DEV_PROJECT_REF }}" | |
| working-directory: packages/db | |
| env: | |
| SUPABASE_ACCESS_TOKEN: ${{ secrets.SUPABASE_ACCESS_TOKEN }} | |
| - name: Lint migrations | |
| run: supabase db lint --linked | |
| working-directory: packages/db | |
| env: | |
| SUPABASE_ACCESS_TOKEN: ${{ secrets.SUPABASE_ACCESS_TOKEN }} | |
| - name: Summary | |
| if: always() | |
| run: echo "### DB Lint ${{ job.status == 'success' && '✅' || '❌' }}" >> "$GITHUB_STEP_SUMMARY" | |
| migration-dry-run: | |
| name: Migration Dry Run | |
| needs: [changes] | |
| if: needs.changes.outputs.migrations == 'true' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: supabase/setup-cli@v1 | |
| with: | |
| version: latest | |
| - name: Start local Supabase (db only) | |
| run: supabase start -x realtime,storage,imgproxy,inbucket,postgrest,gotrue,studio,edge-runtime,logflare,vector,supavisor | |
| working-directory: packages/db | |
| - name: Reset DB (replay all migrations + seed) | |
| run: supabase db reset | |
| working-directory: packages/db | |
| - name: Stop local Supabase | |
| if: always() | |
| run: supabase stop | |
| working-directory: packages/db | |
| - name: Summary | |
| if: always() | |
| run: echo "### Migration Dry Run ${{ job.status == 'success' && '✅' || '❌' }}" >> "$GITHUB_STEP_SUMMARY" |