diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0d97573..403b823 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -190,24 +190,28 @@ jobs: cache: npm - name: Install dependencies + # `npm ci` triggers the `postinstall` hook (`npx prisma generate`), + # so the Prisma client is already regenerated against the current + # schema before the build step runs. run: npm ci - - name: Generate Prisma client - run: npm run db:generate - - - name: Build site - run: npm run build - - name: Deploy to Netlify (prod) - # --skip-functions-cache forces a fresh function bundle so the + # `--build` runs `netlify build` first, which pulls the Netlify + # project env vars (VITE_ANALYTICS_*, VITE_POSTHOG_*, VITE_GOOGLE_*, + # SITE_URL, etc.) into the Vite build process. Without this flag we + # would run `npm run build` here with no VITE_* values in scope and + # silently ship a bundle where analytics, OAuth visibility, and any + # other frontend env-driven feature is empty. Source of truth for + # those values stays in Netlify's project env. + # + # `--skip-functions-cache` forces a fresh function bundle so the # deployed Prisma client always matches the current schema. The # earlier deploy-race incident happened because Netlify reused a # cached function bundle generated under a stale schema. run: | npx -y netlify-cli@latest deploy \ --prod \ - --dir dist \ - --functions netlify/functions \ + --build \ --skip-functions-cache \ --message "ci: ${GITHUB_SHA::7} (${GITHUB_REF_NAME})" diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 263f9f9..af36dd0 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -760,7 +760,7 @@ Pipeline contract (`.github/workflows/ci.yml`): | `check-production-migrate` | push to `main` | gates the migrate job on `PRODUCTION_DATABASE_URL` | | `migrate-production` | push to `main` (when gated true) | `npm run db:migrate:deploy` + `npm run db:seed` | | `check-production-deploy` | push to `main` | gates the deploy job on `NETLIFY_AUTH_TOKEN` + `NETLIFY_SITE_ID` | -| `deploy-production` | push to `main` (when gated true) | builds, generates Prisma client, `netlify deploy --prod --skip-functions-cache`, then smoke-checks the live API | +| `deploy-production` | push to `main` (when gated true) | `npm ci` (postinstall regenerates Prisma client), `netlify deploy --prod --build --skip-functions-cache` (Netlify build injects `VITE_*` project env into the bundle), then smoke-checks the live API | Required GitHub secrets: @@ -770,9 +770,11 @@ Required GitHub secrets: Notes: +- `--build` is mandatory: it runs `netlify build` which loads the Netlify project env vars into the Vite build, so `VITE_*` values (PostHog key, analytics flags, Google client id, etc.) are baked into the deployed bundle. Without it we would silently ship a bundle with empty `VITE_*` values. - `--skip-functions-cache` is mandatory: the older "Deploying functions from cache" path can publish a function bundle whose Prisma client predates the current schema. - The deploy job runs a 5-attempt curl smoke check against `/api/tournaments?status=active,upcoming` and fails the CI run if the live API does not 200. -- Netlify's git auto-deploy remains enabled as a fallback. With `[build.environment] NODE_VERSION = "24.1.0"` pinned in `netlify.toml`, future image rollouts have one fewer variable; but the GitHub Actions path is the source of truth. +- Source of truth for both frontend (`VITE_*`) and backend runtime env vars is Netlify's project env, not GitHub Actions secrets. GitHub secrets only carry the values needed to authenticate the deploy itself (`NETLIFY_AUTH_TOKEN`, `NETLIFY_SITE_ID`) plus `PRODUCTION_DATABASE_URL` for the migrate job. +- Netlify's git auto-deploy is disabled (`stop_builds: true`) so the only deploy path is GitHub Actions. `[build.environment] NODE_VERSION = "24.1.0"` is still pinned in `netlify.toml` for defence in depth if auto-deploys are ever re-enabled. ### Destructive Migrations And The Deploy Race