import type { Pool } from "mysql2/promise"; import fs from "fs"; import path from "path"; import { fileURLToPath } from "url"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const MIGRATIONS_DIR = path.join(__dirname, "..", "migrations"); export async function runMigrations(pool: Pool): Promise { await pool.execute(` CREATE TABLE IF NOT EXISTS _migrations ( id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, filename VARCHAR(255) NOT NULL UNIQUE, run_at DATETIME DEFAULT CURRENT_TIMESTAMP ) `); const files = fs .readdirSync(MIGRATIONS_DIR) .filter((f) => f.endsWith(".sql")) .sort(); for (const file of files) { const [rows] = await pool.execute( "SELECT id FROM _migrations WHERE filename = ?", [file] ); if (rows.length > 0) continue; const sql = fs.readFileSync(path.join(MIGRATIONS_DIR, file), "utf-8"); // NOTE: This splitter is intentionally naive — migration files must not // contain semicolons inside string literals or block comments. const statements = sql .split(";") .map((s) => s.trim()) .filter((s) => s.length > 0 && !s.startsWith("--")); const conn = await pool.getConnection(); await conn.beginTransaction(); try { for (const stmt of statements) { await conn.execute(stmt); } await conn.execute("INSERT INTO _migrations (filename) VALUES (?)", [file]); await conn.commit(); console.log(`Migration applied: ${file}`); } catch (err) { await conn.rollback(); throw err; } finally { conn.release(); } } }