Files
doc-manager/apps/api/scripts/dev-server.ts
T
admin 4553f63deb init: M1 scaffolding + M2 organization/clients/services CRUD
- monorepo (npm workspaces): apps/api (Fastify+Prisma+TS), apps/web (Vite+React+TS), packages/shared (zod schemas)
- SSO via auth.queo.ru: jose+JWKS plugin, requireDocPermission(viewer|user|admin)
- DEV_BYPASS_AUTH for local development (hard-checked off in production)
- M2: organization upsert, clients CRUD with search, services catalog with soft-delete
- BigInt -> Number serializer for Prisma money columns
- Embedded Postgres + npm run dev:demo for one-command local boot
- Docker compose for queoserver: postgres + api + web (nginx as ingress proxying /api -> api:3030)
- First migration 0_init committed (prisma migrate diff)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 21:24:26 +03:00

130 lines
3.9 KiB
TypeScript

/**
* 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<void> {
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<typeof spawn> | 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');
});