feat: auto-run SQL migrations and one-time seed on boot
This commit is contained in:
@@ -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 }));
|
||||
|
||||
Reference in New Issue
Block a user