#!/usr/bin/env node const { spawnSync } = require('node:child_process') const { readdirSync } = require('node:fs') const path = require('node:path') const { ensureDatabaseUrl, loadDefaultEnvFiles, normalizeDatabaseUrlForExecution, } = require('../src/runtime-config') const { PrismaClient } = require('../generated') const repoRoot = path.resolve(__dirname, '../../..') const prismaBin = path.join(repoRoot, 'node_modules', '.bin', 'prisma') const schemaPath = path.join(repoRoot, 'packages/database/prisma/schema.prisma') const migrationsDir = path.join(repoRoot, 'packages/database/prisma/migrations') function getDatabaseName(databaseUrl) { try { const parsed = new URL(databaseUrl) const databaseName = parsed.pathname.replace(/^\/+/, '') return databaseName ? decodeURIComponent(databaseName) : null } catch { return null } } function getMaintenanceDatabaseUrl(databaseUrl) { try { const parsed = new URL(databaseUrl) parsed.pathname = '/postgres' return parsed.toString() } catch { return databaseUrl } } function quoteIdentifier(value) { return `"${String(value).replace(/"/g, '""')}"` } function run(command, args) { const result = spawnSync(command, args, { stdio: 'inherit', cwd: repoRoot, env: process.env, }) if (result.status !== 0) { process.exit(result.status ?? 1) } } async function main() { loadDefaultEnvFiles(repoRoot) ensureDatabaseUrl() normalizeDatabaseUrlForExecution() const targetDatabaseUrl = process.env.DATABASE_URL const targetDatabaseName = getDatabaseName(targetDatabaseUrl) if (targetDatabaseUrl && targetDatabaseName && targetDatabaseName !== 'postgres') { const adminPrisma = new PrismaClient({ datasources: { db: { url: getMaintenanceDatabaseUrl(targetDatabaseUrl), }, }, }) try { const existingDatabase = await adminPrisma.$queryRaw` SELECT datname FROM pg_database WHERE datname = ${targetDatabaseName} LIMIT 1 ` if (existingDatabase.length === 0) { console.log(`[db:deploy] Creating missing database ${targetDatabaseName}`) await adminPrisma.$executeRawUnsafe( `CREATE DATABASE ${quoteIdentifier(targetDatabaseName)}`, ) } } finally { await adminPrisma.$disconnect() } } const prisma = new PrismaClient() try { const [state] = await prisma.$queryRawUnsafe(` SELECT to_regclass('public.companies') IS NOT NULL AS "hasCompanies", to_regclass('public.employees') IS NOT NULL AS "hasEmployees", to_regclass('public._prisma_migrations') IS NOT NULL AS "hasMigrationsTable" `) const failedMigrations = state.hasMigrationsTable ? await prisma.$queryRawUnsafe(` SELECT migration_name FROM "_prisma_migrations" WHERE finished_at IS NULL AND rolled_back_at IS NULL ORDER BY started_at `) : [] const hasBaseSchema = state.hasCompanies || state.hasEmployees if (!hasBaseSchema) { if (failedMigrations.length > 0) { console.log('[db:deploy] Fresh database with failed migration records detected; marking them rolled back before bootstrap') for (const migration of failedMigrations) { run(prismaBin, [ 'migrate', 'resolve', '--schema', schemaPath, '--rolled-back', migration.migration_name, ]) } } console.log('[db:deploy] Fresh database detected; bootstrapping schema with prisma db push') run(prismaBin, ['db', 'push', '--schema', schemaPath]) const migrationNames = readdirSync(migrationsDir, { withFileTypes: true }) .filter((entry) => entry.isDirectory()) .map((entry) => entry.name) .sort() if (migrationNames.length > 0) { console.log('[db:deploy] Marking existing migrations as applied for the bootstrapped schema') for (const migrationName of migrationNames) { run(prismaBin, [ 'migrate', 'resolve', '--schema', schemaPath, '--applied', migrationName, ]) } } return } if (failedMigrations.length > 0) { console.error('[db:deploy] Found failed migrations in a non-empty database. Resolve them manually before deploying again.') for (const migration of failedMigrations) { console.error(`- ${migration.migration_name}`) } process.exit(1) } console.log('[db:deploy] Existing database detected; applying Prisma migrations') run(prismaBin, ['migrate', 'deploy', '--schema', schemaPath]) } finally { await prisma.$disconnect() } } main().catch((error) => { if (error?.message?.includes("Can't reach database server at `localhost:5432`")) { console.error('[db:deploy] Hint: start the local Postgres service first. In this repo that is usually `docker compose -f docker-compose.dev.yml up -d postgres`.') } console.error('[db:deploy] Failed:', error) process.exit(1) })