Add Expo mobile app with multi-tenant support #126
Workflow file for this run
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 | |
| 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: | |
| # ─── Lint & Type-check (parallel) ───────────────────────────── | |
| lint: | |
| name: Lint | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| # Fetch base branch so turbo filter can diff against it | |
| fetch-depth: 0 | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: 24 | |
| - name: Install dependencies | |
| run: corepack disable && rm package-lock.json && npm install --no-audit --no-fund | |
| - 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 | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: 24 | |
| - name: Install dependencies | |
| run: corepack disable && rm package-lock.json && npm install --no-audit --no-fund | |
| - 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: [lint, typecheck] | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: 24 | |
| - name: Install dependencies | |
| run: corepack disable && rm package-lock.json && npm install --no-audit --no-fund | |
| - 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: [lint, typecheck] | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: 24 | |
| - name: Install dependencies | |
| run: corepack disable && rm package-lock.json && npm install --no-audit --no-fund | |
| - 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 (after lint+typecheck) ─────────────────────────────── | |
| e2e: | |
| name: E2E | |
| needs: [lint, typecheck] | |
| 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 | |
| - name: Install dependencies | |
| run: corepack disable && rm package-lock.json && npm install --no-audit --no-fund | |
| - 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: sb_publishable_ACJWlzQHlZjBrEguHvfOxg_3BJgxAaH | |
| SUPABASE_SECRET_KEY: sb_secret_N7UND0UgjKTVK-Uodkm0Hg_xSvEMPvz | |
| - 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 ──────────────────────────────── | |
| db-lint: | |
| name: DB Lint | |
| 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" | |
| schema-drift: | |
| name: Schema Drift Detection | |
| 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: Check for schema drift | |
| # Warn-only — non-zero exit is informational, not blocking | |
| continue-on-error: true | |
| run: | | |
| OUTPUT=$(supabase db diff --linked 2>&1) || true | |
| if [ -n "$OUTPUT" ] && [ "$OUTPUT" != "No changes found" ]; then | |
| echo "::warning::Schema drift detected against dev project" | |
| echo "### ⚠️ Schema Drift Detected" >> "$GITHUB_STEP_SUMMARY" | |
| echo '```sql' >> "$GITHUB_STEP_SUMMARY" | |
| echo "$OUTPUT" >> "$GITHUB_STEP_SUMMARY" | |
| echo '```' >> "$GITHUB_STEP_SUMMARY" | |
| else | |
| echo "### Schema Drift — None ✅" >> "$GITHUB_STEP_SUMMARY" | |
| fi | |
| working-directory: packages/db | |
| env: | |
| SUPABASE_ACCESS_TOKEN: ${{ secrets.SUPABASE_ACCESS_TOKEN }} | |
| SUPABASE_DB_PASSWORD: ${{ secrets.SUPABASE_DEV_DB_PASSWORD }} | |
| migration-dry-run: | |
| name: Migration Dry Run | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: supabase/setup-cli@v1 | |
| with: | |
| version: latest | |
| - name: Start local Supabase (db only) | |
| # Only start the db service — skip realtime, storage, studio, etc. for speed | |
| 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" |