mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-07-30 15:53:59 -05:00
89 lines
2.2 KiB
TypeScript
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 },
|
|
});
|
|
}
|