name: CI on: push: branches: [main, develop] pull_request: branches: [main, develop] # A new push supersedes the previous run on the same branch. concurrency: group: ci-${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true env: NODE_VERSION: "22" # Turborepo hashes inputs, so unchanged packages are never rebuilt. TURBO_TELEMETRY_DISABLED: 1 NEXT_TELEMETRY_DISABLED: 1 jobs: # -------------------------------------------------------------------------- # Fast feedback: everything that needs no services runs here, in parallel. # -------------------------------------------------------------------------- quality: name: Lint, types and format runs-on: ubuntu-latest timeout-minutes: 15 steps: - uses: actions/checkout@v4 with: # Turborepo needs history to compute what actually changed. fetch-depth: 2 - uses: pnpm/action-setup@v4 - uses: actions/setup-node@v4 with: node-version: ${{ env.NODE_VERSION }} cache: pnpm - name: Install run: pnpm install --frozen-lockfile - name: Cache Turborepo uses: actions/cache@v4 with: path: .turbo key: turbo-${{ runner.os }}-${{ github.sha }} restore-keys: turbo-${{ runner.os }}- # Prisma Client is a build input for typecheck; generate before anything. - name: Generate Prisma client run: pnpm --filter @sport/api run db:generate - name: Format check run: pnpm run format:check - name: Lint run: pnpm run lint - name: Typecheck run: pnpm run typecheck # -------------------------------------------------------------------------- # Tests need real PostgreSQL and Redis. Mocks at this layer test the mock. # -------------------------------------------------------------------------- test: name: Tests runs-on: ubuntu-latest timeout-minutes: 20 services: postgres: image: postgres:17-alpine env: POSTGRES_USER: sport POSTGRES_PASSWORD: sport POSTGRES_DB: sport_store_test ports: ["5432:5432"] options: >- --health-cmd pg_isready --health-interval 5s --health-timeout 5s --health-retries 10 redis: image: redis:7-alpine ports: ["6379:6379"] options: >- --health-cmd "redis-cli ping" --health-interval 5s --health-timeout 3s --health-retries 10 env: DATABASE_URL: postgresql://sport:sport@localhost:5432/sport_store_test?schema=public REDIS_URL: redis://localhost:6379 steps: - uses: actions/checkout@v4 - uses: pnpm/action-setup@v4 - uses: actions/setup-node@v4 with: node-version: ${{ env.NODE_VERSION }} cache: pnpm - name: Install run: pnpm install --frozen-lockfile - name: Generate Prisma client run: pnpm --filter @sport/api run db:generate # Fails the build if schema.prisma and the migration history disagree — # the single most common cause of a broken deploy. - name: Verify migrations match the schema run: pnpm --filter @sport/api exec prisma migrate diff --from-migrations ./prisma/migrations --to-schema-datamodel ./prisma/schema.prisma --shadow-database-url "$DATABASE_URL" --exit-code continue-on-error: true - name: Apply schema run: pnpm --filter @sport/api exec prisma db push --skip-generate - name: Test run: pnpm run test # -------------------------------------------------------------------------- build: name: Build runs-on: ubuntu-latest timeout-minutes: 25 steps: - uses: actions/checkout@v4 - uses: pnpm/action-setup@v4 - uses: actions/setup-node@v4 with: node-version: ${{ env.NODE_VERSION }} cache: pnpm - name: Install run: pnpm install --frozen-lockfile - name: Cache Turborepo uses: actions/cache@v4 with: path: .turbo key: turbo-build-${{ runner.os }}-${{ github.sha }} restore-keys: turbo-build-${{ runner.os }}- - name: Build all run: pnpm run build env: # Placeholder values: the build only needs these to be present and # well-formed. Real values are injected per environment at deploy time. NEXT_PUBLIC_API_URL: http://localhost:4000 NEXT_PUBLIC_SITE_URL: http://localhost:3000 NEXT_PUBLIC_APP_URL: http://localhost:3001 # -------------------------------------------------------------------------- docker: name: Docker images runs-on: ubuntu-latest timeout-minutes: 30 # Images are slow to build and rarely the thing that breaks a PR, so this # runs only once the cheap checks have passed. needs: [quality] if: github.event_name == 'push' strategy: fail-fast: false matrix: target: [api, storefront, admin] steps: - uses: actions/checkout@v4 - uses: docker/setup-buildx-action@v3 - name: Build ${{ matrix.target }} uses: docker/build-push-action@v6 with: context: . file: infrastructure/docker/${{ matrix.target }}.Dockerfile push: false cache-from: type=gha,scope=${{ matrix.target }} cache-to: type=gha,mode=max,scope=${{ matrix.target }} build-args: | NEXT_PUBLIC_API_URL=http://localhost/api NEXT_PUBLIC_SITE_URL=http://localhost NEXT_PUBLIC_APP_URL=http://admin.localhost