name: CI on: push: pull_request: concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true jobs: quality: name: Quality Gates runs-on: ubuntu-latest timeout-minutes: 45 env: DATABASE_URL: postgresql://toolctrl:toolctrl@localhost:5432/toolctrl?schema=public steps: - name: Checkout code uses: actions/checkout@v4 - name: Enable Corepack run: corepack enable - name: Prepare pnpm 10.33.0 run: corepack prepare pnpm@10.33.0 --activate - name: Configure pnpm store run: pnpm config set store-dir .pnpm-store - name: Cache pnpm store uses: actions/cache@v4 with: path: .pnpm-store key: ${{ runner.os }}-pnpm-${{ hashFiles('pnpm-lock.yaml') }} restore-keys: | ${{ runner.os }}-pnpm- - name: Install dependencies run: pnpm install --frozen-lockfile --prefer-offline - name: Verify GitHub CI workflow run: pnpm run verify:github-ci-workflow - name: Verify Gitea CI workflow run: pnpm run verify:gitea-ci-workflow - name: Verify Gitea deploy workflow run: pnpm run verify:deploy-workflow - name: Verify deploy readiness run: pnpm run verify:deploy-readiness - name: Verify CI baseline run: pnpm run verify:ci-baseline - name: Verify source health run: pnpm run verify:source-health - name: Verify ESLint baseline run: pnpm run verify:eslint-baseline - name: Verify env drift run: pnpm run verify:env-drift - name: Verify health baseline run: pnpm run verify:health-baseline - name: Verify API inventory run: pnpm run verify:api-inventory - name: Verify tenant isolation report run: pnpm run verify:tenant-isolation-report - name: Verify sidebar routes run: pnpm run verify:sidebar-routes - name: Verify i18n keys run: pnpm run verify:i18n - name: Verify Tenant Auth tenant and onboarding gates run: | pnpm run verify:tenant-auth-baseline pnpm run verify:onboarding-flow - name: Verify Phase C package boundaries run: pnpm run verify:phase-c-boundaries - name: Verify Feature Manifest plugin removal gates run: | pnpm run verify:core-build-without-plugins-smoke pnpm run verify:removed-modules - name: Verify UI mock static baseline run: pnpm run verify:ui-visual - name: Test UI mock hot-swap run: pnpm run test:hot-swap - name: Verify accessibility coverage run: pnpm run verify:a11y-coverage - name: Test accessibility smoke run: pnpm run test:a11y - name: Verify documentation gates run: | pnpm run verify:porting-docs-baseline pnpm run verify:production-docs pnpm run verify:release-docs pnpm run verify:docs-links - name: Typecheck run: pnpm typecheck - name: Lint run: pnpm lint - name: Foundation package tests run: pnpm run test:foundation - name: Workspace package tests run: pnpm run test:packages - name: Mobile wrapper tests run: pnpm run test:mobile - name: Unit tests run: pnpm test - name: Build env: TOOLCTRL_BUILD_SOURCEMAPS: "1" run: pnpm run build:ci - name: Verify bundle size run: pnpm run verify:bundle-size - name: Verify web Sentry sourcemaps run: test -n "$(find apps/web/build/client -type f -name '*.map' -print -quit)" - name: Upload web Sentry sourcemaps uses: actions/upload-artifact@v4 with: name: web-client-sourcemaps path: apps/web/build/client if-no-files-found: error retention-days: 7 - name: Verify production seed artifact run: pnpm run verify:production-seed-artifact api: name: API Service runs-on: ubuntu-latest timeout-minutes: 25 env: DATABASE_URL: postgresql://toolctrl:toolctrl@localhost:5432/toolctrl?schema=public steps: - name: Checkout code uses: actions/checkout@v4 - name: Enable Corepack run: corepack enable - name: Prepare pnpm 10.33.0 run: corepack prepare pnpm@10.33.0 --activate - name: Configure pnpm store run: pnpm config set store-dir .pnpm-store - name: Cache pnpm store uses: actions/cache@v4 with: path: .pnpm-store key: ${{ runner.os }}-pnpm-${{ hashFiles('pnpm-lock.yaml') }} restore-keys: | ${{ runner.os }}-pnpm- - name: Install dependencies run: pnpm install --frozen-lockfile --prefer-offline - name: Verify OpenAPI security run: pnpm run verify:openapi-security - name: Typecheck API run: pnpm --filter @toolctrl/api typecheck - name: Test API run: pnpm exec vitest run apps/api/src - name: Build API run: pnpm --filter @toolctrl/api build worker: name: Worker Service runs-on: ubuntu-latest timeout-minutes: 25 env: DATABASE_URL: postgresql://toolctrl:toolctrl@localhost:5432/toolctrl?schema=public steps: - name: Checkout code uses: actions/checkout@v4 - name: Enable Corepack run: corepack enable - name: Prepare pnpm 10.33.0 run: corepack prepare pnpm@10.33.0 --activate - name: Configure pnpm store run: pnpm config set store-dir .pnpm-store - name: Cache pnpm store uses: actions/cache@v4 with: path: .pnpm-store key: ${{ runner.os }}-pnpm-${{ hashFiles('pnpm-lock.yaml') }} restore-keys: | ${{ runner.os }}-pnpm- - name: Install dependencies run: pnpm install --frozen-lockfile --prefer-offline - name: Typecheck Worker run: pnpm --filter @toolctrl/worker typecheck - name: Test Worker run: pnpm --filter @toolctrl/worker exec vitest run - name: Build Worker run: pnpm --filter @toolctrl/worker build - name: Health Check Worker run: pnpm --filter @toolctrl/worker health env: WORKER_ENABLED: "false" database: name: Database Checks runs-on: ubuntu-latest timeout-minutes: 20 steps: - name: Checkout code uses: actions/checkout@v4 - name: Enable Corepack run: corepack enable - name: Prepare pnpm 10.33.0 run: corepack prepare pnpm@10.33.0 --activate - name: Configure pnpm store run: pnpm config set store-dir .pnpm-store - name: Cache pnpm store uses: actions/cache@v4 with: path: .pnpm-store key: ${{ runner.os }}-pnpm-${{ hashFiles('pnpm-lock.yaml') }} restore-keys: | ${{ runner.os }}-pnpm- - name: Install dependencies run: pnpm install --frozen-lockfile --prefer-offline - name: Generate database client run: pnpm run db:generate env: DATABASE_URL: postgresql://toolctrl:toolctrl@localhost:5432/toolctrl?schema=public - name: Verify database migration status if: github.event_name != 'pull_request' run: pnpm run verify:db-migrate-status env: DATABASE_URL: ${{ secrets.DATABASE_URL }} - name: Test database package run: pnpm --filter @toolctrl/db test docker-smoke: name: Docker Smoke runs-on: ubuntu-latest timeout-minutes: 30 needs: [quality, api, worker, database] steps: - name: Checkout code uses: actions/checkout@v4 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Build web Docker target uses: docker/build-push-action@v7 with: context: . target: web build-args: APP_REVISION=ci-smoke-revision tags: toolctrl-web:smoke load: true push: false cache-from: type=gha,scope=toolctrl-web-smoke cache-to: type=gha,mode=max,scope=toolctrl-web-smoke - name: Validate web Docker artifacts run: docker run --rm --entrypoint /bin/sh toolctrl-web:smoke -lc 'test -f /myapp/apps/web/build/server/index.js && test -d /myapp/apps/web/build/client && test -f /myapp/build/server-runtime/server.js' - name: Validate web Docker runtime run: bash scripts/docker-web-runtime-smoke.sh toolctrl-web:smoke - name: Build API Docker target uses: docker/build-push-action@v7 with: context: . target: api build-args: APP_REVISION=ci-smoke-revision tags: toolctrl-api:smoke load: true push: false cache-from: type=gha,scope=toolctrl-api-smoke cache-to: type=gha,mode=max,scope=toolctrl-api-smoke - name: Validate API Docker runtime run: bash scripts/docker-api-runtime-smoke.sh toolctrl-api:smoke - name: Build worker Docker target uses: docker/build-push-action@v7 with: context: . target: worker build-args: APP_REVISION=ci-smoke-revision tags: toolctrl-worker:smoke load: true push: false cache-from: type=gha,scope=toolctrl-worker-smoke cache-to: type=gha,mode=max,scope=toolctrl-worker-smoke - name: Validate worker Docker runtime run: bash scripts/docker-worker-runtime-smoke.sh toolctrl-worker:smoke - name: Validate Docker Compose config run: docker compose config resolve-sentry-environment: name: Resolve Sentry environment runs-on: ubuntu-latest timeout-minutes: 5 needs: docker-smoke outputs: environment: ${{ steps.resolve.outputs.environment }} steps: - name: Resolve target environment from ref id: resolve run: | REF="${GITHUB_REF:-}" if [ "$REF" = "refs/heads/main" ]; then echo "environment=production" >> "$GITHUB_OUTPUT" elif [ "$REF" = "refs/heads/staging" ]; then echo "environment=staging" >> "$GITHUB_OUTPUT" else echo "environment=preview" >> "$GITHUB_OUTPUT" fi sentry-release-api: name: Sentry release (api) needs: resolve-sentry-environment uses: ./.github/workflows/_sentry-release.yml with: project: boilerplate-api environment: ${{ needs.resolve-sentry-environment.outputs.environment }} sourcemaps-path: none secrets: SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} SENTRY_ORG: ${{ secrets.SENTRY_ORG }} SENTRY_URL: ${{ secrets.SENTRY_URL }} sentry-release-worker: name: Sentry release (worker) needs: resolve-sentry-environment uses: ./.github/workflows/_sentry-release.yml with: project: boilerplate-worker environment: ${{ needs.resolve-sentry-environment.outputs.environment }} sourcemaps-path: none secrets: SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} SENTRY_ORG: ${{ secrets.SENTRY_ORG }} SENTRY_URL: ${{ secrets.SENTRY_URL }} sentry-release-web: name: Sentry release (web) needs: resolve-sentry-environment uses: ./.github/workflows/_sentry-release.yml with: project: boilerplate-web environment: ${{ needs.resolve-sentry-environment.outputs.environment }} sourcemaps-path: apps/web/build/client sourcemaps-artifact: web-client-sourcemaps secrets: SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} SENTRY_ORG: ${{ secrets.SENTRY_ORG }} SENTRY_URL: ${{ secrets.SENTRY_URL }}