/** * Dev orchestrator: embedded Postgres → prisma db push → seed → API server. * Запуск: npm run dev:demo (в корне) или tsx scripts/dev-server.ts (в apps/api). * * Использовать ТОЛЬКО для локальной разработки. Защита: процесс выходит, * если NODE_ENV=production или DEV_BYPASS_AUTH не включён (см. ниже). */ import { spawn } from 'node:child_process'; import { existsSync, mkdirSync } from 'node:fs'; import { dirname, resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; import EmbeddedPostgres from 'embedded-postgres'; const __dirname = dirname(fileURLToPath(import.meta.url)); const apiRoot = resolve(__dirname, '..'); const PG_PORT = 5433; const PG_USER = 'postgres'; const PG_PASSWORD = 'postgres'; const PG_DB = 'docmanager'; const PG_DATA = resolve(apiRoot, '../../data/embedded-pg'); const DATABASE_URL = `postgresql://${PG_USER}:${PG_PASSWORD}@localhost:${PG_PORT}/${PG_DB}?schema=public`; if (process.env.NODE_ENV === 'production') { console.error('FATAL: dev-server.ts запрещён в production'); process.exit(1); } mkdirSync(dirname(PG_DATA), { recursive: true }); const pg = new EmbeddedPostgres({ databaseDir: PG_DATA, user: PG_USER, password: PG_PASSWORD, port: PG_PORT, persistent: true, }); async function exec(cmd: string, args: string[], env: NodeJS.ProcessEnv): Promise { return new Promise((res, rej) => { const child = spawn(cmd, args, { stdio: 'inherit', cwd: apiRoot, env: { ...process.env, ...env }, shell: process.platform === 'win32', }); child.on('exit', (code) => (code === 0 ? res() : rej(new Error(`${cmd} ${args.join(' ')} exited ${code}`)))); child.on('error', rej); }); } let serverChild: ReturnType | null = null; let stopping = false; async function shutdown(reason: string) { if (stopping) return; stopping = true; console.log(`\n[dev-server] shutdown: ${reason}`); try { if (serverChild && !serverChild.killed) { serverChild.kill('SIGTERM'); await new Promise((r) => setTimeout(r, 500)); } } catch (e) { console.warn('[dev-server] error stopping API:', e); } try { await pg.stop(); } catch (e) { console.warn('[dev-server] error stopping pg:', e); } process.exit(0); } process.on('SIGINT', () => void shutdown('SIGINT')); process.on('SIGTERM', () => void shutdown('SIGTERM')); async function main() { const dataInitialised = existsSync(resolve(PG_DATA, 'PG_VERSION')); if (!dataInitialised) { console.log('[dev-server] initialising embedded Postgres (~80MB binaries on first run)…'); await pg.initialise(); } console.log(`[dev-server] starting Postgres on :${PG_PORT}…`); await pg.start(); if (!dataInitialised) { console.log(`[dev-server] creating database "${PG_DB}"…`); try { await pg.createDatabase(PG_DB); } catch (e) { console.warn('[dev-server] createDatabase warning:', (e as Error).message); } } console.log('[dev-server] applying schema (prisma db push)…'); await exec('npx', ['prisma', 'db', 'push', '--skip-generate', '--accept-data-loss'], { DATABASE_URL }); console.log('[dev-server] seeding default organization…'); await exec('npx', ['tsx', 'prisma/seed.ts'], { DATABASE_URL }); console.log('[dev-server] starting API server…'); serverChild = spawn('npx', ['tsx', 'watch', 'src/server.ts'], { stdio: 'inherit', cwd: apiRoot, env: { ...process.env, DATABASE_URL, DEV_BYPASS_AUTH: '1', PORT: '3030', HOST: '127.0.0.1', NODE_ENV: 'development', }, shell: process.platform === 'win32', }); serverChild.on('exit', (code) => { if (!stopping) { console.error(`[dev-server] API exited unexpectedly (${code})`); void shutdown('api-exit'); } }); } main().catch((err) => { console.error('[dev-server] fatal:', err); void shutdown('fatal'); });