import path from "node:path"; import { adapterEnv, identityShimEnabled, isDeployedEnv } from "../src/config/env"; import { exitOnInvalidConfig } from "../src/config/fail-fast"; import { buildPostgresOptions, databaseUrlFor, scrubDbError, type DbPurpose } from "../src/infra/db/connection-policy"; import { createFileDb } from "../src/infra/db/pglite"; import { createPostgresDb } from "../src/infra/db/postgres"; import { assertDatabaseGuard } from "../src/infra/db/target-guard"; import type { Db } from "../src/infra/db/types"; /** * Banco para os scripts operacionais. Em staging e produção `DATABASE_URL` é * obrigatória: cair em PGlite com um segredo ausente faria uma migração * silenciosa num arquivo local parecer sucesso. */ export async function openOperationalDb(purpose: DbPurpose): Promise { const databaseUrl = databaseUrlFor(purpose); if (databaseUrl) { const db = createPostgresDb(databaseUrl, buildPostgresOptions(databaseUrl, purpose)); try { await assertDatabaseGuard(db, purpose, databaseUrl); return db; } catch (error) { await db.close().catch(() => undefined); throw error; } } if (isDeployedEnv()) { throw new Error("DATABASE_URL é obrigatória fora do PGlite de development."); } const dataDir = path.join(process.cwd(), ".data", "pglite"); const { mkdir } = await import("node:fs/promises"); await mkdir(dataDir, { recursive: true }); return createFileDb(dataDir); } /** Recusa configuração inválida antes de tocar em qualquer recurso externo. */ export function bootstrapScript(name: string): void { exitOnInvalidConfig(process.env); console.log(`[${name}] ambiente=${adapterEnv()} shim_identidade=${identityShimEnabled() ? "sim" : "nao"}`); } export function describeTarget(): string { return process.env.DATABASE_URL ? "postgres" : "pglite local"; } /** * Executa o corpo do script. * * Existe para que nenhum script use top-level await: `tsx` compila `.ts` como * CommonJS quando o `package.json` não declara `"type": "module"`, e aí top-level * await é erro de transformação. Isso impedia rodar os scripts com * `node --import tsx`, que é a forma que entrega SIGTERM ao nosso processo — o * CLI do `tsx` cria um processo filho e o sinal não chega ao handler. */ export function runScript(name: string, body: () => Promise): void { body().then( () => { process.exitCode ??= 0; }, (error: unknown) => { console.error(`[${name}] ${scrubDbError(error)}`); process.exit(1); }, ); } /** Abre o banco, executa e fecha, mesmo em caso de falha. */ export function runWithDb(name: string, purpose: DbPurpose, body: (db: Db) => Promise): void { runScript(name, async () => { const db = await openOperationalDb(purpose); try { await body(db); } finally { await db.close().catch(() => undefined); } }); }