import { spawn } from 'node:child_process'; import { existsSync, readFileSync } from 'node:fs'; import { dirname, resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; const scriptDirectory = dirname(fileURLToPath(import.meta.url)); const repositoryRoot = resolve(scriptDirectory, '../../..'); const environmentFile = resolve(repositoryRoot, '.env'); const localEnvironment = existsSync(environmentFile) ? parseEnvironment(readFileSync(environmentFile, 'utf8')) : {}; const apiPort = process.env.API_PORT ?? localEnvironment.API_PORT ?? '8200'; const swaggerEnabled = (process.env.SWAGGER_ENABLED ?? localEnvironment.SWAGGER_ENABLED ?? 'true') === 'true'; const nodeEnvironment = process.env.NODE_ENV ?? localEnvironment.NODE_ENV ?? 'development'; const swaggerUrl = `http://localhost:${apiPort}/api-docs`; const tsxCommand = process.platform === 'win32' ? 'tsx.cmd' : 'tsx'; const apiProcess = spawn(tsxCommand, ['watch', 'src/main.ts'], { shell: process.platform === 'win32', stdio: 'inherit', }); if ( process.platform === 'darwin' && nodeEnvironment === 'development' && swaggerEnabled ) { void openSwaggerAfterApiIsReady(); } for (const signal of ['SIGINT', 'SIGTERM']) { process.on(signal, () => apiProcess.kill(signal)); } apiProcess.on('exit', (code, signal) => { if (signal) process.exitCode = 1; else process.exitCode = code ?? 0; }); function parseEnvironment(content) { return Object.fromEntries( content .split(/\r?\n/) .map((line) => line.match(/^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/)) .filter(Boolean) .map((match) => [match[1], match[2]]), ); } async function openSwaggerAfterApiIsReady() { const deadline = Date.now() + 30_000; while (Date.now() < deadline) { try { const response = await fetch(`http://localhost:${apiPort}/health`); if (response.ok) { // 开发启动器进程不会随 tsx watch 的热重启重建,因此只会打开一次。 spawn('open', [swaggerUrl], { detached: true, stdio: 'ignore', }).unref(); return; } } catch { // API 仍在启动或热重启,继续等待到超时为止。 } await new Promise((resolveDelay) => setTimeout(resolveDelay, 250)); } console.warn(`Swagger 未在 30 秒内就绪:${swaggerUrl}`); }