// Applies pending Drizzle migrations from ./drizzle. Runs as the schema owner // (DB_MIGRATE_URL, role `personal`); falls back to DB_URL for local dev. // // bun scripts/db-migrate.ts apply pending migrations // bun scripts/db-migrate.ts --init record the baseline as applied without running it // (once, on a database already reorganised by the // hand-written reorg scripts in db/reorg/) import { drizzle } from 'drizzle-orm/postgres-js'; import { migrate } from 'drizzle-orm/postgres-js/migrator'; import postgres from 'postgres'; const url = process.env.DB_MIGRATE_URL || process.env.DB_URL; if (!url) throw new Error('Set DB_MIGRATE_URL (schema owner) or DB_URL'); const init = process.argv.includes('--init'); const client = postgres(url, { max: 1, connect_timeout: 10, onnotice: () => {} }); try { const db = drizzle({ client }); // Safety: never run the baseline on a database that already has data. Existing // databases are brought to the baseline by the reviewed reorg scripts and then // recorded with --init; only a truly empty database gets the baseline executed. const [state] = await client` select to_regclass('drizzle.__drizzle_migrations') is not null as tracked, (select count(*)::int from pg_class c join pg_namespace n on n.oid = c.relnamespace where c.relkind = 'r' and n.nspname not in ('pg_catalog', 'information_schema', 'drizzle') and n.nspname not like 'pg_toast%') as tables`; const recorded = state.tracked ? (await client`select count(*)::int as n from drizzle.__drizzle_migrations`)[0].n : 0; if (!init && recorded === 0 && state.tables > 0) throw new Error( `Refusing to run the Drizzle baseline on a non-empty database (${state.tables} tables). ` + 'Apply the reorg scripts (db/reorg/) and record the baseline with --init first.' ); if (init && state.tables === 0) throw new Error('--init on an empty database: run without --init to create the schema'); const result = await migrate(db, { migrationsFolder: new URL('../drizzle', import.meta.url).pathname, migrationsSchema: 'drizzle', migrationsTable: '__drizzle_migrations', ...(init ? { init: true } : {}) }); if (result && 'exitCode' in result) { throw new Error( result.exitCode === 'databaseMigrations' ? '--init refused: migrations are already recorded in this database' : '--init refused: more than one local migration exists' ); } const [{ n }] = await client`select count(*)::int as n from drizzle.__drizzle_migrations`; console.log(`${init ? 'Baseline recorded' : 'Migrations up to date'}; ${n} recorded`); } finally { await client.end(); }