name: CI on: push: branches: [main] paths-ignore: - "docs/**" - ".gitea/**" - "**/*.md" - "LICENSE" pull_request: branches: [main] paths-ignore: - "docs/**" - ".gitea/**" - "**/*.md" - "LICENSE" concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true env: NODE_VERSION: "20" PNPM_VERSION: "9.14.2" CI_AUTH_URL: http://localhost:3100 CI_AUTH_SECRET: ${{ secrets.CI_AUTH_SECRET }} jobs: guardrails: name: Architecture Guardrails runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Install pnpm run: npm install -g pnpm@${{ env.PNPM_VERSION }} - uses: actions/setup-node@v4 with: node-version: ${{ env.NODE_VERSION }} cache: pnpm - name: Install dependencies run: pnpm install --frozen-lockfile - name: Run repo script tests run: pnpm test:scripts - name: Check architecture guardrails run: pnpm check:architecture - name: Check workspace exports run: pnpm check:exports - name: Check workspace imports run: pnpm check:imports - name: Security audit (high+ severity) run: pnpm audit --audit-level=high # ────────────────────────────────────────────── # Typecheck — ~40s, no services needed # ────────────────────────────────────────────── typecheck: name: Typecheck runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Install pnpm run: npm install -g pnpm@${{ env.PNPM_VERSION }} - uses: actions/setup-node@v4 with: node-version: ${{ env.NODE_VERSION }} cache: pnpm - name: Install dependencies run: pnpm install --frozen-lockfile - name: Generate Prisma client run: pnpm db:generate - name: Cache Turborepo uses: actions/cache@v4 with: path: .turbo key: turbo-typecheck-${{ github.sha }} restore-keys: turbo-typecheck- - name: Run typecheck run: pnpm typecheck assistant-split: name: Assistant Split Regression runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Install pnpm run: npm install -g pnpm@${{ env.PNPM_VERSION }} - uses: actions/setup-node@v4 with: node-version: ${{ env.NODE_VERSION }} cache: pnpm - name: Install dependencies run: pnpm install --frozen-lockfile - name: Generate Prisma client run: pnpm db:generate - name: Run assistant split regression run: pnpm --filter @capakraken/api test:assistant-split # ────────────────────────────────────────────── # Lint — ~20s, no services needed # ────────────────────────────────────────────── lint: name: Lint runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Install pnpm run: npm install -g pnpm@${{ env.PNPM_VERSION }} - uses: actions/setup-node@v4 with: node-version: ${{ env.NODE_VERSION }} cache: pnpm - name: Install dependencies run: pnpm install --frozen-lockfile - name: Generate Prisma client run: pnpm db:generate - name: Cache Turborepo uses: actions/cache@v4 with: path: .turbo key: turbo-lint-${{ github.sha }} restore-keys: turbo-lint- - name: Run lint run: pnpm lint # ────────────────────────────────────────────── # Unit tests — needs PostgreSQL + Redis # ────────────────────────────────────────────── test: name: Unit Tests runs-on: ubuntu-latest services: postgres: image: postgres:16 env: POSTGRES_DB: capakraken_test POSTGRES_USER: capakraken POSTGRES_PASSWORD: capakraken_test options: >- --health-cmd="pg_isready -U capakraken -d capakraken_test" --health-interval=10s --health-timeout=5s --health-retries=5 redis: image: redis:7 options: >- --health-cmd="redis-cli ping" --health-interval=10s --health-timeout=5s --health-retries=5 env: DATABASE_URL: postgresql://capakraken:capakraken_test@postgres:5432/capakraken_test REDIS_URL: redis://redis:6379 NEXTAUTH_URL: ${{ env.CI_AUTH_URL }} AUTH_URL: ${{ env.CI_AUTH_URL }} NEXTAUTH_SECRET: ${{ env.CI_AUTH_SECRET }} AUTH_SECRET: ${{ env.CI_AUTH_SECRET }} steps: - uses: actions/checkout@v4 - name: Install pnpm run: npm install -g pnpm@${{ env.PNPM_VERSION }} - uses: actions/setup-node@v4 with: node-version: ${{ env.NODE_VERSION }} cache: pnpm - name: Install dependencies run: pnpm install --frozen-lockfile - name: Generate Prisma client run: pnpm db:generate - name: Run unit tests with coverage run: | pnpm --filter @capakraken/web test:unit -- --coverage pnpm --filter @capakraken/engine exec vitest run --coverage pnpm --filter @capakraken/staffing exec vitest run --coverage pnpm --filter @capakraken/api exec vitest run --coverage pnpm --filter @capakraken/application exec vitest run --coverage pnpm --filter @capakraken/shared exec vitest run --coverage pnpm --filter @capakraken/db test:unit - name: Upload coverage reports uses: actions/upload-artifact@v4 if: ${{ !cancelled() }} with: name: coverage-reports path: | apps/web/coverage/ packages/engine/coverage/ packages/staffing/coverage/ packages/api/coverage/ packages/application/coverage/ packages/shared/coverage/ retention-days: 14 # ────────────────────────────────────────────── # Build — depends on typecheck passing # ────────────────────────────────────────────── build: name: Build needs: [guardrails, typecheck] runs-on: ubuntu-latest env: DATABASE_URL: postgresql://placeholder:placeholder@localhost:5432/placeholder NEXTAUTH_URL: ${{ env.CI_AUTH_URL }} AUTH_URL: ${{ env.CI_AUTH_URL }} NEXTAUTH_SECRET: ${{ env.CI_AUTH_SECRET }} AUTH_SECRET: ${{ env.CI_AUTH_SECRET }} steps: - uses: actions/checkout@v4 - name: Install pnpm run: npm install -g pnpm@${{ env.PNPM_VERSION }} - uses: actions/setup-node@v4 with: node-version: ${{ env.NODE_VERSION }} cache: pnpm - name: Install dependencies run: pnpm install --frozen-lockfile - name: Generate Prisma client run: pnpm db:generate - name: Cache Turborepo uses: actions/cache@v4 with: path: .turbo key: turbo-build-${{ github.sha }} restore-keys: turbo-build- - name: Cache Next.js build uses: actions/cache@v4 with: path: apps/web/.next/cache key: nextjs-${{ hashFiles('pnpm-lock.yaml') }}-${{ github.sha }} restore-keys: nextjs-${{ hashFiles('pnpm-lock.yaml') }}- - name: Build run: pnpm --filter @capakraken/web exec next build # ────────────────────────────────────────────── # E2E — depends on build, needs PostgreSQL + Redis # ────────────────────────────────────────────── e2e: name: E2E Tests needs: [build] runs-on: ubuntu-latest services: postgres: image: postgres:16 env: POSTGRES_DB: capakraken_test POSTGRES_USER: capakraken POSTGRES_PASSWORD: capakraken_test options: >- --health-cmd="pg_isready -U capakraken -d capakraken_test" --health-interval=10s --health-timeout=5s --health-retries=5 redis: image: redis:7 options: >- --health-cmd="redis-cli ping" --health-interval=10s --health-timeout=5s --health-retries=5 env: DATABASE_URL: postgresql://capakraken:capakraken_test@postgres:5432/capakraken_test ALLOW_DESTRUCTIVE_DB_TOOLS: "true" CONFIRM_DESTRUCTIVE_DB_NAME: capakraken_test REDIS_URL: redis://redis:6379 PORT: 3100 NEXTAUTH_URL: ${{ env.CI_AUTH_URL }} AUTH_URL: ${{ env.CI_AUTH_URL }} NEXTAUTH_SECRET: ${{ env.CI_AUTH_SECRET }} AUTH_SECRET: ${{ env.CI_AUTH_SECRET }} steps: - uses: actions/checkout@v4 - name: Install pnpm run: npm install -g pnpm@${{ env.PNPM_VERSION }} - uses: actions/setup-node@v4 with: node-version: ${{ env.NODE_VERSION }} cache: pnpm - name: Install dependencies run: pnpm install --frozen-lockfile - name: Generate Prisma client run: pnpm db:generate - name: Cache Playwright browsers id: playwright-cache uses: actions/cache@v4 with: path: ~/.cache/ms-playwright key: playwright-${{ hashFiles('apps/web/package.json') }} restore-keys: playwright- - name: Install Playwright browsers if: steps.playwright-cache.outputs.cache-hit != 'true' run: pnpm --filter @capakraken/web exec playwright install --with-deps chromium - name: Install Playwright system deps if: steps.playwright-cache.outputs.cache-hit == 'true' run: pnpm --filter @capakraken/web exec playwright install-deps chromium - name: Push DB schema & seed run: | pnpm db:push pnpm db:seed - name: Run E2E tests run: pnpm test:e2e - name: Upload Playwright report uses: actions/upload-artifact@v4 if: ${{ !cancelled() }} with: name: playwright-report path: apps/web/playwright-report/ retention-days: 14 # ────────────────────────────────────────────── # Fresh Docker Compose deploy test — validates # that the prod compose bundle comes up clean # from scratch and the smoke tests pass. # ────────────────────────────────────────────── docker-deploy-test: name: Fresh-Linux Docker Deploy needs: [build] runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Create minimal .env run: | cat <<'EOF' > .env NEXTAUTH_URL=http://localhost:3100 NEXTAUTH_SECRET=ci-test-secret-minimum-32-chars-xx PGADMIN_PASSWORD=ci-pgadmin EOF - name: Start infrastructure (postgres + redis) run: docker compose up -d postgres redis - name: Wait for postgres run: | for i in $(seq 1 20); do docker compose exec -T postgres pg_isready -U capakraken -d capakraken && break sleep 3 done - name: Build and start app (full profile) run: docker compose --profile full up -d --build app - name: Wait for /api/health (up to 3 minutes) run: | for i in $(seq 1 36); do STATUS=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:3100/api/health || echo "000") echo "Attempt $i: HTTP $STATUS" if [ "$STATUS" = "200" ]; then exit 0; fi sleep 5 done echo "Health check timed out" docker compose logs app --tail=50 exit 1 - name: Verify health response contains status ok run: | BODY=$(curl -sf http://localhost:3100/api/health) echo "$BODY" echo "$BODY" | grep '"status":"ok"' - name: Seed admin user run: | docker compose exec -T app node /app/scripts/setup-admin.mjs \ --email admin@capakraken.dev \ --name "Admin" \ --password admin123 - name: Set up Node.js 20 uses: actions/setup-node@v4 with: node-version: "20" - name: Install Playwright and Chromium run: | npm install -g @playwright/test@1.49 playwright install chromium --with-deps - name: Run smoke tests run: npx playwright test --config apps/web/playwright.ci.config.ts - name: Upload Playwright report if: failure() uses: actions/upload-artifact@v4 with: name: playwright-smoke-report path: apps/web/playwright-report/ retention-days: 7 - name: Show logs on failure if: failure() run: docker compose logs --tail=100 # ────────────────────────────────────────────── # Release images — only on push to main, after # every check has passed. Calls the reusable # release-image.yml workflow. # ────────────────────────────────────────────── release-images: name: Release Images if: github.event_name == 'push' && github.ref == 'refs/heads/main' needs: [lint, test, e2e, assistant-split, docker-deploy-test] uses: ./.github/workflows/release-image.yml secrets: inherit