@codescene/codehealth-mcp 1.1.4 → 1.1.6
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.
- package/lib/run.js +102 -25
- package/package.json +1 -1
package/lib/run.js
CHANGED
|
@@ -10,7 +10,53 @@
|
|
|
10
10
|
* works as expected.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import {
|
|
13
|
+
import { spawn } from "node:child_process";
|
|
14
|
+
|
|
15
|
+
/** Signals that indicate a normal client-initiated shutdown. */
|
|
16
|
+
const CLEAN_SHUTDOWN_SIGNALS = new Set(["SIGTERM", "SIGINT"]);
|
|
17
|
+
|
|
18
|
+
/** Maps signal names to their numeric values for exit code encoding. */
|
|
19
|
+
const SIGNAL_NUMBERS = { SIGKILL: 9, SIGHUP: 1 };
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Handle spawn errors (binary not found, permission denied, etc.).
|
|
23
|
+
* Throws for unexpected errors.
|
|
24
|
+
*
|
|
25
|
+
* @param {Error} error - The spawn error
|
|
26
|
+
* @param {string} binaryPath - Path to the binary that failed to spawn
|
|
27
|
+
* @returns {never}
|
|
28
|
+
*/
|
|
29
|
+
function handleSpawnError(error, binaryPath) {
|
|
30
|
+
if (error.code === "ENOENT") {
|
|
31
|
+
process.stderr.write(`Error: Binary not found at ${binaryPath}\n`);
|
|
32
|
+
process.exit(127);
|
|
33
|
+
}
|
|
34
|
+
if (error.code === "EACCES") {
|
|
35
|
+
process.stderr.write(
|
|
36
|
+
`Error: Permission denied executing ${binaryPath}\n`
|
|
37
|
+
);
|
|
38
|
+
process.exit(126);
|
|
39
|
+
}
|
|
40
|
+
throw error;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Determine the exit code when the child was killed by a signal.
|
|
45
|
+
*
|
|
46
|
+
* SIGTERM and SIGINT are normal shutdown signals sent by MCP clients
|
|
47
|
+
* (e.g. VS Code, Zed) when the user closes the agent. Exiting with
|
|
48
|
+
* a non-zero code causes the client to surface a "fatal error" dialog,
|
|
49
|
+
* so treat these as clean exits.
|
|
50
|
+
*
|
|
51
|
+
* @param {string} signal - The signal name (e.g. "SIGTERM")
|
|
52
|
+
* @returns {number} The exit code to use
|
|
53
|
+
*/
|
|
54
|
+
function exitCodeForSignal(signal) {
|
|
55
|
+
if (CLEAN_SHUTDOWN_SIGNALS.has(signal)) {
|
|
56
|
+
return 0;
|
|
57
|
+
}
|
|
58
|
+
return 128 + (SIGNAL_NUMBERS[signal] || 1);
|
|
59
|
+
}
|
|
14
60
|
|
|
15
61
|
/**
|
|
16
62
|
* Runs the cs-mcp binary, forwarding all stdio and signals.
|
|
@@ -23,37 +69,68 @@ import { spawnSync } from "node:child_process";
|
|
|
23
69
|
* @returns {never}
|
|
24
70
|
*/
|
|
25
71
|
export function runBinary(binaryPath, args) {
|
|
26
|
-
const
|
|
72
|
+
const child = spawn(binaryPath, args, {
|
|
27
73
|
stdio: "inherit",
|
|
28
74
|
env: process.env,
|
|
29
75
|
windowsHide: true,
|
|
30
76
|
});
|
|
31
77
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
process.exit(126);
|
|
78
|
+
let exited = false;
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Exit exactly once and unregister signal handlers.
|
|
82
|
+
*
|
|
83
|
+
* @param {number} code
|
|
84
|
+
*/
|
|
85
|
+
function exitOnce(code) {
|
|
86
|
+
if (exited) {
|
|
87
|
+
return;
|
|
43
88
|
}
|
|
44
|
-
|
|
45
|
-
}
|
|
89
|
+
exited = true;
|
|
46
90
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
process.exit(result.status);
|
|
91
|
+
process.off("SIGTERM", forwardSigterm);
|
|
92
|
+
process.off("SIGINT", forwardSigint);
|
|
93
|
+
process.exit(code);
|
|
51
94
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Forward a signal to the child process.
|
|
98
|
+
*
|
|
99
|
+
* @param {NodeJS.Signals} signal
|
|
100
|
+
*/
|
|
101
|
+
function forwardSignal(signal) {
|
|
102
|
+
if (child.exitCode !== null || child.signalCode !== null) {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
try {
|
|
106
|
+
child.kill(signal);
|
|
107
|
+
} catch {
|
|
108
|
+
// Ignore race conditions where the child exits before kill().
|
|
109
|
+
}
|
|
57
110
|
}
|
|
58
|
-
|
|
111
|
+
|
|
112
|
+
const forwardSigterm = () => forwardSignal("SIGTERM");
|
|
113
|
+
const forwardSigint = () => forwardSignal("SIGINT");
|
|
114
|
+
|
|
115
|
+
process.on("SIGTERM", forwardSigterm);
|
|
116
|
+
process.on("SIGINT", forwardSigint);
|
|
117
|
+
|
|
118
|
+
child.once("error", (error) => {
|
|
119
|
+
handleSpawnError(error, binaryPath);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
child.once("exit", (status, signal) => {
|
|
123
|
+
if (status !== null) {
|
|
124
|
+
exitOnce(status);
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
if (signal) {
|
|
128
|
+
exitOnce(exitCodeForSignal(signal));
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
exitOnce(1);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
// Keep the wrapper process alive while the child is running.
|
|
135
|
+
// Actual exit happens in the child "exit" event handler above.
|
|
59
136
|
}
|
package/package.json
CHANGED