name: CI on: pull_request: push: branches: - main jobs: infra: name: Infra (Compose + Scripts) runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 - name: Validate Docker Compose config run: docker compose -f infra/docker-compose.yml config -q - name: Validate Docker Compose with .env.example run: docker compose -f infra/docker-compose.yml --env-file .env.example config -q - name: Validate startup scripts run: | bash -n start-unified.sh bash -n start.sh - name: Run startup preflight tests run: bash scripts/test-start-unified-preflight.sh backend: name: Backend (Go) runs-on: ubuntu-latest defaults: run: working-directory: app/backend steps: - name: Checkout uses: actions/checkout@v4 - name: Setup Go uses: actions/setup-go@v5 with: go-version-file: app/backend/go.mod cache: true - name: Run backend tests run: go test ./... frontend: name: Frontend (React) runs-on: ubuntu-latest defaults: run: working-directory: app/frontend steps: - name: Checkout uses: actions/checkout@v4 - name: Setup Node uses: actions/setup-node@v4 with: node-version: "22" cache: npm cache-dependency-path: app/frontend/package-lock.json - name: Install dependencies run: npm ci - name: Lint run: npm run lint - name: Build (typecheck + bundle) run: npm run build:check - name: Test run: npm test smoke: name: Compose boot smoke runs-on: ubuntu-latest timeout-minutes: 20 steps: - name: Checkout uses: actions/checkout@v4 - name: Boot stack run: | docker compose -f infra/docker-compose.yml up -d --build postgres redis backend frontend traefik - name: Wait for backend health run: | for i in $(seq 1 60); do if curl -sf http://localhost:8082/health; then exit 0; fi sleep 5 done docker compose -f infra/docker-compose.yml logs --tail 50 backend exit 1 - name: Check frontend responds run: curl -sf http://localhost:3000/ -o /dev/null - name: Teardown if: always() run: docker compose -f infra/docker-compose.yml down -v images: name: Publish images to GHCR runs-on: ubuntu-latest needs: [backend, frontend, smoke] if: startsWith(github.ref, 'refs/tags/v') permissions: packages: write contents: read steps: - name: Checkout uses: actions/checkout@v4 - name: Login to GHCR uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Build and push backend uses: docker/build-push-action@v6 with: context: app/backend push: true tags: ghcr.io/${{ github.repository }}-backend:${{ github.ref_name }},ghcr.io/${{ github.repository }}-backend:latest - name: Build and push frontend uses: docker/build-push-action@v6 with: context: app/frontend push: true tags: ghcr.io/${{ github.repository }}-frontend:${{ github.ref_name }},ghcr.io/${{ github.repository }}-frontend:latest