Skip to content
Closed
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
109 changes: 109 additions & 0 deletions .github/workflows/supabase-migrations.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
name: Supabase migrations

on:
pull_request:
paths:
- "supabase/**"
- ".github/workflows/supabase-migrations.yml"
push:
branches: [main]
paths:
- "supabase/migrations/**"
# Report-only: lists what production is missing without applying anything.
workflow_dispatch:

permissions:
contents: read

jobs:
validate:
name: Migrations apply to a fresh database
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: supabase/setup-cli@v1
with:
version: 2.121.0

- name: Apply all migrations
run: supabase db start

production:
name: Apply to production
# Stays off until a maintainer sets the SUPABASE_PROJECT_ID repository
# variable; see "Database migrations" in the README.
if: github.event_name != 'pull_request' && vars.SUPABASE_PROJECT_ID != ''
runs-on: ubuntu-latest
environment: production
concurrency:
group: supabase-production
cancel-in-progress: false
env:
SUPABASE_ACCESS_TOKEN: ${{ secrets.SUPABASE_ACCESS_TOKEN }}
SUPABASE_DB_PASSWORD: ${{ secrets.SUPABASE_DB_PASSWORD }}
SUPABASE_PROJECT_ID: ${{ vars.SUPABASE_PROJECT_ID }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- uses: supabase/setup-cli@v1
with:
version: 2.121.0

- run: supabase link --project-ref "$SUPABASE_PROJECT_ID"

- name: Migration history (repo vs production)
run: supabase migration list

- name: Check pending migrations
id: pending
env:
BEFORE: ${{ github.event.before }}
run: |
set -euo pipefail

# The CLI already refuses when production's history and the repo
# disagree (a version missing from the repo, or a new file older than
# the last applied one) and prints how to repair it.
if ! result=$(supabase db push --dry-run --output-format json); then
echo "$result"
exit 1
fi
pending=$(jq -r '.migrations[]' <<<"$result")

if [ -z "$pending" ]; then
echo "Production is up to date."
echo "apply=false" >> "$GITHUB_OUTPUT"
exit 0
fi
echo "Pending migrations:"
echo "$pending"

if [ "$GITHUB_EVENT_NAME" != "push" ]; then
echo "Manual runs only report; nothing was applied."
echo "apply=false" >> "$GITHUB_OUTPUT"
exit 0
fi

# Only apply files this push added, so a history that's out of sync
# with the repo can never replay older migrations against production.
added=$(git diff --name-only --diff-filter=A "$BEFORE" "$GITHUB_SHA" -- supabase/migrations | xargs -r -n1 basename)
unexpected=$(comm -23 <(sort <<<"$pending") <(sort <<<"$added"))
if [ -n "$unexpected" ]; then
echo "::error::Production is missing migrations this push didn't add: $(tr '\n' ' ' <<<"$unexpected"). Reconcile the migration history (README, Database migrations) before applying."
exit 1
fi

edited=$(git diff --name-only --diff-filter=M "$BEFORE" "$GITHUB_SHA" -- supabase/migrations)
if [ -n "$edited" ]; then
echo "::warning::Edits to already-applied migrations don't run again: $(tr '\n' ' ' <<<"$edited")"
fi

echo "apply=true" >> "$GITHUB_OUTPUT"

- name: Apply
if: steps.pending.outputs.apply == 'true'
run: supabase db push
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ Lint from repo root: `bunx biome ci .` (matches CI in `.github/workflows/ci.yml`

- `bun` is installed via npm into `~/.local/bin` (on `PATH` via `~/.bashrc`); the standalone bun.sh installer is blocked by the network egress policy.
- The repo's dated migrations assume a pre-existing hosted base schema. `supabase/migrations/00000000000000_local_base_schema.sql` reconstructs it (tables, signup trigger, slug triggers, `pgmq_public` wrappers, storage policies) so a fresh `supabase start` / `supabase db reset` works locally. It is guarded to be a no-op where objects already exist.
- Merging a migration to `main` applies it to production through `.github/workflows/supabase-migrations.yml` (after an environment approval), in parallel with the Vercel deploy, so migrations must work with both the old and new code. Never run `supabase db push` against production by hand; see README → Database migrations.
- App env lives in `apps/cursor/.env` (gitignored). For local dev it uses the Supabase CLI's well-known local default keys (`supabase status` prints them) with `NEXT_PUBLIC_SUPABASE_URL=http://127.0.0.1:54321`. `CRON_SECRET` guards `/api/queue/plugin-scans/drain` and `/api/cron/*`.
- The login page only offers GitHub/Google OAuth, which are not configured locally. Create a confirmed user via the GoTrue admin API (`POST {SUPABASE_URL}/auth/v1/admin/users` with the secret key, `"email_confirm": true`); a DB trigger provisions the `public.users` profile row. To get a browser session, add a temporary route that calls `supabase.auth.signInWithPassword(...)` with the server client (do not commit it). Put the user's id in `ADMIN_USER_IDS`/`NEXT_PUBLIC_ADMIN_USER_IDS` to use `/admin/plugins`.
- Cache Components (`cacheComponents: true`) makes pages with `generateStaticParams` throw 500 on an empty database — seed at least one active plugin. Use `bun run seed:extract` / `seed:insert`, but note: the insert script must be run as `bun run --conditions=react-server --env-file=apps/cursor/.env apps/cursor/src/scripts/insert-from-jsonl.ts` or the `server-only` import throws under plain `bun run`. GitHub Code Search (used by `seed:extract` discovery) is heavily rate-limited; hand-writing `apps/cursor/.seed/candidates.json` with `{"candidates":[{"owner":...,"repo":...,"source":"seed:topic","matchedQuery":"manual"}]}` skips discovery.
Expand Down
44 changes: 44 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,50 @@ See the [Open Plugins specification](https://open-plugins.com/plugin-builders/sp
- **URL State**: [nuqs](https://nuqs.47ng.com)
- **Linting**: [Biome](https://biomejs.dev)

## Database migrations

Create migrations with `supabase migration new <name>`. The **Supabase
migrations** workflow (`.github/workflows/supabase-migrations.yml`) then:

- On pull requests that touch `supabase/`, applies every migration to a fresh
database, so broken SQL fails before merge.
- On merges to `main`, applies the migrations that merge added to production,
once someone approves the `production` environment. It refuses if production
is missing any other migration or has one the repo doesn't, so a history
that's out of sync can't replay old migrations.
- When run by hand (Actions → Supabase migrations → Run workflow), lists what
production is missing without applying anything.

Vercel deploys the same merge in parallel, so a migration has to work with both
the old and the new code: add tables and columns first, and remove them in a
later PR. If new code can't run without its migration, merge the migration in
its own PR first.

### One-time setup

The production job stays off until a repo admin does this:

1. In Supabase, create a
[personal access token](https://supabase.com/docs/guides/platform/personal-access-tokens)
scoped to the Cursor Directory project, with read access to Project
Settings, API Keys, and API Key Secrets. Note the database password
(Project Settings → Database). The app only uses API keys, so resetting the
password doesn't affect it.
2. In GitHub → Settings → Environments, create `production`. Add required
reviewers, limit deployment branches to `main`, and add the secrets
`SUPABASE_ACCESS_TOKEN` and `SUPABASE_DB_PASSWORD`.
3. Line up production's migration history with the repo. Earlier migrations
were applied by hand, so the history may be empty or use other version
numbers. With the CLI, run `supabase link --project-ref <ref>` and
`supabase migration list`. Mark every migration that's already in
production as applied with
`supabase migration repair --status applied <version>...`, and remove
versions the repo doesn't have with `--status reverted`. Repeat until
nothing is missing on either side.
4. In GitHub → Settings → Variables, add the repository variable
`SUPABASE_PROJECT_ID` (the project ref). This turns the production job on.
Run the workflow by hand once to confirm it reports nothing pending.

## Plugin security scan

Submitted plugins are auto-reviewed by a Cursor SDK agent (`composer-2`) running
Expand Down
Loading