Dev Build / cleanup (push) Canceled after 0s
Dev Build / typecheck (push) Canceled after 0s
Dev Build / build (linux-arm64, ubuntu-24.04-arm, linux-arm64) (push) Canceled after 0s
Dev Build / build (linux-x64, ubuntu-latest, linux-x64) (push) Canceled after 0s
Dev Build / build (win-x64, windows-latest, win32-x64) (push) Canceled after 0s
Dev Build / dispatch-dev-docker (push) Canceled after 0s
218 lines
6.0 KiB
Plaintext
218 lines
6.0 KiB
Plaintext
import { Nt as SdkError, Pt as SdkErrorCode, f as serializeMessage, l as ReadBuffer } from "./src-WCy6ifGf.mjs";
|
|
import process from "node:process";
|
|
import { PassThrough } from "node:stream";
|
|
import spawn from "cross-spawn";
|
|
|
|
//#region src/client/stdio.ts
|
|
/**
|
|
* Environment variables to inherit by default, if an environment is not explicitly given.
|
|
*/
|
|
const DEFAULT_INHERITED_ENV_VARS = process.platform === "win32" ? [
|
|
"APPDATA",
|
|
"COMSPEC",
|
|
"HOMEDRIVE",
|
|
"HOMEPATH",
|
|
"LOCALAPPDATA",
|
|
"PATH",
|
|
"PATHEXT",
|
|
"PROCESSOR_ARCHITECTURE",
|
|
"PROGRAMDATA",
|
|
"PROGRAMFILES",
|
|
"PROGRAMFILES(X86)",
|
|
"PROGRAMW6432",
|
|
"SYSTEMDRIVE",
|
|
"SYSTEMROOT",
|
|
"TEMP",
|
|
"USERNAME",
|
|
"USERPROFILE",
|
|
"WINDIR"
|
|
] : [
|
|
"HOME",
|
|
"LOGNAME",
|
|
"PATH",
|
|
"SHELL",
|
|
"TERM",
|
|
"USER"
|
|
];
|
|
/**
|
|
* Returns a default environment object including only environment variables deemed safe to inherit.
|
|
*/
|
|
function getDefaultEnvironment() {
|
|
const env = {};
|
|
for (const key of DEFAULT_INHERITED_ENV_VARS) {
|
|
const value = process.env[key];
|
|
if (value === void 0) continue;
|
|
if (value.startsWith("()")) continue;
|
|
env[key] = value;
|
|
}
|
|
return env;
|
|
}
|
|
/**
|
|
* Client transport for stdio: this will connect to a server by spawning a process and communicating with it over stdin/stdout.
|
|
*
|
|
* This transport is only available in Node.js environments.
|
|
*/
|
|
var StdioClientTransport = class {
|
|
_process;
|
|
_readBuffer;
|
|
_serverParams;
|
|
_stderrStream = null;
|
|
onclose;
|
|
onerror;
|
|
onmessage;
|
|
constructor(server) {
|
|
this._serverParams = server;
|
|
this._readBuffer = new ReadBuffer({ maxBufferSize: server.maxBufferSize });
|
|
if (server.stderr === "pipe" || server.stderr === "overlapped") this._stderrStream = new PassThrough();
|
|
}
|
|
/**
|
|
* Starts the server process and prepares to communicate with it.
|
|
*/
|
|
async start() {
|
|
if (this._process) throw new Error("StdioClientTransport already started! If using Client class, note that connect() calls start() automatically.");
|
|
return new Promise((resolve, reject) => {
|
|
this._process = spawn(this._serverParams.command, this._serverParams.args ?? [], {
|
|
env: {
|
|
...getDefaultEnvironment(),
|
|
...this._serverParams.env
|
|
},
|
|
stdio: [
|
|
"pipe",
|
|
"pipe",
|
|
this._serverParams.stderr ?? "inherit"
|
|
],
|
|
shell: false,
|
|
windowsHide: process.platform === "win32",
|
|
cwd: this._serverParams.cwd
|
|
});
|
|
this._process.on("error", (error) => {
|
|
reject(error);
|
|
this.onerror?.(error);
|
|
});
|
|
this._process.on("spawn", () => {
|
|
resolve();
|
|
});
|
|
this._process.on("close", (_code) => {
|
|
this._process = void 0;
|
|
this.onclose?.();
|
|
});
|
|
this._process.stdin?.on("error", (error) => {
|
|
this.onerror?.(error);
|
|
});
|
|
this._process.stdout?.on("data", (chunk) => {
|
|
try {
|
|
this._readBuffer.append(chunk);
|
|
this.processReadBuffer();
|
|
} catch (error) {
|
|
this.onerror?.(error);
|
|
this.close().catch(() => {});
|
|
}
|
|
});
|
|
this._process.stdout?.on("error", (error) => {
|
|
this.onerror?.(error);
|
|
});
|
|
if (this._stderrStream && this._process.stderr) this._process.stderr.pipe(this._stderrStream);
|
|
});
|
|
}
|
|
/**
|
|
* The `stderr` stream of the child process, if {@linkcode StdioServerParameters.stderr} was set to `"pipe"` or `"overlapped"`.
|
|
*
|
|
* If `stderr` piping was requested, a `PassThrough` stream is returned _immediately_, allowing callers to
|
|
* attach listeners before the `start` method is invoked. This prevents loss of any early
|
|
* error output emitted by the child process.
|
|
*/
|
|
get stderr() {
|
|
if (this._stderrStream) return this._stderrStream;
|
|
return this._process?.stderr ?? null;
|
|
}
|
|
/**
|
|
* The child process pid spawned by this transport.
|
|
*
|
|
* This is only available after the transport has been started.
|
|
*/
|
|
get pid() {
|
|
return this._process?.pid ?? null;
|
|
}
|
|
processReadBuffer() {
|
|
while (true) try {
|
|
const message = this._readBuffer.readMessage();
|
|
if (message === null) break;
|
|
this.onmessage?.(message);
|
|
} catch (error) {
|
|
this.onerror?.(error);
|
|
}
|
|
}
|
|
/**
|
|
* Reap a disposable probe sibling (see the version-negotiation sibling
|
|
* flow): signal-first teardown awaiting process `exit` — never the `close`
|
|
* event, so a helper process holding the child's stdio pipes can never
|
|
* block disposal. Not part of the public transport lifecycle.
|
|
*
|
|
* @internal
|
|
*/
|
|
async _dispose() {
|
|
const proc = this._process;
|
|
this._process = void 0;
|
|
if (proc && proc.exitCode === null && proc.signalCode === null) {
|
|
const exited = new Promise((resolve) => proc.once("exit", () => resolve()));
|
|
try {
|
|
proc.stdin?.end();
|
|
} catch {}
|
|
try {
|
|
proc.kill("SIGTERM");
|
|
} catch {}
|
|
await Promise.race([exited, new Promise((resolve) => setTimeout(resolve, 1e3).unref())]);
|
|
if (proc.exitCode === null && proc.signalCode === null) try {
|
|
proc.kill("SIGKILL");
|
|
} catch {}
|
|
await exited;
|
|
}
|
|
try {
|
|
proc?.stdout?.destroy();
|
|
} catch {}
|
|
try {
|
|
proc?.stdin?.destroy();
|
|
} catch {}
|
|
try {
|
|
proc?.stderr?.destroy();
|
|
} catch {}
|
|
this._readBuffer.clear();
|
|
}
|
|
async close() {
|
|
if (this._process) {
|
|
const processToClose = this._process;
|
|
this._process = void 0;
|
|
const closePromise = new Promise((resolve) => {
|
|
processToClose.once("close", () => {
|
|
resolve();
|
|
});
|
|
});
|
|
try {
|
|
processToClose.stdin?.end();
|
|
} catch {}
|
|
await Promise.race([closePromise, new Promise((resolve) => setTimeout(resolve, 2e3).unref())]);
|
|
if (processToClose.exitCode === null) {
|
|
try {
|
|
processToClose.kill("SIGTERM");
|
|
} catch {}
|
|
await Promise.race([closePromise, new Promise((resolve) => setTimeout(resolve, 2e3).unref())]);
|
|
}
|
|
if (processToClose.exitCode === null) try {
|
|
processToClose.kill("SIGKILL");
|
|
} catch {}
|
|
}
|
|
this._readBuffer.clear();
|
|
}
|
|
send(message) {
|
|
return new Promise((resolve) => {
|
|
if (!this._process?.stdin) throw new SdkError(SdkErrorCode.NotConnected, "Not connected");
|
|
const json = serializeMessage(message);
|
|
if (this._process.stdin.write(json)) resolve();
|
|
else this._process.stdin.once("drain", resolve);
|
|
});
|
|
}
|
|
};
|
|
|
|
//#endregion
|
|
export { DEFAULT_INHERITED_ENV_VARS, StdioClientTransport, getDefaultEnvironment };
|
|
//# sourceMappingURL=stdio.mjs.map |