feat: auto-run SQL migrations and one-time seed on boot

This commit is contained in:
mahmood
2026-09-10 17:15:17 +03:30
parent a4c5c9259f
commit f19f49daeb
12 changed files with 2569 additions and 0 deletions
+186
View File
@@ -77552,6 +77552,190 @@ var require_app_module = __commonJS({
}
});
// dist/prisma/migrate.js
var require_migrate = __commonJS({
"dist/prisma/migrate.js"(exports2) {
"use strict";
Object.defineProperty(exports2, "__esModule", { value: true });
exports2.splitSqlStatements = splitSqlStatements;
exports2.runMigrations = runMigrations;
exports2.runOnce = runOnce;
var fs_1 = require("fs");
var path_1 = require("path");
var TRACKING_TABLE = "_goldbank_migrations";
function resolveMigrationsDir() {
const candidates = [
process.env.MIGRATIONS_DIR,
(0, path_1.join)(process.cwd(), "migrations"),
(0, path_1.join)(__dirname, "..", "..", "migrations"),
(0, path_1.join)(__dirname, "..", "..", "prisma", "migrations"),
(0, path_1.join)(process.cwd(), "prisma", "migrations")
].filter((p) => Boolean(p));
const found = candidates.find((dir) => (0, fs_1.existsSync)(dir));
if (!found) {
throw new Error(`\u067E\u0648\u0634\u0647 migrations \u067E\u06CC\u062F\u0627 \u0646\u0634\u062F. \u0645\u0633\u06CC\u0631\u0647\u0627\u06CC \u0628\u0631\u0631\u0633\u06CC\u200C\u0634\u062F\u0647: ${candidates.join(", ")}`);
}
return found;
}
function splitSqlStatements(sql) {
const statements = [];
let current = "";
let i = 0;
while (i < sql.length) {
const char = sql[i];
const rest = sql.slice(i);
if (rest.startsWith("--")) {
const lineEnd = sql.indexOf("\n", i);
i = lineEnd === -1 ? sql.length : lineEnd + 1;
continue;
}
if (rest.startsWith("/*")) {
const commentEnd = sql.indexOf("*/", i + 2);
i = commentEnd === -1 ? sql.length : commentEnd + 2;
continue;
}
if (char === "'" || char === '"') {
const quote = char;
current += char;
i += 1;
while (i < sql.length) {
current += sql[i];
if (sql[i] === quote) {
if (sql[i + 1] === quote) {
current += sql[i + 1];
i += 2;
continue;
}
i += 1;
break;
}
i += 1;
}
continue;
}
const dollarTag = /^\$[A-Za-z_]*\$/.exec(rest);
if (dollarTag) {
const tag = dollarTag[0];
const closing = sql.indexOf(tag, i + tag.length);
const end = closing === -1 ? sql.length : closing + tag.length;
current += sql.slice(i, end);
i = end;
continue;
}
if (char === ";") {
if (current.trim())
statements.push(current.trim());
current = "";
i += 1;
continue;
}
current += char;
i += 1;
}
if (current.trim())
statements.push(current.trim());
return statements;
}
async function runMigrations(prisma) {
const dir = resolveMigrationsDir();
const files = (0, fs_1.readdirSync)(dir).filter((name) => name.endsWith(".sql")).sort();
const trackingExisted = await prisma.$queryRawUnsafe(`SELECT EXISTS (
SELECT 1 FROM information_schema.tables
WHERE table_schema = current_schema() AND table_name = $1
) AS "exists"`, TRACKING_TABLE);
const freshDatabase = !trackingExisted[0]?.exists;
await prisma.$executeRawUnsafe(`CREATE TABLE IF NOT EXISTS "${TRACKING_TABLE}" (
name TEXT PRIMARY KEY,
applied_at TIMESTAMPTZ NOT NULL DEFAULT now()
)`);
const alreadyApplied = await prisma.$queryRawUnsafe(`SELECT name FROM "${TRACKING_TABLE}"`);
const appliedNames = new Set(alreadyApplied.map((row) => row.name));
const applied = [];
for (const file2 of files) {
if (appliedNames.has(file2))
continue;
const statements = splitSqlStatements((0, fs_1.readFileSync)((0, path_1.join)(dir, file2), "utf8"));
await prisma.$transaction(
async (tx) => {
for (const statement of statements) {
await tx.$executeRawUnsafe(statement);
}
await tx.$executeRawUnsafe(`INSERT INTO "${TRACKING_TABLE}" (name) VALUES ($1)`, file2);
},
// The baseline migration is hundreds of DDL statements; the 5s default is
// not enough, especially against a remote database.
{ maxWait: 6e4, timeout: 6e5 }
);
applied.push(file2);
}
return { freshDatabase, applied };
}
async function runOnce(prisma, name, task) {
const rows = await prisma.$queryRawUnsafe(`SELECT name FROM "${TRACKING_TABLE}" WHERE name = $1`, name);
if (rows.length > 0)
return false;
await task();
await prisma.$executeRawUnsafe(`INSERT INTO "${TRACKING_TABLE}" (name) VALUES ($1)`, name);
return true;
}
}
});
// dist/prisma/bootstrap.js
var require_bootstrap = __commonJS({
"dist/prisma/bootstrap.js"(exports2) {
"use strict";
Object.defineProperty(exports2, "__esModule", { value: true });
exports2.prepareDatabase = prepareDatabase;
var child_process_1 = require("child_process");
var fs_1 = require("fs");
var path_1 = require("path");
var util_1 = require("util");
var common_12 = require_common();
var client_1 = require("@prisma/client");
var migrate_1 = require_migrate();
var execFileAsync = (0, util_1.promisify)(child_process_1.execFile);
function resolveSeedScript() {
const candidates = [
process.env.SEED_SCRIPT,
(0, path_1.join)(process.cwd(), "seed.js"),
(0, path_1.join)(__dirname, "..", "..", "seed.js")
].filter((p) => Boolean(p));
return candidates.find((file2) => (0, fs_1.existsSync)(file2));
}
async function prepareDatabase() {
if (process.env.RUN_MIGRATIONS === "false")
return;
const logger = new common_12.Logger("DatabaseBootstrap");
const prisma = new client_1.PrismaClient();
try {
const { applied } = await (0, migrate_1.runMigrations)(prisma);
if (applied.length > 0) {
logger.log(`Applied migrations: ${applied.join(", ")}`);
} else {
logger.log("No pending migrations");
}
if (process.env.RUN_SEED === "false")
return;
const seedScript = resolveSeedScript();
if (!seedScript) {
logger.warn("No seed script found \u2014 skipping seed");
return;
}
const seeded = await (0, migrate_1.runOnce)(prisma, "seed:initial", async () => {
const { stdout } = await execFileAsync(process.execPath, [seedScript]);
if (stdout.trim())
logger.log(stdout.trim());
});
if (seeded)
logger.log("Seed data inserted");
} finally {
await prisma.$disconnect();
}
}
}
});
// dist/prisma/prisma-exception.filter.js
var require_prisma_exception_filter = __commonJS({
"dist/prisma/prisma-exception.filter.js"(exports2) {
@@ -77626,8 +77810,10 @@ require_Reflect();
var core_1 = require_core3();
var common_1 = require_common();
var app_module_1 = require_app_module();
var bootstrap_1 = require_bootstrap();
var prisma_exception_filter_1 = require_prisma_exception_filter();
async function bootstrap() {
await (0, bootstrap_1.prepareDatabase)();
const app = await core_1.NestFactory.create(app_module_1.AppModule);
app.setGlobalPrefix("api");
app.useGlobalPipes(new common_1.ValidationPipe({ whitelist: true, transform: true }));