Files
bewcloud/migrate-db.ts
Bruno Bernardino 869e712432 Fix small typo in documentation
Also make sure the migrations run in order. Sets are unordered and thus can't guarantee the expected sorted order required in migrations.
2025-02-21 17:47:46 +00:00

102 lines
2.5 KiB
TypeScript

import 'std/dotenv/load.ts';
import Database, { sql } from '/lib/interfaces/database.ts';
const migrationsDirectoryPath = `${Deno.cwd()}/db-migrations`;
const migrationsDirectory = Deno.readDir(migrationsDirectoryPath);
const db = new Database();
interface Migration {
id: string;
name: string;
executed_at: Date;
}
async function getExecutedMigrations(): Promise<Set<string>> {
const executedMigrations = new Set(
Array.from(
(await db.query<Migration>(sql`SELECT * FROM "bewcloud_migrations" ORDER BY "name" ASC`)).map((migration) =>
migration.name
),
),
);
return executedMigrations;
}
async function getMissingMigrations(): Promise<string[]> {
const existingMigrations: Set<string> = new Set();
for await (const migrationFile of migrationsDirectory) {
// Skip non-files
if (!migrationFile.isFile) {
continue;
}
// Skip files not in the "001-blah.pgsql" format
if (!migrationFile.name.match(/^\d+-.*(\.pgsql)$/)) {
continue;
}
existingMigrations.add(migrationFile.name);
}
// Sort migrations
const sortedExistingMigrations = [...existingMigrations].sort();
// Add everything to run, by default
const migrationsToExecute = new Set([...sortedExistingMigrations]);
try {
const executedMigrations = await getExecutedMigrations();
// Remove any existing migrations that were executed, from the list of migrations to execute
for (const executedMigration of executedMigrations) {
migrationsToExecute.delete(executedMigration);
}
} catch (_error) {
// The table likely doesn't exist, so run everything.
}
return Array.from(migrationsToExecute).sort();
}
async function runMigrations(missingMigrations: string[]): Promise<void> {
for (const missingMigration of missingMigrations) {
console.log(`Running "${missingMigration}"...`);
try {
const migrationSql = await Deno.readTextFile(`${migrationsDirectoryPath}/${missingMigration}`);
await db.query(migrationSql);
await db.query(sql`INSERT INTO "public"."bewcloud_migrations" ("name", "executed_at") VALUES ($1, NOW())`, [
missingMigration,
]);
console.log('Success!');
} catch (error) {
console.log('Failed!');
console.error(error);
throw error;
}
}
}
try {
const missingMigrations = await getMissingMigrations();
await runMigrations(missingMigrations);
if (missingMigrations.length === 0) {
console.log('No migrations to run!');
}
Deno.exit(0);
} catch (error) {
console.error(error);
Deno.exit(1);
}