|
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