feat: add staging environment with prod sync and migration CI#30
feat: add staging environment with prod sync and migration CI#30
Conversation
Adds three Vite-mode-based environments so we can test changes against a
staging Supabase project before touching prod, plus a one-way sync script
that copies prod data into staging or local with PII scrubbed.
- New scripts: dev:staging, dev:local, build:staging, db:sync:{staging,local}
- scripts/sync-from-prod.sh: pg_dump public schema -> truncate target ->
restore -> run anonymize.sql. Refuses to write back to prod, prompts
before overwriting, never copies auth.users.
- scripts/anonymize.sql: scrubs profiles.username, artist_notes.note_content,
rotates group_invites.invite_token.
- .env.{,staging,local}.example templates + scripts/.env.sync.example.
- docs/ENVIRONMENTS.md walks through one-time setup and day-to-day usage.
Adds an auth.users sync step that runs before the public schema dump so synced FK references resolve and RLS-gated reads/writes work as a test user. - Pulls (id, email, email_confirmed_at, created_at, updated_at, aud, role) from prod via psql \copy into a temp table on the target. - Upserts into auth.users with ON CONFLICT (id) DO NOTHING so existing test accounts on staging/local are preserved. - Synced rows get email = user-<short-id>@example.test, no password, no OAuth metadata, is_super_admin = false. They can't sign in. - Skip with SYNC_AUTH=0. Also fixes a bogus pg_dump flag (--column-inserts=false isn't valid).
Adds .github/workflows/db-migrate.yml that runs on push to develop or main
when supabase/migrations/** changes. Resolves target from the branch (or
workflow_dispatch input), then runs supabase link + supabase db push against
the matching project.
Uses GitHub environments (staging, production) so prod migrations can later
be gated behind required-reviewer approval via repo settings.
Required secrets: SUPABASE_ACCESS_TOKEN, {PROD,STAGING}_PROJECT_REF,
{PROD,STAGING}_DB_PASSWORD. Documented in docs/ENVIRONMENTS.md.
PRs target main directly. Vercel preview deploys (any non-main branch) point at staging Supabase, so testing happens on the preview URL before merge. - db-migrate.yml: only auto-runs on push to main (-> prod). Use workflow_dispatch with target=staging to push a PR's migrations to staging before merging. - lint/unit-tests/e2e-tests: drop develop from triggers. - docs/ENVIRONMENTS.md: rewrite the promotion flow + flag the staging drift caveat (abandoned PRs leave migrations on staging) and recommend required-reviewer protection on the production environment.
Removes the manual workflow_dispatch step from the dev loop. The DB Migrate workflow now also runs on pull_request to main when supabase/migrations/** changes, and resolves target=staging for that event. supabase db push is idempotent so re-runs on each PR commit are safe. workflow_dispatch is kept for manual re-runs and for migrating staging when no migration file changed.
Adds a step that runs only on failure during pull_request events. Posts a comment with a link to the failed run and a hint about common causes, so the failure is visible in the PR conversation rather than only in the Actions tab. Adds the required pull-requests: write permission at the workflow level.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
❌ DB Migrate failed — Common causes: a migration that conflicts with the current schema, a dependency on objects that don't exist, or an out-of-order timestamp. Fix the migration and push again to re-run. |
Project refs aren't sensitive (they're in dashboard URLs), so they belong as Actions variables. Using secrets.PROD_PROJECT_REF returned an empty string and supabase link failed.
There was a problem hiding this comment.
Pull request overview
Adds a first-class staging Supabase environment alongside prod/local, plus tooling and CI automation to keep staging data/schema aligned with production for safer preview testing.
Changes:
- Introduces Vite modes and env templates for prod / staging / local (
dev:staging,dev:local,build:staging). - Adds a prod→staging/local sync script (public schema restore + PII anonymization, optional
auth.userssync). - Adds DB migration CI to push to staging on PRs and to prod on merges to
main, and removesdevelopfrom CI triggers.
Reviewed changes
Copilot reviewed 12 out of 13 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
scripts/sync-from-prod.sh |
Synces prod data into staging/local (auth user stub sync + public schema restore + anonymize). |
scripts/anonymize.sql |
Post-restore PII scrubbing steps for synced datasets. |
scripts/.env.sync.example |
Template for local-only DB connection strings used by the sync script. |
package.json |
Adds Vite mode scripts and db sync script entrypoints. |
docs/ENVIRONMENTS.md |
Documents env model, bootstrap steps, sync flow, and CI migration behavior. |
.gitignore |
Ensures sync script credentials file is ignored. |
.github/workflows/unit-tests.yml |
Drops develop branch trigger. |
.github/workflows/lint.yml |
Drops develop branch trigger. |
.github/workflows/e2e-tests.yml |
Drops develop branch trigger. |
.github/workflows/db-migrate.yml |
New workflow to push Supabase migrations to staging/prod with PR failure comment. |
.env.example |
Template for prod Supabase browser env vars. |
.env.staging.example |
Template for staging Supabase browser env vars. |
.env.local.example |
Template for local Supabase browser env vars. |
- package.json: pnpm dev now defaults to local supabase; old behavior moves to pnpm dev:prod - db-migrate.yml: environment selection now event-driven (workflow_dispatch with target=staging from main no longer picks production); pin supabase-cli to 2.58.5 instead of latest; rewrite the failure-comment as a single-line --body so leading indentation doesn't render as a code block - anonymize.sql: header comment was stale — auth.users IS now synced separately by sync-from-prod.sh - docs/ENVIRONMENTS.md: spell out PII on first use; correct Vite env load order (.env -> .env.local -> .env.[mode] -> .env.[mode].local); reorder env table to put local first
…prod The hardcoded fallback to prod URL/anon key meant that running pnpm dev without an env file would quietly send local-dev requests to production. Removes the fallback and throws at module load with a message pointing to .env.local.example. Wires VITE_SUPABASE_URL / VITE_SUPABASE_PUBLISHABLE_KEY into the e2e workflow at job level (pointing at the local supabase started in the Setup Supabase step) so the build and the Playwright webServer both have them. Lint and unit tests don't import the client and don't need them.
psql -c with multi-line continuations and leading whitespace was sending \copy to the server as SQL instead of treating it as a client meta-command. Single line works because psql sees the leading backslash immediately.
The :'authcsv' substitution wasn't being applied — psql was trying to open the literal string ":'authcsv'" as a file. Unquoted heredoc with '$AUTH_CSV' lets bash do the substitution before psql sees the SQL, which is more portable across psql versions.
…triggers pg_dump --disable-triggers emits ALTER TABLE ... DISABLE TRIGGER ALL, which Postgres treats as touching system triggers (RI_ConstraintTrigger_*) and rejects without superuser — Supabase's postgres role isn't super. Drop the flag and instead set session_replication_role=replica for the restore session, which silences FK/constraint triggers session-wide without per-trigger ALTERs.
|
❌ DB Migrate failed — |
Same fix as e2e-tests.yml — without the fallback, any test that transitively imports the supabase client now hits the throw at module load. Setting the local-supabase URL + demo anon key at job level keeps the import side-effect-free for tests.
…cess Old behavior posted a fresh failure comment on every failed run and never cleaned up after a subsequent success — leaving stale comments on the PR. New behavior: - Always runs (success or failure) and finds prior comments via a hidden marker. - On failure: upserts (PATCH if exists, create if not) — at most one failure comment per PR. - On success: deletes any existing failure comment. Also trims the canned "common causes" text since it was misleading when the actual failure was a config issue (missing secret, etc.).
Always upserts a single status comment (success or failure) so the current state is always visible on the PR rather than disappearing on the happy path.
|
✅ DB Migrate succeeded for |
Adds .nvmrc (22) so local devs using nvm pick up the right runtime, and mirrors it in package.json engines (node >=22 <23, pnpm >=10) so pnpm validates the environment on install. Workflows now read node-version-file: .nvmrc instead of hardcoding the version, giving us one place to bump.
- pnpm dev: 'vite --mode local' -> 'vite' (default mode=development). This makes .env.local the natural personal override (loaded for every mode but beaten by .env.[mode] / .env.[mode].local), so the awkward .env.local.local filename goes away. - pnpm dev:prod: explicit '--mode production'. - .env.example: deleted (was a duplicate "prod template" with stale comments). Devs only need .env.local for normal local dev. - .env.local.example: refreshed wording, copy target is .env.local. - client.ts: tiny tweak to error message. - db-migrate.yml push trigger: drop the workflow file from paths so editing the workflow on main doesn't fire a no-op prod migration. Kept in pull_request paths so workflow changes are still tested on staging before merge.
- scripts/sync-from-prod.sh: drop email column from the prod auth.users dump and the temp table on target. Email is recomputed from id during the upsert; no need to write real prod emails to disk in between. - package.json: remove the dev:prod script. There's no .env.production* template and devving against prod from a laptop is a footgun. - docs/ENVIRONMENTS.md: drop dev:prod from the day-to-day commands and from the env table.
Summary
pnpm run devfor local,pnpm run dev:stagingfor staging).scripts/sync-from-prod.sh(pnpm run db:sync:{staging,local}) that pulls the public schema from prod, anonymizes PII, and upsertsauth.userswith rewritten emails so RLS-gated reads/writes work on synced data..github/workflows/db-migrate.yml: PRs touchingsupabase/migrations/**auto-push to staging; merges tomainauto-push to prod. Posts a single status comment on the PR (success or failure).developbranch from CI triggers..nvmrc+enginesinpackage.json.supabase/templates/magic_link.htmland tracks key auth settings insupabase/config.toml.src/integrations/supabase/client.ts— missing env vars now throw with a helpful message instead of silently hitting prod.docs/ENVIRONMENTS.md.Test plan
PROD_PROJECT_REF,STAGING_PROJECT_REF) and secrets (SUPABASE_ACCESS_TOKEN,{PROD,STAGING}_DB_PASSWORD).VITE_SUPABASE_URL/VITE_SUPABASE_PUBLISHABLE_KEYto Production = prod values, Preview = staging values.staging. Confirm migrations apply.pnpm run db:sync:stagingand confirm staging gets prod data with anonymized emails (user-*@example.test) and nonote_content.productionGitHub environment so prod migrations pause for approval.