sendou.ink/scripts/ensure-test-db.ts
2026-07-30 12:03:44 +03:00

89 lines
2.2 KiB
TypeScript

import { execSync } from "node:child_process";
import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
import Database from "better-sqlite3";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const ROOT_DIR = path.join(__dirname, "..");
const MIGRATIONS_DIR = path.join(ROOT_DIR, "migrations");
const TEST_DB_PATH = path.join(ROOT_DIR, "db-test.sqlite3");
export function setup() {
ensureMigratedDb(TEST_DB_PATH);
}
/**
* Ensures the SQLite file at `dbPath` has every migration applied: creates it
* if missing, applies pending migrations, and rebuilds it from scratch if it
* contains a migration that no longer exists on disk.
*/
export function ensureMigratedDb(dbPath: string) {
const resolvedPath = path.resolve(ROOT_DIR, dbPath);
if (!fs.existsSync(resolvedPath)) {
migrateUp(resolvedPath);
return;
}
const applied = appliedMigrations(resolvedPath);
const onDisk = migrationFilesOnDisk();
const hasDrift =
applied === null || applied.some((name) => !onDisk.has(name));
if (hasDrift) {
deleteDbFiles(resolvedPath);
migrateUp(resolvedPath);
return;
}
const appliedSet = new Set(applied);
const hasPending = [...onDisk].some((name) => !appliedSet.has(name));
if (hasPending) {
migrateUp(resolvedPath);
}
}
function migrationFilesOnDisk() {
return new Set(
fs.readdirSync(MIGRATIONS_DIR).filter((file) => file.endsWith(".js")),
);
}
function appliedMigrations(dbPath: string) {
const db = new Database(dbPath, { readonly: true });
try {
const hasMigrationsTable = db
.prepare(
"select 1 from sqlite_master where type = 'table' and name = 'migrations'",
)
.get();
if (!hasMigrationsTable) return null;
const rows = db.prepare("select name from migrations").all() as Array<{
name: string;
}>;
return rows.map((row) => row.name);
} catch {
return null;
} finally {
db.close();
}
}
function deleteDbFiles(dbPath: string) {
for (const suffix of ["", "-shm", "-wal"]) {
fs.rmSync(`${dbPath}${suffix}`, { force: true });
}
}
function migrateUp(dbPath: string) {
execSync("pnpm run migrate up", {
cwd: ROOT_DIR,
stdio: "inherit",
env: { ...process.env, DB_PATH: dbPath },
});
}