#!/usr/bin/env node /** * Lightweight migration runner for Supabase. * Reads SQL files in supabase/migrations (sorted) and applies any that have not been recorded. * * Env: * - SUPABASE_DB_URL or DATABASE_URL: postgres connection string (service role / connection string) */ import fs from 'fs'; import path from 'path'; import { fileURLToPath } from 'url'; import { Pool } from 'pg'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const migrationsDir = path.resolve(__dirname, '..', 'supabase', 'migrations'); const connectionString = process.env.SUPABASE_DB_URL || process.env.DATABASE_URL; if (!connectionString) { console.error('Missing SUPABASE_DB_URL (or DATABASE_URL) environment variable. Aborting migrations.'); process.exit(1); } if (!fs.existsSync(migrationsDir)) { console.error(`Migrations directory not found: ${migrationsDir}`); process.exit(1); } const pool = new Pool({ connectionString }); async function ensureMigrationsTable(client) { await client.query(` create table if not exists public.schema_migrations ( id serial primary key, filename text not null unique, applied_at timestamptz not null default now() ); `); } async function getApplied(client) { const { rows } = await client.query('select filename from public.schema_migrations'); return new Set(rows.map((r) => r.filename)); } function listMigrations() { return fs .readdirSync(migrationsDir) .filter((file) => file.endsWith('.sql')) .sort(); } async function applyMigration(client, filename) { const filePath = path.join(migrationsDir, filename); const sql = fs.readFileSync(filePath, 'utf8'); console.log(`Applying migration: ${filename}`); await client.query('begin'); try { await client.query(sql); await client.query('insert into public.schema_migrations (filename) values ($1)', [filename]); await client.query('commit'); console.log(`✓ Applied ${filename}`); } catch (err) { await client.query('rollback'); console.error(`✗ Failed ${filename}:`, err.message); throw err; } } async function run() { const client = await pool.connect(); try { await ensureMigrationsTable(client); const applied = await getApplied(client); const migrations = listMigrations(); const pending = migrations.filter((m) => !applied.has(m)); if (pending.length === 0) { console.log('No pending migrations.'); return; } for (const migration of pending) { await applyMigration(client, migration); } console.log('All pending migrations applied.'); } finally { client.release(); await pool.end(); } } run().catch((err) => { console.error('Migration run failed:', err); process.exit(1); });