CICD - Unit | Integration | E2E #9
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/CD Pipeline | |
| # ============================================================================= | |
| # | |
| # Two-stage pipeline that gates merges to main: | |
| # Stage 1 (test): Unit + integration tests on GH Actions runner | |
| # [TODO] Stage 2 (e2e): Playwright E2E against a real staging deployment | |
| # | |
| # Both stages must pass before a PR can be merged to main. | |
| # Merging to main triggers production deploy via Coolify (existing setup). | |
| # | |
| # REQUIRED BRANCH PROTECTION RULES ON main: | |
| # - Require status checks for unit-integration and e2e | |
| # - Require branches to be up to date before merging | |
| # ============================================================================= | |
| name: CI Pipeline | |
| on: | |
| pull_request: | |
| branches: [main] | |
| # ============================================================================= | |
| # WHY NO WORKFLOW-LEVEL CONCURRENCY: | |
| # | |
| # We need different concurrency strategies per job: | |
| # - unit-integration: cancel-in-progress per PR (fast feedback on new pushes) | |
| # - e2e: queue globally (staging is a shared single resource) | |
| # | |
| # Workflow-level concurrency would force one strategy for both. | |
| # ============================================================================= | |
| jobs: | |
| # =========================================================================== | |
| # STAGE 1: Unit & Integration Tests | |
| # =========================================================================== | |
| # | |
| # Runs Vitest against a local Supabase subset. | |
| # | |
| # Concurrency: cancel-in-progress PER PR. If you push again to the same PR | |
| # while tests are running, the old run is killed immediately. No point | |
| # finishing tests on stale code. | |
| # =========================================================================== | |
| unit-integration: | |
| name: Unit & Integration Tests | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| pull-requests: write | |
| concurrency: | |
| group: test-pr-${{ github.event.pull_request.number }} | |
| cancel-in-progress: true | |
| # ------------------------------------------------------------------------- | |
| # maximum amount of time a job or step can run before GitHub automatically cancels it | |
| # ------------------------------------------------------------------------- | |
| timeout-minutes: 20 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| # RLIMIT ISSUE temp fix | |
| # this step is only necessary because of an issue with Docker and Supabase where it cannot set the `ulimit`. | |
| # this step sets the `ulimit` higher than the Supabase supavisor container's 100000, to prevent an "Operation not permitted" | |
| # error stopping the supavisor from starting. | |
| # https://github.com/supabase/cli/issues/4443 | |
| - name: Configure system limits | |
| run: | | |
| echo "This is due to a bug with Supabase and GitHub runner: https://github.com/supabase/cli/issues/4443" | |
| # Set ulimit for system (higher than Supabase's 100000) | |
| # Run ulimit within a root shell since it's a builtin and cannot be run like `sudo ulimit` | |
| sudo bash -c 'ulimit -n 200000 && ulimit -u 16384' | |
| # Configure Docker daemon with merged config | |
| DOCKER_CONFIG="/etc/docker/daemon.json" | |
| # Read existing config or start with empty object | |
| if [ -f "$DOCKER_CONFIG" ]; then | |
| EXISTING_CONFIG=$(cat "$DOCKER_CONFIG") | |
| else | |
| EXISTING_CONFIG="{}" | |
| fi | |
| # Merge new limits with existing config using jq (higher than Supabase's 100000) | |
| echo "$EXISTING_CONFIG" | jq '. + { | |
| "default-ulimits": { | |
| "nofile": { | |
| "Name": "nofile", | |
| "Hard": 200000, | |
| "Soft": 200000 | |
| }, | |
| "nproc": { | |
| "Name": "nproc", | |
| "Hard": 16384, | |
| "Soft": 16384 | |
| } | |
| } | |
| }' | sudo tee "$DOCKER_CONFIG" > /dev/null | |
| # Restart Docker to apply changes | |
| sudo systemctl restart docker | |
| # Wait for Docker to be ready | |
| timeout 30 sh -c 'until docker info > /dev/null 2>&1; do sleep 1; done' | |
| - uses: actions/setup-node@v4 | |
| # use Node version enforced for the project | |
| # see https://github.com/actions/setup-node | |
| with: | |
| node-version-file: '.nvmrc' | |
| cache: "npm" | |
| - name: Install dependencies | |
| run: npm ci | |
| # load the env variables for the sveltekit app | |
| # (slightly) different from .env.ci | |
| - name: Load app env | |
| run: cat cicd/.env.app.ci >> "$GITHUB_ENV" | |
| # ----------------------------------------------------------------------- | |
| - name: Run Unit Tests | |
| run: npm run test:unit | |
| - name: Run Integration Tests | |
| if: always() | |
| run: npm run test:integration | |
| - name: Upload unit coverage | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: coverage-unit | |
| path: reports/coverage/unit | |
| retention-days: 7 | |
| - name: Upload integration coverage | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: coverage-integration | |
| path: reports/coverage/integration | |
| retention-days: 7 | |
| # https://github.com/davelosert/vitest-coverage-report-action | |
| - name: Report Unit Coverage | |
| # Set if: always() to also generate the report if tests are failing | |
| # Only works if you set `reportOnFailure: true` in your vite config as specified above | |
| if: always() | |
| uses: davelosert/vitest-coverage-report-action@v2 | |
| with: | |
| name: Unit | |
| json-summary-path: reports/coverage/unit/coverage-summary.json | |
| json-final-path: reports/coverage/unit/coverage-final.json | |
| vite-config-path: vitest.config.unit.ts | |
| - name: Report Integration Coverage | |
| if: always() | |
| uses: davelosert/vitest-coverage-report-action@v2 | |
| with: | |
| name: Integration | |
| json-summary-path: reports/coverage/integration/coverage-summary.json | |
| json-final-path: reports/coverage/integration/coverage-final.json | |
| vite-config-path: vitest.config.integration.ts |