mcp-probe-kit 3.0.11 → 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,34 +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 下将 cmd 工具包装为 cmd.exe /c 启动", () => {
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.args.slice(0, 3)).toEqual(["/d", "/s", "/c"]);
34
- expect(String(wrapped.args[3]).toLowerCase()).toContain("npx");
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"]);
35
53
  });
36
- test("Windows 下带空格路径的 cmd 可执行文件会被正确加引号", () => {
54
+ test.runIf(process.platform === "win32")("Windows 下带空格路径的 cmd 可执行文件可以真实启动", async () => {
37
55
  const root = makeTempDir("gitnexus-space-");
38
- const executable = path.join(root, "Program Files", "nodejs", "npx.cmd");
56
+ const executable = path.join(root, "Program Files", "tool.cmd");
39
57
  fs.mkdirSync(path.dirname(executable), { recursive: true });
40
- fs.writeFileSync(executable, "@echo off\r\n", "utf-8");
58
+ fs.writeFileSync(executable, "@echo off\r\necho ok %*\r\n", "utf-8");
41
59
  const wrapped = resolveSpawnCommand(executable, ["-y", "gitnexus@latest", "mcp"], "win32");
42
- expect(wrapped.command.toLowerCase()).toContain("cmd");
43
- expect(wrapped.args.slice(0, 3)).toEqual(["/d", "/s", "/c"]);
44
- expect(wrapped.args[3]).toBe(`"${executable}"`);
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);
45
74
  });
46
75
  test("Windows 下 git.exe 直接启动,不走 git.cmd 壳层", () => {
47
76
  const resolved = resolveExecutableCommand("git", "win32").toLowerCase();
48
77
  const spawned = resolveSpawnCommand("git", ["init", "-q"], "win32");
49
78
  expect(resolved).toContain("git");
50
- expect(resolved.endsWith(".exe") || resolved === "git").toBe(true);
51
- expect(spawned.command.toLowerCase()).not.toContain("cmd.exe /d /s /c git.cmd");
52
- if (spawned.command.toLowerCase().includes("cmd")) {
53
- expect(String(spawned.args[3]).toLowerCase()).not.toContain("git.cmd");
54
- }
55
- else {
56
- expect(spawned.command.toLowerCase()).toContain("git");
57
- }
79
+ expect(resolved.endsWith(".exe") || resolved === "git" || resolved === "git.cmd").toBe(true);
80
+ expect(spawned.command.toLowerCase()).toContain("git");
58
81
  });
59
82
  test("git 目录直接使用源仓库", async () => {
60
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, spawn } from "node:child_process";
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,44 +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
- function quoteForCmd(executable) {
261
- if (!executable.includes(" ")) {
262
- return executable;
263
- }
264
- if (executable.startsWith("\"") && executable.endsWith("\"")) {
265
- return executable;
266
- }
267
- return `"${executable}"`;
268
- }
269
243
  export function resolveSpawnCommand(command, args, platform = process.platform) {
270
244
  const executable = resolveExecutableCommand(command, platform);
271
- if (!shouldWrapWithCmd(command, executable, platform)) {
272
- return {
273
- command: executable,
274
- args,
275
- };
276
- }
277
245
  return {
278
- command: process.env.ComSpec || "cmd.exe",
279
- args: ["/d", "/s", "/c", quoteForCmd(executable), ...args],
246
+ command: executable,
247
+ args,
280
248
  };
281
249
  }
282
250
  function extractText(result) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcp-probe-kit",
3
- "version": "3.0.11",
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",