name: CI on: pull_request: branches: [master] push: branches: [master] permissions: contents: read concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true env: COREPACK_ENABLE_DOWNLOAD_PROMPT: 0 jobs: checks: name: Lint, Typecheck, test, build, schema runs-on: ubuntu-latest timeout-minutes: 15 steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - name: Enable corepack run: corepack enable - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: node-version: 24 - name: Install run: pnpm install --frozen-lockfile - name: Lint run: pnpm lint - name: Typecheck run: pnpm typecheck - name: Test run: pnpm test - name: Build app run: pnpm build - name: Build docs run: pnpm build:docs - name: Check migrations match the schema working-directory: apps/app env: DATABASE_URL: postgres://unused run: ./node_modules/.bin/drizzle-kit check integration: name: Integration tests runs-on: ubuntu-latest timeout-minutes: 25 # A real PostgreSQL, because what these cover is the routes against the schema, not pure logic. services: postgres: image: postgres:18 env: POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres POSTGRES_DB: shhh_test ports: - 5432:5432 options: >- --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - name: Enable corepack run: corepack enable - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: node-version: 24 - name: Install run: pnpm install --frozen-lockfile # Turnstile runs for real, on Cloudflare's always-passes test keys, so this step needs egress. - name: Integration tests env: TEST_DATABASE_URL: postgres://postgres:postgres@127.0.0.1:5432/shhh_test run: pnpm test:integration docker-app: name: Docker image (app) runs-on: ubuntu-latest timeout-minutes: 20 steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - name: Build image run: docker build -f docker/Dockerfile -t shhh-app:ci . # Building only proves it compiles. Any HTTP response passes: this asks whether the server came # up, not whether it is healthy — there is no database here, so /api/health answers 503. # Without SKIP_MIGRATIONS the migrate plugin exits before Nitro ever listens. - name: Boot it run: | docker run -d --name probe -p 3000:3000 \ -e SKIP_MIGRATIONS=true \ -e DATABASE_URL=postgres://unused \ -e BETTER_AUTH_SECRET=ci-secret-not-used-for-anything-000 \ -e BETTER_AUTH_URL=http://localhost:3000 \ shhh-app:ci for _ in $(seq 1 30); do if curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:3000/ | grep -qE '^[2345]'; then echo "the app image answers" exit 0 fi sleep 2 done echo "::error::the app image never answered" docker logs probe exit 1 docker-docs: name: Docker image (docs) runs-on: ubuntu-latest timeout-minutes: 20 steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - name: Build image run: docker build -f docker/docs.Dockerfile -t shhh-docs:ci . # The docs image built cleanly for a long time while being unable to boot at all, because # nothing ever started it. It needs no environment: no database, no secrets. - name: Boot it run: | docker run -d --name probe -p 3000:3000 shhh-docs:ci for _ in $(seq 1 30); do if curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:3000/ | grep -qE '^[2345]'; then echo "the docs image answers" exit 0 fi sleep 2 done echo "::error::the docs image never answered" docker logs probe exit 1