Replace native spawn with cross-spawn for improved cross-platform compatibility

This commit is contained in:
zhibisora 2025-03-16 05:00:36 +08:00
commit d4bf73b06e
3 changed files with 115 additions and 1 deletions

View file

@ -48,6 +48,7 @@
"dependencies": {
"content-type": "^1.0.5",
"cors": "^2.8.5",
"cross-spawn": "^7.0.3",
"eventsource": "^3.0.2",
"express": "^5.0.1",
"express-rate-limit": "^7.5.0",

View file

@ -0,0 +1,112 @@
import { StdioClientTransport } from "./stdio.js";
import spawn from "cross-spawn";
// mock cross-spawn
jest.mock("cross-spawn");
const mockSpawn = spawn as jest.MockedFunction<typeof spawn>;
describe("StdioClientTransport using cross-spawn", () => {
beforeEach(() => {
// mock cross-spawn's return value
mockSpawn.mockImplementation(() => {
const mockProcess = {
on: jest.fn((event: string, callback: Function) => {
if (event === "spawn") {
callback();
}
return mockProcess;
}),
stdin: {
on: jest.fn(),
write: jest.fn().mockReturnValue(true)
},
stdout: {
on: jest.fn()
},
stderr: null
};
return mockProcess as any;
});
});
afterEach(() => {
jest.clearAllMocks();
});
test("should call cross-spawn correctly", async () => {
const transport = new StdioClientTransport({
command: "test-command",
args: ["arg1", "arg2"]
});
await transport.start();
// verify spawn is called correctly
expect(mockSpawn).toHaveBeenCalledWith(
"test-command",
["arg1", "arg2"],
expect.objectContaining({
shell: false
})
);
});
test("should pass environment variables correctly", async () => {
const customEnv = { TEST_VAR: "test-value" };
const transport = new StdioClientTransport({
command: "test-command",
env: customEnv
});
await transport.start();
// verify environment variables are passed correctly
expect(mockSpawn).toHaveBeenCalledWith(
"test-command",
[],
expect.objectContaining({
env: customEnv
})
);
});
test("should send messages correctly", async () => {
const transport = new StdioClientTransport({
command: "test-command"
});
// get the mock process object
const mockProcess = {
on: jest.fn((event, callback) => {
if (event === "spawn") {
callback();
}
return mockProcess;
}),
stdin: {
on: jest.fn(),
write: jest.fn().mockReturnValue(true),
once: jest.fn()
},
stdout: {
on: jest.fn()
},
stderr: null
};
mockSpawn.mockReturnValue(mockProcess as any);
await transport.start();
const message = {
jsonrpc: "2.0",
id: "test-id",
method: "test-method"
};
await transport.send(message);
// verify message is sent correctly
expect(mockProcess.stdin.write).toHaveBeenCalled();
});
});

View file

@ -1,4 +1,5 @@
import { ChildProcess, IOType, spawn } from "node:child_process";
import { ChildProcess, IOType } from "node:child_process";
import spawn from "cross-spawn";
import process from "node:process";
import { Stream } from "node:stream";
import { ReadBuffer, serializeMessage } from "../shared/stdio.js";