"use strict"; const fs = require("fs"); const path = require("path"); const { spawnSync } = require("child_process"); const migrationsDir = path.resolve( __dirname, "..", "packages", "db", "migrations" ); function ensureDatabaseUrl() { const databaseUrl = process.env.DATABASE_URL; if (!databaseUrl) { throw new Error("DATABASE_URL is required."); } return databaseUrl; } function ensurePsql() { const result = spawnSync("psql", ["--version"], { stdio: "pipe" }); if (result.error || result.status !== 0) { throw new Error("psql executable was not found in PATH."); } } function ensureMigrationsDir() { if (!fs.existsSync(migrationsDir)) { throw new Error(`Migrations directory not found: ${migrationsDir}`); } } function getMigrationFiles() { ensureMigrationsDir(); return fs .readdirSync(migrationsDir) .filter((file) => file.endsWith(".sql")) .sort((a, b) => a.localeCompare(b)); } function runPsql(databaseUrl, args) { const result = spawnSync("psql", [databaseUrl, ...args], { stdio: "pipe", encoding: "utf8", }); if (result.status !== 0) { const stderr = (result.stderr || "").trim(); const stdout = (result.stdout || "").trim(); const details = [stderr, stdout].filter(Boolean).join("\n"); throw new Error(details || "psql command failed"); } return result.stdout || ""; } function escapeSqlLiteral(value) { return value.replace(/'/g, "''"); } function ensureSchemaMigrationsTable(databaseUrl) { runPsql(databaseUrl, [ "-v", "ON_ERROR_STOP=1", "-c", "CREATE TABLE IF NOT EXISTS schema_migrations (filename TEXT PRIMARY KEY, applied_at TIMESTAMPTZ NOT NULL DEFAULT NOW());", ]); } function getAppliedMigrations(databaseUrl) { const output = runPsql(databaseUrl, [ "-At", "-v", "ON_ERROR_STOP=1", "-c", "SELECT filename FROM schema_migrations ORDER BY filename ASC;", ]); return new Set( output .split(/\r?\n/) .map((line) => line.trim()) .filter(Boolean) ); } function applyMigration(databaseUrl, filename) { const fullPath = path.join(migrationsDir, filename); runPsql(databaseUrl, ["-v", "ON_ERROR_STOP=1", "-f", fullPath]); runPsql(databaseUrl, [ "-v", "ON_ERROR_STOP=1", "-c", `INSERT INTO schema_migrations (filename) VALUES ('${escapeSqlLiteral( filename )}') ON CONFLICT DO NOTHING;`, ]); } module.exports = { applyMigration, ensureDatabaseUrl, ensurePsql, ensureSchemaMigrationsTable, getAppliedMigrations, getMigrationFiles, migrationsDir, };