name: CI on: push: branches: [main] pull_request: types: [opened, synchronize] concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: lint-and-typecheck: name: Lint & Typecheck runs-on: ubuntu-latest timeout-minutes: 10 env: TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} TURBO_TEAM: ${{ vars.TURBO_TEAM }} steps: - name: Checkout uses: actions/checkout@v4 with: fetch-depth: 2 - name: Setup pnpm uses: pnpm/action-setup@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: 22 cache: 'pnpm' - name: Cache Turborepo uses: actions/cache@v4 with: path: .turbo key: ${{ runner.os }}-turbo-${{ hashFiles('**/pnpm-lock.yaml') }}-${{ github.sha }} restore-keys: | ${{ runner.os }}-turbo-${{ hashFiles('**/pnpm-lock.yaml') }}- ${{ runner.os }}-turbo- - name: Install dependencies run: pnpm install --frozen-lockfile - name: Lint run: pnpm lint - name: Typecheck run: pnpm typecheck test: name: Test (${{ matrix.group }}) runs-on: ubuntu-latest timeout-minutes: 15 env: TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} TURBO_TEAM: ${{ vars.TURBO_TEAM }} strategy: fail-fast: false matrix: group: [unit, services, routes, security] steps: - name: Checkout uses: actions/checkout@v4 with: fetch-depth: 2 - name: Setup pnpm uses: pnpm/action-setup@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: 22 cache: 'pnpm' - name: Cache Turborepo uses: actions/cache@v4 with: path: .turbo key: ${{ runner.os }}-turbo-${{ hashFiles('**/pnpm-lock.yaml') }}-${{ github.sha }} restore-keys: | ${{ runner.os }}-turbo-${{ hashFiles('**/pnpm-lock.yaml') }}- ${{ runner.os }}-turbo- - name: Install dependencies run: pnpm install --frozen-lockfile - name: Build dependencies (shared, test-utils) run: pnpm turbo run build --filter=@tracearr/shared --filter=@tracearr/test-utils - name: Run ${{ matrix.group }} tests run: pnpm --filter @tracearr/server test:${{ matrix.group }} # Integration tests require real database - run separately from unit tests test-integration: name: Test (integration) runs-on: ubuntu-latest timeout-minutes: 15 env: TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} TURBO_TEAM: ${{ vars.TURBO_TEAM }} TEST_DATABASE_URL: postgresql://test:test@localhost:5433/tracearr_test TEST_REDIS_URL: redis://localhost:6380 services: timescale: image: timescale/timescaledb:latest-pg15 ports: - 5433:5432 env: POSTGRES_USER: test POSTGRES_PASSWORD: test POSTGRES_DB: tracearr_test options: >- --health-cmd "pg_isready -U test -d tracearr_test" --health-interval 5s --health-timeout 3s --health-retries 10 redis: image: redis:7-alpine ports: - 6380:6379 options: >- --health-cmd "redis-cli ping" --health-interval 5s --health-timeout 3s --health-retries 5 steps: - name: Checkout uses: actions/checkout@v4 with: fetch-depth: 2 - name: Setup pnpm uses: pnpm/action-setup@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: 22 cache: 'pnpm' - name: Cache Turborepo uses: actions/cache@v4 with: path: .turbo key: ${{ runner.os }}-turbo-${{ hashFiles('**/pnpm-lock.yaml') }}-${{ github.sha }} restore-keys: | ${{ runner.os }}-turbo-${{ hashFiles('**/pnpm-lock.yaml') }}- ${{ runner.os }}-turbo- - name: Install dependencies run: pnpm install --frozen-lockfile - name: Build dependencies (shared, test-utils) run: pnpm turbo run build --filter=@tracearr/shared --filter=@tracearr/test-utils - name: Run integration tests run: pnpm --filter @tracearr/server test:integration # Separate job for coverage reporting (runs all tests together for accurate coverage) # Runs in parallel with test matrix - if tests pass there, coverage will too test-coverage: name: Test Coverage runs-on: ubuntu-latest timeout-minutes: 15 env: TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} TURBO_TEAM: ${{ vars.TURBO_TEAM }} steps: - name: Checkout uses: actions/checkout@v4 with: fetch-depth: 2 - name: Setup pnpm uses: pnpm/action-setup@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: 22 cache: 'pnpm' - name: Cache Turborepo uses: actions/cache@v4 with: path: .turbo key: ${{ runner.os }}-turbo-${{ hashFiles('**/pnpm-lock.yaml') }}-${{ github.sha }} restore-keys: | ${{ runner.os }}-turbo-${{ hashFiles('**/pnpm-lock.yaml') }}- ${{ runner.os }}-turbo- - name: Install dependencies run: pnpm install --frozen-lockfile - name: Build dependencies (shared, test-utils) run: pnpm turbo run build --filter=@tracearr/shared --filter=@tracearr/test-utils - name: Run tests with coverage run: pnpm test:coverage - name: Upload coverage reports uses: actions/upload-artifact@v4 if: always() with: name: coverage-report path: apps/server/coverage/ retention-days: 7 if-no-files-found: warn - name: Report coverage to PR if: github.event_name == 'pull_request' uses: davelosert/vitest-coverage-report-action@v2 with: working-directory: apps/server vite-config-path: vitest.config.ts build: name: Build runs-on: ubuntu-latest timeout-minutes: 15 needs: [lint-and-typecheck, test, test-integration, test-coverage] env: TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} TURBO_TEAM: ${{ vars.TURBO_TEAM }} steps: - name: Checkout uses: actions/checkout@v4 with: fetch-depth: 2 - name: Setup pnpm uses: pnpm/action-setup@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: 22 cache: 'pnpm' - name: Cache Turborepo uses: actions/cache@v4 with: path: .turbo key: ${{ runner.os }}-turbo-${{ hashFiles('**/pnpm-lock.yaml') }}-${{ github.sha }} restore-keys: | ${{ runner.os }}-turbo-${{ hashFiles('**/pnpm-lock.yaml') }}- ${{ runner.os }}-turbo- - name: Install dependencies run: pnpm install --frozen-lockfile - name: Build run: pnpm build