Skip to content

Commit a7922a8

Browse files
Merge pull request #198 from zhibisora/add-cross-spawn
Add cross spawn and the test for it
2 parents 54aa316 + c6635d7 commit a7922a8

File tree

4 files changed

+150
-10
lines changed

4 files changed

+150
-10
lines changed

package-lock.json

+15-9
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+2
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
"dependencies": {
4949
"content-type": "^1.0.5",
5050
"cors": "^2.8.5",
51+
"cross-spawn": "^7.0.3",
5152
"eventsource": "^3.0.2",
5253
"express": "^5.0.1",
5354
"express-rate-limit": "^7.5.0",
@@ -61,6 +62,7 @@
6162
"@jest-mock/express": "^3.0.0",
6263
"@types/content-type": "^1.1.8",
6364
"@types/cors": "^2.8.17",
65+
"@types/cross-spawn": "^6.0.6",
6466
"@types/eslint__js": "^8.42.3",
6567
"@types/eventsource": "^1.1.15",
6668
"@types/express": "^5.0.0",

src/client/cross-spawn.test.ts

+131
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
import { StdioClientTransport } from "./stdio.js";
2+
import spawn from "cross-spawn";
3+
import { JSONRPCMessage } from "../types.js";
4+
import { ChildProcess } from "node:child_process";
5+
6+
// mock cross-spawn
7+
jest.mock("cross-spawn");
8+
const mockSpawn = spawn as jest.MockedFunction<typeof spawn>;
9+
10+
describe("StdioClientTransport using cross-spawn", () => {
11+
beforeEach(() => {
12+
// mock cross-spawn's return value
13+
mockSpawn.mockImplementation(() => {
14+
const mockProcess: {
15+
on: jest.Mock;
16+
stdin?: { on: jest.Mock; write: jest.Mock };
17+
stdout?: { on: jest.Mock };
18+
stderr?: null;
19+
} = {
20+
on: jest.fn((event: string, callback: () => void) => {
21+
if (event === "spawn") {
22+
callback();
23+
}
24+
return mockProcess;
25+
}),
26+
stdin: {
27+
on: jest.fn(),
28+
write: jest.fn().mockReturnValue(true)
29+
},
30+
stdout: {
31+
on: jest.fn()
32+
},
33+
stderr: null
34+
};
35+
return mockProcess as unknown as ChildProcess;
36+
});
37+
});
38+
39+
afterEach(() => {
40+
jest.clearAllMocks();
41+
});
42+
43+
test("should call cross-spawn correctly", async () => {
44+
const transport = new StdioClientTransport({
45+
command: "test-command",
46+
args: ["arg1", "arg2"]
47+
});
48+
49+
await transport.start();
50+
51+
// verify spawn is called correctly
52+
expect(mockSpawn).toHaveBeenCalledWith(
53+
"test-command",
54+
["arg1", "arg2"],
55+
expect.objectContaining({
56+
shell: false
57+
})
58+
);
59+
});
60+
61+
test("should pass environment variables correctly", async () => {
62+
const customEnv = { TEST_VAR: "test-value" };
63+
const transport = new StdioClientTransport({
64+
command: "test-command",
65+
env: customEnv
66+
});
67+
68+
await transport.start();
69+
70+
// verify environment variables are passed correctly
71+
expect(mockSpawn).toHaveBeenCalledWith(
72+
"test-command",
73+
[],
74+
expect.objectContaining({
75+
env: customEnv
76+
})
77+
);
78+
});
79+
80+
test("should send messages correctly", async () => {
81+
const transport = new StdioClientTransport({
82+
command: "test-command"
83+
});
84+
85+
// get the mock process object
86+
const mockProcess: {
87+
on: jest.Mock;
88+
stdin: {
89+
on: jest.Mock;
90+
write: jest.Mock;
91+
once: jest.Mock;
92+
};
93+
stdout: {
94+
on: jest.Mock;
95+
};
96+
stderr: null;
97+
} = {
98+
on: jest.fn((event: string, callback: () => void) => {
99+
if (event === "spawn") {
100+
callback();
101+
}
102+
return mockProcess;
103+
}),
104+
stdin: {
105+
on: jest.fn(),
106+
write: jest.fn().mockReturnValue(true),
107+
once: jest.fn()
108+
},
109+
stdout: {
110+
on: jest.fn()
111+
},
112+
stderr: null
113+
};
114+
115+
mockSpawn.mockReturnValue(mockProcess as unknown as ChildProcess);
116+
117+
await transport.start();
118+
119+
// 关键修复:确保 jsonrpc 是字面量 "2.0"
120+
const message: JSONRPCMessage = {
121+
jsonrpc: "2.0",
122+
id: "test-id",
123+
method: "test-method"
124+
};
125+
126+
await transport.send(message);
127+
128+
// verify message is sent correctly
129+
expect(mockProcess.stdin.write).toHaveBeenCalled();
130+
});
131+
});

src/client/stdio.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { ChildProcess, IOType, spawn } from "node:child_process";
1+
import { ChildProcess, IOType } from "node:child_process";
2+
import spawn from "cross-spawn";
23
import process from "node:process";
34
import { Stream } from "node:stream";
45
import { ReadBuffer, serializeMessage } from "../shared/stdio.js";

0 commit comments

Comments
 (0)