diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e8e4e95d..51cbbffd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -203,3 +203,47 @@ jobs: apps/web/playwright-report apps/web/test-results retention-days: 7 + + production-database: + name: Apply production migrations + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + needs: + - quality + - routes + - database + - browser + runs-on: ubuntu-latest + timeout-minutes: 10 + environment: Production + env: + SUPABASE_POOLER_URL: postgresql://postgres.mogdmhkqkpvksvtvwdgl@aws-0-ap-southeast-2.pooler.supabase.com:5432/postgres + + steps: + - name: Check out repository + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + + - name: Set up Node.js + uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + with: + node-version-file: .node-version + + - name: Set up Supabase CLI + uses: supabase/setup-cli@46f7f98c7f948ad727d22c1e67fab04c223a0520 # v3.0.0 + with: + version: 2.114.0 + + - name: Prepare production database connection + env: + SUPABASE_DB_PASSWORD: ${{ secrets.SUPABASE_DB_PASSWORD }} + run: >- + node -e 'const fs = require("node:fs"); const url = new + URL(process.env.SUPABASE_POOLER_URL); url.password = + process.env.SUPABASE_DB_PASSWORD; + fs.appendFileSync(process.env.GITHUB_ENV, + `SUPABASE_DB_URL=${url.href}\n`);' + + - name: Preview production migrations + run: supabase db push --db-url "$SUPABASE_DB_URL" --dry-run + + - name: Apply production migrations + run: supabase db push --db-url "$SUPABASE_DB_URL" --yes diff --git a/docs/architecture.md b/docs/architecture.md index 3213805d..f8133f07 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -214,7 +214,7 @@ official calendar; importing calendar events does not itself reconcile them. ## Delivery -Changes move through focused branches and pull requests. GitHub Actions checks formatting, linting, types, tests and the production build. Vercel creates preview deployments and promotes `main` after checks. Supabase schema changes remain forward-only migrations in the same pull request as their application code. +Changes move through focused branches and pull requests. GitHub Actions checks formatting, linting, types, tests and the production build. Vercel creates preview deployments and promotes `main` after checks. Supabase schema changes remain forward-only migrations in the same pull request as their application code. Pull requests exercise the complete migration history locally. After every gate passes on `main`, the production database job previews and applies pending migrations through the protected GitHub `Production` environment. `supabase/migrations/` is an eight-part baseline that states the schema as it is rather than the ninety-five migrations that reached it. The hosted project diff --git a/supabase/README.md b/supabase/README.md index 48c6c5fc..b7b6e0b9 100644 --- a/supabase/README.md +++ b/supabase/README.md @@ -36,6 +36,23 @@ Edit a baseline part only to correct something that has never been deployed. Once the hosted project has applied a file, changing it puts the two out of step, and a forward migration is the only way back. +## Production delivery + +Pull requests rebuild and test the complete migration history against local +Supabase. They never receive production credentials and cannot change the +hosted database. + +After a commit reaches `main`, GitHub Actions waits for the quality, route, +database and browser gates. The `Apply production migrations` job then enters +the `Production` environment, previews the pending migration plan and applies +it to project `mogdmhkqkpvksvtvwdgl` with `supabase db push`. The environment +must provide `SUPABASE_DB_PASSWORD` as a secret. The job constructs the +percent-encoded session-pooler URL at runtime, so it does not require a broad +Supabase Management API token. + +Production migrations are forward-only. Do not edit, rename or remove a +migration after it has been applied. Add a new migration to correct it. + `seed.sql` intentionally contains no catalogue or user fixtures. `pnpm db:reset` performs an explicitly local reset, then applies the separate preview fixture through a database client that refuses every non-loopback connection. Reapply it to an already running local stack with `pnpm db:seed:preview`.