import { spawn, exec, ChildProcess } from 'child_process'; import { promisify } from 'util'; import fetch from 'node-fetch'; const execAsync = promisify(exec); async function waitForServer(url: string, maxRetries: number = 30, interval: number = 1000): Promise { for (let i = 0; i < maxRetries; i++) { try { const response = await fetch(url); if (response.ok) { console.log('✓ Server is ready'); return true; } } catch (error) { // Server not ready yet } console.log(`Waiting for server... (${i + 1}/${maxRetries})`); await new Promise(resolve => setTimeout(resolve, interval)); } return false; } // 改进全局变量类型 let serverProcess: ChildProcess | null = null; let webProcess: ChildProcess | null = null; async function startWeb(): Promise { console.log('Starting Vite frontend...'); return new Promise((resolve, reject) => { const process = spawn('npm', ['run', 'dev'], { stdio: 'pipe' }); let webStarted = false; process.stdout?.on('data', (data) => { const output = data.toString(); console.log(`Web: ${output}`); // 检查 Vite 是否已启动 if ((output.includes('Local:') || output.includes('ready in')) && !webStarted) { webStarted = true; resolve(process); } }); process.stderr?.on('data', (data) => { console.error(`Web Error: ${data}`); }); process.on('error', (error) => { reject(error); }); process.on('exit', (code, signal) => { console.log(`Web process exited with code ${code} and signal ${signal}`); if (!webStarted) { reject(new Error(`Web process exited unexpectedly with code ${code}`)); } }); // 存储进程引用 webProcess = process; // 超时处理 setTimeout(() => { if (!webStarted) { reject(new Error('Web server failed to start within timeout')); } }, 30000); // 30秒超时 }); } async function startServer(): Promise { console.log('Starting .NET server...'); return new Promise((resolve, reject) => { const process = spawn('dotnet', ['run', '--property:Configuration=Release'], { cwd: 'server', stdio: 'pipe' }); let serverStarted = false; process.stdout?.on('data', (data) => { const output = data.toString(); console.log(`Server: ${output}`); // 检查服务器是否已启动 if (output.includes('Now listening on:') && !serverStarted) { serverStarted = true; resolve(process); } }); process.stderr?.on('data', (data) => { console.error(`Server Error: ${data}`); }); process.on('error', (error) => { reject(error); }); process.on('exit', (code, signal) => { console.log(`Server process exited with code ${code} and signal ${signal}`); if (!serverStarted) { reject(new Error(`Server process exited unexpectedly with code ${code}`)); } }); // 存储进程引用 serverProcess = process; // 超时处理 setTimeout(() => { if (!serverStarted) { reject(new Error('Server failed to start within timeout')); } }, 30000); // 30秒超时 }); } async function stopServer(): Promise { console.log('Stopping server...'); if (!serverProcess) { console.log('No server process to stop'); return; } try { // 检查进程是否还存在 if (serverProcess.killed || serverProcess.exitCode !== null) { console.log('✓ Server process already terminated'); serverProcess = null; return; } // 发送 SIGTERM 信号 const killed = serverProcess.kill('SIGTERM'); if (!killed) { console.warn('Failed to send SIGTERM to server process'); return; } // 等待进程优雅退出 const exitPromise = new Promise((resolve) => { if (serverProcess) { serverProcess.on('exit', () => { console.log('✓ Server stopped gracefully'); resolve(); }); } else { resolve(); } }); // 设置超时,如果 5 秒内没有退出则强制终止 const timeoutPromise = new Promise((resolve) => { setTimeout(() => { if (serverProcess && !serverProcess.killed && serverProcess.exitCode === null) { console.log('Force killing server process...'); serverProcess.kill('SIGKILL'); } resolve(); }, 5000); }); await Promise.race([exitPromise, timeoutPromise]); } catch (error) { console.warn('Warning: Could not stop server process:', error); } finally { serverProcess = null; // 额外清理:确保没有遗留的 dotnet 进程 try { if (process.platform !== 'win32') { // 只清理与我们项目相关的进程 await execAsync('pkill -f "dotnet.*run.*--property:Configuration=Release"').catch(() => { // 忽略错误,可能没有匹配的进程 }); } } catch (cleanupError) { // 忽略清理错误 } } } async function stopWeb(): Promise { console.log('Stopping web server...'); if (!webProcess) { console.log('No web process to stop'); return; } try { // 检查进程是否还存在 if (webProcess.killed || webProcess.exitCode !== null) { console.log('✓ Web process already terminated'); webProcess = null; return; } // 发送 SIGTERM 信号 const killed = webProcess.kill('SIGTERM'); if (!killed) { console.warn('Failed to send SIGTERM to web process'); return; } // 等待进程优雅退出 const exitPromise = new Promise((resolve) => { if (webProcess) { webProcess.on('exit', () => { console.log('✓ Web server stopped gracefully'); resolve(); }); } else { resolve(); } }); // 设置超时,如果 5 秒内没有退出则强制终止 const timeoutPromise = new Promise((resolve) => { setTimeout(() => { if (webProcess && !webProcess.killed && webProcess.exitCode === null) { console.log('Force killing web process...'); webProcess.kill('SIGKILL'); } resolve(); }, 5000); }); await Promise.race([exitPromise, timeoutPromise]); } catch (error) { console.warn('Warning: Could not stop web process:', error); } finally { webProcess = null; // 额外清理:确保没有遗留的 npm/node 进程 try { if (process.platform !== 'win32') { // 清理可能的 vite 进程 await execAsync('pkill -f "vite"').catch(() => { // 忽略错误,可能没有匹配的进程 }); } } catch (cleanupError) { // 忽略清理错误 } } } async function generateApiClient(): Promise { console.log('Generating API client...'); try { await execAsync('npx nswag openapi2tsclient /input:http://localhost:5000/swagger/v1/swagger.json /output:src/APIClient.ts'); console.log('✓ API client generated successfully'); } catch (error) { throw new Error(`Failed to generate API client: ${error}`); } } async function main(): Promise { try { // Start web frontend first await startWeb(); console.log('✓ Frontend started'); // Wait a bit for frontend to fully initialize await new Promise(resolve => setTimeout(resolve, 3000)); // Start server await startServer(); console.log('✓ Backend started'); // Wait for server to be ready (给服务器额外时间完全启动) await new Promise(resolve => setTimeout(resolve, 2000)); // Check if swagger endpoint is available const serverReady = await waitForServer('http://localhost:5000/swagger/v1/swagger.json'); if (!serverReady) { throw new Error('Server failed to start within the expected time'); } // Generate API client await generateApiClient(); console.log('✓ API generation completed successfully'); } catch (error) { console.error('❌ Error:', error); process.exit(1); } finally { // Always try to stop processes in order: server first, then web await stopServer(); await stopWeb(); } } // 改进的进程终止处理 const cleanup = async (signal: string) => { console.log(`\nReceived ${signal}, cleaning up...`); await stopServer(); await stopWeb(); process.exit(0); }; process.on('SIGINT', () => cleanup('SIGINT')); process.on('SIGTERM', () => cleanup('SIGTERM')); // 处理未捕获的异常 process.on('uncaughtException', async (error) => { console.error('❌ Uncaught exception:', error); await stopServer(); await stopWeb(); process.exit(1); }); process.on('unhandledRejection', async (reason, promise) => { console.error('❌ Unhandled rejection at:', promise, 'reason:', reason); await stopServer(); await stopWeb(); process.exit(1); }); main().catch(async (error) => { console.error('❌ Unhandled error:', error); await stopServer(); await stopWeb(); process.exit(1); });