Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 71 additions & 0 deletions .github/workflows/migrations.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
name: Migrations

# Replaces the "Supabase Preview" check, which never worked: it fired
# only on migration changes and then either 404'd in four seconds
# ("Postgres config not found") or hung forever waiting on a branch
# action, because two stale dashboard integrations were pointed at this
# repo and neither could provision. It never applied a migration, so
# every `supabase/migrations/*.sql` file in this repo has shipped
# unexecuted by CI.
#
# This job does the thing that check only claimed to: boot a clean
# Postgres and replay every migration, in order, from nothing. It needs
# no secrets and no Supabase account — the CLI runs the database in a
# container on the runner.

on:
pull_request:
# `supabase/**` rather than `supabase/migrations/**` so a change to
# config.toml re-runs the check too, and this workflow is listed so
# that edits to the check are validated by the check.
#
# NOTE: if this is ever made a REQUIRED status check, drop these
# filters. GitHub leaves a required check that never ran stuck as
# "expected", which blocks every PR that doesn't touch SQL.
paths:
- 'supabase/**'
- '.github/workflows/migrations.yml'
push:
branches: [main]
paths:
- 'supabase/**'
- '.github/workflows/migrations.yml'

concurrency:
group: migrations-${{ github.ref }}
cancel-in-progress: true

jobs:
apply:
name: Apply to a clean database
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7

- name: Install the Supabase CLI
uses: supabase/setup-cli@v1
with:
# Pinned rather than `latest`: this check is infrastructure,
# and a CLI release changing behaviour should be a deliberate
# bump in a PR, not a Tuesday where main goes red.
version: 2.113.0

- name: Start Postgres
run: supabase db start

# `db start` already applies the migrations on a cold volume, but
# only on a cold volume — it is a no-op against a database that
# already exists. `db reset` drops and rebuilds unconditionally,
# so this step, not the one above, is the guarantee that every
# migration ran in filename order against nothing. It fails on the
# first statement Postgres rejects. --no-seed because there is no
# seed script and this is about schema, not data.
- name: Replay every migration from scratch
run: supabase db reset --local --no-seed

# A migration can apply "successfully" and still do nothing —
# every DDL statement here is IF NOT EXISTS-guarded, which turns a
# typo'd object name into a silent no-op. Assert the outcome, not
# just the absence of an error.
- name: Verify the resulting schema
run: supabase db query --local --file supabase/ci/verify-schema.sql
62 changes: 62 additions & 0 deletions supabase/ci/verify-schema.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
-- Post-migration assertions for the CI job in
-- `.github/workflows/migrations.yml`.
--
-- `supabase db reset` already fails on any statement Postgres rejects,
-- so this is not about syntax. It's about the quieter failure: a
-- migration that applies cleanly and does nothing. Every DDL statement
-- in this repo is guarded with IF NOT EXISTS / ON CONFLICT so the files
-- can be re-run safely, and that same guard turns a typo'd object name
-- into a silent no-op with a green checkmark.
--
-- Keep this thin. It is a smoke test for "did the migrations actually
-- build the schema", not a spec of it — asserting every column here
-- would just be the migrations restated in a second place, drifting.
DO $$
BEGIN
-- The core tables, from 001.
IF to_regclass('public.messages') IS NULL THEN
RAISE EXCEPTION 'public.messages is missing — migrations did not apply';
END IF;
IF to_regclass('public.whatsapp_config') IS NULL THEN
RAISE EXCEPTION 'public.whatsapp_config is missing — migrations did not apply';
END IF;

-- Supabase provides the storage schema; migrations 016/020/023 write
-- to it. If it is absent the bucket migrations silently accomplish
-- nothing, which is precisely the case a plain "no errors" run hides.
IF to_regclass('storage.buckets') IS NULL THEN
RAISE EXCEPTION
'storage.buckets is missing — the storage schema was not available when the bucket migrations ran';
END IF;

-- Buckets are UPSERTed, so their absence means the INSERT never ran.
IF NOT EXISTS (SELECT 1 FROM storage.buckets WHERE id = 'chat-media') THEN
RAISE EXCEPTION 'the chat-media bucket row was not created (migration 023)';
END IF;
IF NOT EXISTS (SELECT 1 FROM storage.buckets WHERE id = 'flow-media') THEN
RAISE EXCEPTION 'the flow-media bucket row was not created (migration 016)';
END IF;

-- Account scoping (017) is load-bearing for every RLS policy.
IF to_regclass('public.accounts') IS NULL THEN
RAISE EXCEPTION 'public.accounts is missing — migration 017 did not apply';
END IF;

RAISE NOTICE 'schema verification passed';
END
$$;

-- Two things this file has already been burned by, both verified in CI
-- rather than assumed:
--
-- 1. It must contain EXACTLY ONE statement. `supabase db query --file`
-- sends the whole file as a prepared statement, and a second
-- top-level statement fails with the distinctly unhelpful "cannot
-- insert multiple commands into a prepared statement" (commit
-- f91a6c8). Add assertions INSIDE the DO block above; do not append
-- a second one.
--
-- 2. A RAISE in here really does fail the job. A deliberately false
-- assertion (commit 42c7db0, run 31579334056) surfaced as
-- `failed to execute query: error: ...` and exited 1. This is not a
-- decorative green tick.
24 changes: 24 additions & 0 deletions supabase/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Supabase CLI configuration.
#
# This file exists for ONE reason: the CI job in
# `.github/workflows/migrations.yml` needs a project directory the CLI
# recognises before it can boot a throwaway Postgres and replay
# `supabase/migrations/` against it.
#
# It is NOT a mirror of the hosted project's settings. Auth providers,
# storage limits, SMTP, rate limits and the rest are configured in the
# Supabase dashboard and are deliberately left out here rather than
# duplicated into a second source of truth that would quietly drift.
# Run `supabase init` if you ever want the CLI's full annotated
# reference version of this file.
project_id = "wacrm"

[db]
port = 54322
shadow_port = 54320
# Keep this aligned with the hosted project's Postgres version
# (Dashboard → Settings → Infrastructure). A mismatch means CI can
# green-light SQL the production database would reject, which is the
# one failure mode this whole check exists to prevent. 17 is the CLI's
# current default, not a reading of the live project.
major_version = 17
Loading