| 49 | } |
| 50 | |
| 51 | function spawnServer(cwd: string, env: NodeJS.ProcessEnv = {}): SpawnedServer { |
| 52 | const child = spawn(process.execPath, [BIN, 'serve', '--mcp'], { |
| 53 | cwd, |
| 54 | stdio: ['pipe', 'pipe', 'pipe'], |
| 55 | // #618: the daemon-attach log line is now off by default; opt the test |
| 56 | // harness into it (CODEGRAPH_MCP_LOG_ATTACH=1) so the attach assertions |
| 57 | // below can still observe a successful attach. A per-test env still wins. |
| 58 | env: { CODEGRAPH_MCP_LOG_ATTACH: '1', ...process.env, ...env }, |
| 59 | }) as ChildProcessWithoutNullStreams; |
| 60 | // Swallow spawn/EPIPE errors so killing a child mid-write can't surface as an |
| 61 | // unhandled error that crashes the vitest worker. |
| 62 | child.on('error', () => { /* ignore */ }); |
| 63 | child.stdin.on('error', () => { /* ignore */ }); |
| 64 | const stdout: string[] = []; |
| 65 | const stderr: string[] = []; |
| 66 | let stdoutBuf = ''; |
| 67 | let stderrBuf = ''; |
| 68 | child.stdout.on('data', (chunk: Buffer) => { |
| 69 | stdoutBuf += chunk.toString('utf8'); |
| 70 | let idx: number; |
| 71 | while ((idx = stdoutBuf.indexOf('\n')) !== -1) { |
| 72 | stdout.push(stdoutBuf.slice(0, idx)); |
| 73 | stdoutBuf = stdoutBuf.slice(idx + 1); |
| 74 | } |
| 75 | }); |
| 76 | child.stderr.on('data', (chunk: Buffer) => { |
| 77 | stderrBuf += chunk.toString('utf8'); |
| 78 | let idx: number; |
| 79 | while ((idx = stderrBuf.indexOf('\n')) !== -1) { |
| 80 | stderr.push(stderrBuf.slice(0, idx)); |
| 81 | stderrBuf = stderrBuf.slice(idx + 1); |
| 82 | } |
| 83 | }); |
| 84 | return { child, stdout, stderr }; |
| 85 | } |
| 86 | |
| 87 | function sendMessage(child: ChildProcessWithoutNullStreams, msg: unknown): void { |
| 88 | try { child.stdin.write(JSON.stringify(msg) + '\n'); } catch { /* child may be gone */ } |