import { execFileSync, spawn } from 'node:child_process'; import { existsSync, readFileSync } from 'node:fs'; import { resolve } from 'node:path'; const repoRoot = process.cwd(); const apiServerExePath = resolve( repoRoot, 'server-rs/target/debug/api-server.exe', ); function loadEnvFile(path, target) { if (!existsSync(path)) { return; } const rawText = readFileSync(path, 'utf8'); for (const rawLine of rawText.split(/\r?\n/u)) { const line = rawLine.trim(); if (!line || line.startsWith('#')) { continue; } const match = line.match(/^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/u); if (!match) { continue; } const [, key, rawValue] = match; if (target[key] !== undefined) { continue; } target[key] = rawValue.replace(/^['"]|['"]$/gu, ''); } } const mergedEnv = { ...process.env }; loadEnvFile(resolve(repoRoot, '.env'), mergedEnv); loadEnvFile(resolve(repoRoot, '.env.local'), mergedEnv); mergedEnv.GENARRATIVE_API_HOST = mergedEnv.GENARRATIVE_API_HOST || '127.0.0.1'; mergedEnv.GENARRATIVE_API_PORT = mergedEnv.GENARRATIVE_API_PORT || '3100'; mergedEnv.GENARRATIVE_SPACETIME_SERVER_URL = mergedEnv.GENARRATIVE_SPACETIME_SERVER_URL || 'http://127.0.0.1:3101'; mergedEnv.GENARRATIVE_SPACETIME_DATABASE = mergedEnv.GENARRATIVE_SPACETIME_DATABASE || ''; mergedEnv.GENARRATIVE_SPACETIME_TOKEN = mergedEnv.GENARRATIVE_SPACETIME_TOKEN || ''; if (!mergedEnv.GENARRATIVE_SPACETIME_DATABASE) { console.error( '[api-server] 缺少 GENARRATIVE_SPACETIME_DATABASE。', ); process.exit(1); } function stopExistingWindowsApiServer() { if (process.platform !== 'win32') { return; } // Windows 下 cargo 重新编译时无法覆盖仍在运行的 exe,只清理本仓库 target 内的旧进程。 const command = [ '$ErrorActionPreference = "Continue"', '$target = [System.IO.Path]::GetFullPath($env:GENARRATIVE_API_SERVER_EXE_TARGET)', '$processes = Get-Process -Name api-server -ErrorAction SilentlyContinue | Where-Object {', ' $_.Path -and ([System.IO.Path]::GetFullPath($_.Path) -ieq $target)', '}', 'foreach ($process in $processes) {', ' try {', ' Stop-Process -Id $process.Id -Force -ErrorAction Stop', ' Wait-Process -Id $process.Id -Timeout 5 -ErrorAction SilentlyContinue', ' Write-Output $process.Id', ' } catch {', ' Write-Error "[api-server] 忽略旧进程清理瞬时失败 pid=$($process.Id): $($_.Exception.Message)"', ' }', '}', 'exit 0', ].join('\n'); const output = execFileSync( 'powershell.exe', ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-Command', command], { encoding: 'utf8', env: { ...process.env, GENARRATIVE_API_SERVER_EXE_TARGET: apiServerExePath, }, }, ).trim(); if (output) { console.log(`[api-server] 已停止旧 api-server 进程: ${output}`); } } try { stopExistingWindowsApiServer(); } catch (error) { console.error( `[api-server] 清理旧 api-server 进程失败: ${error.message}`, ); process.exit(1); } console.log( `[api-server] SpacetimeDB ${mergedEnv.GENARRATIVE_SPACETIME_DATABASE} @ ${mergedEnv.GENARRATIVE_SPACETIME_SERVER_URL}`, ); const child = spawn( 'cargo', ['run', '-p', 'api-server', '--manifest-path', 'server-rs/Cargo.toml'], { cwd: repoRoot, env: mergedEnv, stdio: 'inherit', }, ); child.on('error', (error) => { console.error(`[api-server] 启动 cargo 失败: ${error.message}`); process.exit(1); }); child.on('exit', (code, signal) => { if (signal) { console.error(`[api-server] api-server 被信号终止: ${signal}`); process.exit(1); } process.exit(code ?? 0); });