Files
esports/scripts/migrate.ts

52 lines
2.1 KiB
TypeScript

/**
* Applies scripts/migrations/*.sql in name order, once each, inside one
* transaction. Applied names are recorded in schema_migration.
*
* bun scripts/migrate.ts apply pending migrations
* bun scripts/migrate.ts --status list applied and pending migrations
*
* Every migration is additive and idempotent, so a database that was set up
* with drizzle-kit push or the old migrate-platform script is safe to run.
*/
import { SQL } from 'bun';
import { readdir, readFile } from 'node:fs/promises';
const databaseUrl = process.env.DB_URL || process.env.DATABASE_URL;
if (!databaseUrl) throw new Error('DB_URL or DATABASE_URL is required');
const directory = new URL('./migrations/', import.meta.url);
const files = (await readdir(directory)).filter((name) => name.endsWith('.sql')).sort();
const sql = new SQL(databaseUrl, { max: 1 });
try {
await sql`CREATE TABLE IF NOT EXISTS schema_migration (name text PRIMARY KEY, applied_at timestamptz NOT NULL DEFAULT now())`;
const applied = new Set(
((await sql`SELECT name FROM schema_migration`) as { name: string }[]).map((row) => row.name)
);
const pending = files.filter((name) => !applied.has(name));
if (process.argv.includes('--status')) {
for (const name of files) console.log(`${applied.has(name) ? 'applied' : 'pending'} ${name}`);
} else if (pending.length === 0) {
console.log('Database is up to date');
} else {
await sql.begin(async (tx) => {
for (const name of pending) {
await tx.unsafe(await readFile(new URL(name, directory), 'utf8'));
await tx`INSERT INTO schema_migration (name) VALUES (${name})`;
console.log(`applied ${name}`);
}
// The root administrator is configured per environment, never guessed.
const rootId = process.env.ROOT_ADMIN_USER_ID;
const legacyRoot = process.env.ROOT_ADMIN_EMAIL;
if (rootId)
await tx`UPDATE "user" SET root_admin = true, role = 'admin' WHERE id = ${rootId}`;
else if (legacyRoot)
await tx`UPDATE "user" SET root_admin = true, role = 'admin' WHERE lower(email) = ${legacyRoot.toLowerCase()}`;
});
console.log(`${pending.length} migration(s) applied atomically`);
}
} finally {
await sql.end();
}