import * as path from 'path' import { promises as fs } from 'fs' import { FileMigrationProvider, Migrator, type MigrationResultSet } from 'kysely/migration' import { db } from './db' import { argv } from 'bun' import { env, log } from '../util' type HandlerResult = { message: string; }; function processResults(label: string, { error, results }: MigrationResultSet): string { let message = ""; results?.forEach((it) => { if (it.status === 'Success') { message = `migration ${label} succeeded: "${it.migrationName}"`; } else if (it.status === 'Error') { throw new Error(`migration ${label} failed: "${it.migrationName}"`); } }); if (error) throw error; return message; } const handlers: Record Promise> = { "up": async function (migrator: Migrator) { const resultSet = await migrator.migrateUp(); const message = processResults("up", resultSet); return { message }; }, "down": async function (migrator: Migrator) { const resultSet = await migrator.migrateDown(); const message = processResults("down", resultSet); return { message }; }, "latest": async function (migrator: Migrator) { const resultSet = await migrator.migrateToLatest(); const message = processResults("to latest", resultSet); return { message }; }, "rollback": async function (migrator: Migrator) { let count = 0; while (true) { const resultSet = await migrator.migrateDown(); if (!resultSet.results?.length) break; processResults("rollback", resultSet); ++count; } return { message: `rolled back ${count} migration(s)` }; }, "reset": async function (migrator: Migrator) { if (env.NODE_ENV !== "development") throw new Error("Development only command"); if (typeof handlers["rollback"] !== "function") throw new Error("Missing rollback handler"); await handlers["rollback"](migrator); if (typeof handlers["latest"] !== "function") throw new Error("Missing latest handler"); await handlers["latest"](migrator); return { message: "database fully reset" }; } }; // Main (async () => { const command = argv[2] ?? ""; if (!handlers[command]) throw new Error("Unrecognized command"); const answer = prompt(`Run "${command}" migration against ${env.DATABASE_URL}? (y/N)`); if (answer?.trim().toLowerCase() !== "y") { log.info("Aborted"); await db.destroy(); return; } const migrator = new Migrator({ db, provider: new FileMigrationProvider({ fs, path, // This needs to be an absolute path. migrationFolder: path.join(__dirname, "migrations"), }), }); try { const result = await handlers[command](migrator); log.info({ name: command, result }, "finished"); } catch (err) { log.error({ name: command, err }, "failed"); process.exit(1) } finally { await db.destroy() } })();