mcp-probe-kit 3.0.10 → 3.0.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as os from "node:os";
|
|
3
3
|
import * as path from "node:path";
|
|
4
|
+
import spawn from "cross-spawn";
|
|
4
5
|
import { afterEach, describe, expect, test } from "vitest";
|
|
5
6
|
import { prepareBridgeWorkspace, resolveExecutableCommand, resolveSpawnCommand } from "../gitnexus-bridge.js";
|
|
6
7
|
const tempRoots = [];
|
|
@@ -17,6 +18,23 @@ function makeTempDir(prefix) {
|
|
|
17
18
|
tempRoots.push(dir);
|
|
18
19
|
return dir;
|
|
19
20
|
}
|
|
21
|
+
async function runSpawned(command, args) {
|
|
22
|
+
return await new Promise((resolve, reject) => {
|
|
23
|
+
const child = spawn(command, args, { windowsHide: true });
|
|
24
|
+
let stdout = "";
|
|
25
|
+
let stderr = "";
|
|
26
|
+
child.stdout?.on("data", (chunk) => {
|
|
27
|
+
stdout += String(chunk);
|
|
28
|
+
});
|
|
29
|
+
child.stderr?.on("data", (chunk) => {
|
|
30
|
+
stderr += String(chunk);
|
|
31
|
+
});
|
|
32
|
+
child.on("error", reject);
|
|
33
|
+
child.on("close", (code) => {
|
|
34
|
+
resolve({ code, stdout, stderr });
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
}
|
|
20
38
|
describe("gitnexus-bridge workspace preparation", () => {
|
|
21
39
|
test("Windows 下将 npx/git 解析为 cmd 可执行文件", () => {
|
|
22
40
|
const npxResolved = resolveExecutableCommand("npx", "win32").toLowerCase();
|
|
@@ -27,24 +45,39 @@ describe("gitnexus-bridge workspace preparation", () => {
|
|
|
27
45
|
expect(resolveExecutableCommand("git", "win32").toLowerCase()).toContain("git");
|
|
28
46
|
expect(resolveExecutableCommand("npx", "linux").toLowerCase()).toContain("npx");
|
|
29
47
|
});
|
|
30
|
-
test("Windows
|
|
48
|
+
test("Windows 下 npx 命令直接交给底层 spawn 处理", () => {
|
|
31
49
|
const wrapped = resolveSpawnCommand("npx", ["-y", "gitnexus@latest", "mcp"], "win32");
|
|
32
|
-
expect(wrapped.command.toLowerCase()).toContain("cmd");
|
|
33
|
-
expect(wrapped.
|
|
34
|
-
expect(
|
|
50
|
+
expect(wrapped.command.toLowerCase()).not.toContain("cmd.exe");
|
|
51
|
+
expect(wrapped.command.toLowerCase()).toContain("npx");
|
|
52
|
+
expect(wrapped.args).toEqual(["-y", "gitnexus@latest", "mcp"]);
|
|
53
|
+
});
|
|
54
|
+
test.runIf(process.platform === "win32")("Windows 下带空格路径的 cmd 可执行文件可以真实启动", async () => {
|
|
55
|
+
const root = makeTempDir("gitnexus-space-");
|
|
56
|
+
const executable = path.join(root, "Program Files", "tool.cmd");
|
|
57
|
+
fs.mkdirSync(path.dirname(executable), { recursive: true });
|
|
58
|
+
fs.writeFileSync(executable, "@echo off\r\necho ok %*\r\n", "utf-8");
|
|
59
|
+
const wrapped = resolveSpawnCommand(executable, ["-y", "gitnexus@latest", "mcp"], "win32");
|
|
60
|
+
const result = await runSpawned(wrapped.command, wrapped.args);
|
|
61
|
+
expect(wrapped.command).toBe(executable);
|
|
62
|
+
expect(wrapped.args).toEqual(["-y", "gitnexus@latest", "mcp"]);
|
|
63
|
+
expect(result.code).toBe(0);
|
|
64
|
+
expect(result.stdout).toContain("ok");
|
|
65
|
+
expect(result.stdout).toContain("-y");
|
|
66
|
+
expect(result.stdout).toContain("gitnexus@latest");
|
|
67
|
+
expect(result.stdout).toContain("mcp");
|
|
68
|
+
});
|
|
69
|
+
test.runIf(process.platform === "win32")("Windows 下 resolveSpawnCommand 生成的 npx 命令可真实执行", async () => {
|
|
70
|
+
const spawned = resolveSpawnCommand("npx", ["--version"], "win32");
|
|
71
|
+
const result = await runSpawned(spawned.command, spawned.args);
|
|
72
|
+
expect(result.code).toBe(0);
|
|
73
|
+
expect(result.stdout.trim().length).toBeGreaterThan(0);
|
|
35
74
|
});
|
|
36
75
|
test("Windows 下 git.exe 直接启动,不走 git.cmd 壳层", () => {
|
|
37
76
|
const resolved = resolveExecutableCommand("git", "win32").toLowerCase();
|
|
38
77
|
const spawned = resolveSpawnCommand("git", ["init", "-q"], "win32");
|
|
39
78
|
expect(resolved).toContain("git");
|
|
40
|
-
expect(resolved.endsWith(".exe") || resolved === "git").toBe(true);
|
|
41
|
-
expect(spawned.command.toLowerCase()).
|
|
42
|
-
if (spawned.command.toLowerCase().includes("cmd")) {
|
|
43
|
-
expect(String(spawned.args[3]).toLowerCase()).not.toContain("git.cmd");
|
|
44
|
-
}
|
|
45
|
-
else {
|
|
46
|
-
expect(spawned.command.toLowerCase()).toContain("git");
|
|
47
|
-
}
|
|
79
|
+
expect(resolved.endsWith(".exe") || resolved === "git" || resolved === "git.cmd").toBe(true);
|
|
80
|
+
expect(spawned.command.toLowerCase()).toContain("git");
|
|
48
81
|
});
|
|
49
82
|
test("git 目录直接使用源仓库", async () => {
|
|
50
83
|
const repoRoot = makeTempDir("gitnexus-direct-");
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
2
2
|
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
3
|
-
import { execFileSync
|
|
3
|
+
import { execFileSync } from "node:child_process";
|
|
4
|
+
import spawn from "cross-spawn";
|
|
4
5
|
import * as fs from "node:fs";
|
|
5
6
|
import * as os from "node:os";
|
|
6
7
|
import * as path from "node:path";
|
|
@@ -239,35 +240,11 @@ function findExecutablePath(command, platform = process.platform) {
|
|
|
239
240
|
}
|
|
240
241
|
return undefined;
|
|
241
242
|
}
|
|
242
|
-
function shouldWrapWithCmd(rawCommand, executable, platform = process.platform) {
|
|
243
|
-
if (platform !== "win32") {
|
|
244
|
-
return false;
|
|
245
|
-
}
|
|
246
|
-
const rawLower = (rawCommand || "").trim().toLowerCase();
|
|
247
|
-
const executableLower = (executable || "").trim().toLowerCase();
|
|
248
|
-
const ext = path.extname(executableLower);
|
|
249
|
-
if (!rawLower && !executableLower) {
|
|
250
|
-
return true;
|
|
251
|
-
}
|
|
252
|
-
if (ext === ".cmd" || ext === ".bat") {
|
|
253
|
-
return true;
|
|
254
|
-
}
|
|
255
|
-
if (ext === ".exe") {
|
|
256
|
-
return false;
|
|
257
|
-
}
|
|
258
|
-
return rawLower === "npx" || rawLower === "npm";
|
|
259
|
-
}
|
|
260
243
|
export function resolveSpawnCommand(command, args, platform = process.platform) {
|
|
261
244
|
const executable = resolveExecutableCommand(command, platform);
|
|
262
|
-
if (!shouldWrapWithCmd(command, executable, platform)) {
|
|
263
|
-
return {
|
|
264
|
-
command: executable,
|
|
265
|
-
args,
|
|
266
|
-
};
|
|
267
|
-
}
|
|
268
245
|
return {
|
|
269
|
-
command:
|
|
270
|
-
args
|
|
246
|
+
command: executable,
|
|
247
|
+
args,
|
|
271
248
|
};
|
|
272
249
|
}
|
|
273
250
|
function extractText(result) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mcp-probe-kit",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.12",
|
|
4
4
|
"description": "AI-Powered Development Toolkit - MCP Server with 22 tools covering code quality, development efficiency, project management, and UI/UX design. Features: Structured Output, Workflow Orchestration, UI/UX Pro Max, and Requirements Interview.",
|
|
5
5
|
"mcpName": "io.github.mybolide/mcp-probe-kit",
|
|
6
6
|
"type": "module",
|
|
@@ -64,10 +64,12 @@
|
|
|
64
64
|
],
|
|
65
65
|
"dependencies": {
|
|
66
66
|
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
67
|
+
"cross-spawn": "^7.0.6",
|
|
67
68
|
"csv-parse": "^6.1.0",
|
|
68
69
|
"tar": "^7.5.6"
|
|
69
70
|
},
|
|
70
71
|
"devDependencies": {
|
|
72
|
+
"@types/cross-spawn": "^6.0.6",
|
|
71
73
|
"@types/node": "^20.0.0",
|
|
72
74
|
"@types/tar": "^6.1.13",
|
|
73
75
|
"@vitest/ui": "^4.0.18",
|