@codescene/codehealth-mcp 1.0.0 → 1.0.1
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/download.js +6 -0
- package/lib/index.js +57 -6
- package/package.json +1 -1
package/lib/download.js
CHANGED
|
@@ -220,6 +220,12 @@ async function renameExtractedBinary(cacheDir, binaryPath) {
|
|
|
220
220
|
* @returns {Promise<string>} Path to the binary
|
|
221
221
|
*/
|
|
222
222
|
export async function ensureBinary(version) {
|
|
223
|
+
if (typeof version !== "string" || !version) {
|
|
224
|
+
throw new Error(
|
|
225
|
+
`ensureBinary requires a non-empty version string, got: ${JSON.stringify(version)}`
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
|
|
223
229
|
const binaryPath = getCachedBinaryPath(version);
|
|
224
230
|
|
|
225
231
|
if (existsSync(binaryPath)) {
|
package/lib/index.js
CHANGED
|
@@ -8,20 +8,50 @@
|
|
|
8
8
|
import { readFileSync, existsSync } from "node:fs";
|
|
9
9
|
import { join, dirname, resolve } from "node:path";
|
|
10
10
|
import { fileURLToPath } from "node:url";
|
|
11
|
+
import { setTimeout as delay } from "node:timers/promises";
|
|
11
12
|
import { ensureBinary } from "./download.js";
|
|
12
13
|
import { runBinary } from "./run.js";
|
|
13
14
|
|
|
14
15
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
15
16
|
const PACKAGE_ROOT = join(__dirname, "..");
|
|
16
17
|
|
|
18
|
+
/** Maximum number of retry attempts when package files are temporarily unavailable. */
|
|
19
|
+
const MAX_RETRIES = 5;
|
|
20
|
+
|
|
21
|
+
/** Delay in milliseconds between retry attempts. */
|
|
22
|
+
const RETRY_DELAY_MS = 1000;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Returns true if the error is transient — caused by package files being
|
|
26
|
+
* replaced during an npm update (file missing, partially written, or
|
|
27
|
+
* missing version field).
|
|
28
|
+
*
|
|
29
|
+
* @param {Error} err
|
|
30
|
+
* @returns {boolean}
|
|
31
|
+
*/
|
|
32
|
+
function isTransientStartupError(err) {
|
|
33
|
+
if (err.code === "ENOENT") return true;
|
|
34
|
+
if (err instanceof SyntaxError) return true;
|
|
35
|
+
if (err.message && err.message.includes("missing version")) return true;
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
|
|
17
39
|
/**
|
|
18
40
|
* Reads the package version from package.json.
|
|
19
41
|
*
|
|
20
42
|
* @returns {string}
|
|
43
|
+
* @throws {Error} If version is missing or not a string.
|
|
21
44
|
*/
|
|
22
45
|
function getPackageVersion() {
|
|
23
46
|
const pkgPath = join(PACKAGE_ROOT, "package.json");
|
|
24
47
|
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
48
|
+
if (typeof pkg.version !== "string" || !pkg.version) {
|
|
49
|
+
throw new Error(
|
|
50
|
+
"Invalid or missing version in package.json — " +
|
|
51
|
+
"the package may be updating. " +
|
|
52
|
+
`Got: ${JSON.stringify(pkg.version)}`
|
|
53
|
+
);
|
|
54
|
+
}
|
|
25
55
|
return pkg.version;
|
|
26
56
|
}
|
|
27
57
|
|
|
@@ -33,19 +63,40 @@ function getPackageVersion() {
|
|
|
33
63
|
* 2. Cached binary for the current package version
|
|
34
64
|
* 3. Download from GitHub releases and cache
|
|
35
65
|
*
|
|
66
|
+
* When package files are temporarily unavailable (e.g. during an npm
|
|
67
|
+
* update), retries up to MAX_RETRIES times before giving up. This
|
|
68
|
+
* prevents transient errors when VS Code restarts the MCP server
|
|
69
|
+
* while the package is being replaced.
|
|
70
|
+
*
|
|
36
71
|
* All command-line arguments (except the node binary and script path)
|
|
37
72
|
* are forwarded to the cs-mcp binary.
|
|
38
73
|
*/
|
|
39
74
|
export async function main() {
|
|
40
75
|
const args = process.argv.slice(2);
|
|
41
76
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
77
|
+
let lastErr;
|
|
78
|
+
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
79
|
+
try {
|
|
80
|
+
const binaryPath = await resolveBinaryPath();
|
|
81
|
+
runBinary(binaryPath, args);
|
|
82
|
+
return;
|
|
83
|
+
} catch (err) {
|
|
84
|
+
lastErr = err;
|
|
85
|
+
if (attempt < MAX_RETRIES && isTransientStartupError(err)) {
|
|
86
|
+
process.stderr.write(
|
|
87
|
+
`Package files unavailable (${err.code || err.constructor.name}), ` +
|
|
88
|
+
`retrying in ${RETRY_DELAY_MS / 1000}s ` +
|
|
89
|
+
`(${attempt + 1}/${MAX_RETRIES})...\n`
|
|
90
|
+
);
|
|
91
|
+
await delay(RETRY_DELAY_MS);
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
48
96
|
}
|
|
97
|
+
|
|
98
|
+
process.stderr.write(`Error: ${lastErr.message}\n`);
|
|
99
|
+
process.exit(1);
|
|
49
100
|
}
|
|
50
101
|
|
|
51
102
|
/**
|
package/package.json
CHANGED