/* eslint-disable @typescript-eslint/no-require-imports */ /** * Container entrypoint: wait for DB, apply Prisma migrations, then start Next server. * * Why: * - In real deployments you want schema changes applied automatically per deploy. * - `prisma migrate deploy` is safe to run multiple times (idempotent). * * Controls: * - Set `SKIP_PRISMA_MIGRATE=true` to skip migrations (emergency / debugging). * - DB_WAIT_RETRIES (default 15) and DB_WAIT_INTERVAL_MS (default 2000) control * how long to wait for the database before giving up. */ const { spawnSync } = require("node:child_process"); const { createConnection } = require("node:net"); const fs = require("node:fs"); const path = require("node:path"); function run(cmd, args, opts = {}) { const res = spawnSync(cmd, args, { stdio: "inherit", env: process.env, ...opts, }); if (res.error) { throw res.error; } if (typeof res.status === "number" && res.status !== 0) { process.exit(res.status); } } /** * Wait for a TCP port to be reachable. * Parses DATABASE_URL to extract host and port. */ function waitForDb() { const dbUrl = process.env.DATABASE_URL; if (!dbUrl) { console.log("[startup] No DATABASE_URL set, skipping DB wait."); return Promise.resolve(); } let host, port; try { // postgresql://user:pass@host:port/db?schema=public const match = dbUrl.match(/@([^:/?]+):(\d+)/); if (!match) throw new Error("Could not parse host:port from DATABASE_URL"); host = match[1]; port = parseInt(match[2], 10); } catch (_err) { console.log("[startup] Could not parse DATABASE_URL, skipping DB wait."); return Promise.resolve(); } const maxRetries = parseInt(process.env.DB_WAIT_RETRIES || "15", 10); const intervalMs = parseInt(process.env.DB_WAIT_INTERVAL_MS || "2000", 10); return new Promise((resolve, reject) => { let attempt = 0; function tryConnect() { attempt++; const sock = createConnection({ host, port }, () => { sock.destroy(); console.log(`[startup] Database at ${host}:${port} is reachable.`); resolve(); }); sock.setTimeout(1500); sock.on("error", () => { sock.destroy(); if (attempt >= maxRetries) { console.error( `[startup] Database at ${host}:${port} not reachable after ${maxRetries} attempts. Proceeding anyway...` ); resolve(); // still try migration - prisma will give a clear error } else { console.log( `[startup] Waiting for database at ${host}:${port}... (${attempt}/${maxRetries})` ); setTimeout(tryConnect, intervalMs); } }); sock.on("timeout", () => { sock.destroy(); if (attempt >= maxRetries) { console.error( `[startup] Database at ${host}:${port} timed out after ${maxRetries} attempts. Proceeding anyway...` ); resolve(); } else { console.log( `[startup] Waiting for database at ${host}:${port}... (${attempt}/${maxRetries})` ); setTimeout(tryConnect, intervalMs); } }); } tryConnect(); }); } async function main() { // 1. Wait for database to be reachable await waitForDb(); // 2. Run migrations const skip = String(process.env.SKIP_PRISMA_MIGRATE || "").toLowerCase() === "true"; if (!skip) { const autoBaseline = String(process.env.PRISMA_AUTO_BASELINE || "").toLowerCase() === "true"; if (autoBaseline) { try { const migrationsDir = path.join(process.cwd(), "prisma", "migrations"); const entries = fs .readdirSync(migrationsDir, { withFileTypes: true }) .filter((d) => d.isDirectory()) .map((d) => d.name); const initMigration = entries.find((n) => n.endsWith("_init")); if (initMigration) { run("node", [ "node_modules/prisma/build/index.js", "migrate", "resolve", "--applied", initMigration, ]); } } catch (_err) { // If baseline fails we continue to migrate deploy, which will surface the real issue. } } const migrateResult = spawnSync( "node", ["node_modules/prisma/build/index.js", "migrate", "deploy"], { stdio: "inherit", env: process.env } ); if (migrateResult.status !== 0) { console.error( `[startup] prisma migrate deploy failed (exit ${migrateResult.status}). Starting server anyway...` ); } } else { console.log("SKIP_PRISMA_MIGRATE=true -> skipping prisma migrate deploy"); } // 3. Start the server run("node", ["server.js"]); } main();