|
1 | 1 | import { ChildProcess, IOType } from "node:child_process"; |
2 | 2 | import spawn from "cross-spawn"; |
3 | 3 | import process from "node:process"; |
4 | | -import { Stream } from "node:stream"; |
| 4 | +import { Stream, PassThrough } from "node:stream"; |
5 | 5 | import { ReadBuffer, serializeMessage } from "../shared/stdio.js"; |
6 | 6 | import { Transport } from "../shared/transport.js"; |
7 | 7 | import { JSONRPCMessage } from "../types.js"; |
@@ -93,13 +93,17 @@ export class StdioClientTransport implements Transport { |
93 | 93 | private _abortController: AbortController = new AbortController(); |
94 | 94 | private _readBuffer: ReadBuffer = new ReadBuffer(); |
95 | 95 | private _serverParams: StdioServerParameters; |
| 96 | + private _stderrStream: PassThrough | null = null; |
96 | 97 |
|
97 | 98 | onclose?: () => void; |
98 | 99 | onerror?: (error: Error) => void; |
99 | 100 | onmessage?: (message: JSONRPCMessage) => void; |
100 | 101 |
|
101 | 102 | constructor(server: StdioServerParameters) { |
102 | 103 | this._serverParams = server; |
| 104 | + if (server.stderr === "pipe" || server.stderr === "overlapped") { |
| 105 | + this._stderrStream = new PassThrough(); |
| 106 | + } |
103 | 107 | } |
104 | 108 |
|
105 | 109 | /** |
@@ -158,15 +162,25 @@ export class StdioClientTransport implements Transport { |
158 | 162 | this._process.stdout?.on("error", (error) => { |
159 | 163 | this.onerror?.(error); |
160 | 164 | }); |
| 165 | + |
| 166 | + if (this._stderrStream && this._process.stderr) { |
| 167 | + this._process.stderr.pipe(this._stderrStream); |
| 168 | + } |
161 | 169 | }); |
162 | 170 | } |
163 | 171 |
|
164 | 172 | /** |
165 | 173 | * The stderr stream of the child process, if `StdioServerParameters.stderr` was set to "pipe" or "overlapped". |
166 | 174 | * |
167 | | - * This is only available after the process has been started. |
| 175 | + * If stderr piping was requested, a PassThrough stream is returned _immediately_, allowing callers to |
| 176 | + * attach listeners before the start method is invoked. This prevents loss of any early |
| 177 | + * error output emitted by the child process. |
168 | 178 | */ |
169 | 179 | get stderr(): Stream | null { |
| 180 | + if (this._stderrStream) { |
| 181 | + return this._stderrStream; |
| 182 | + } |
| 183 | + |
170 | 184 | return this._process?.stderr ?? null; |
171 | 185 | } |
172 | 186 |
|
|
0 commit comments