@sdsrs/code-graph 0.44.0 → 0.45.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.
|
@@ -182,6 +182,21 @@ function cachedBinaryPath() {
|
|
|
182
182
|
return path.join(BINARY_CACHE_DIR, name);
|
|
183
183
|
}
|
|
184
184
|
|
|
185
|
+
/**
|
|
186
|
+
* Decide whether the cached native binary must be (re)downloaded: true when it
|
|
187
|
+
* is missing OR its actual version differs from the latest release. Version-aware
|
|
188
|
+
* rather than existence-only — a stale-but-present binary must still self-heal
|
|
189
|
+
* even when the plugin shell version already matches latest. manifest.version
|
|
190
|
+
* tracks the plugin shell (the marketplace bumps it independently of the native
|
|
191
|
+
* binary), so an existence-only check leaves the engine permanently pinned to an
|
|
192
|
+
* old binary while the updater reports "up to date".
|
|
193
|
+
*/
|
|
194
|
+
function cachedBinaryNeedsUpdate(latest, { binaryPath = cachedBinaryPath(), readVersion = readBinaryVersion } = {}) {
|
|
195
|
+
if (!latest || !latest.binaryUrl) return false;
|
|
196
|
+
if (!fs.existsSync(binaryPath)) return true;
|
|
197
|
+
return readVersion(binaryPath) !== latest.version;
|
|
198
|
+
}
|
|
199
|
+
|
|
185
200
|
/**
|
|
186
201
|
* Download just the platform binary from a GitHub release into the cache.
|
|
187
202
|
* Used in two paths:
|
|
@@ -222,15 +237,21 @@ function promoteVerifiedBinary(binaryTmp, binaryDst, expectedVersion) {
|
|
|
222
237
|
const stat = fs.statSync(binaryTmp);
|
|
223
238
|
if (stat.size <= 1_000_000) return false;
|
|
224
239
|
|
|
240
|
+
// chmod BEFORE reading the version. readBinaryVersion executes the binary
|
|
241
|
+
// (`--version`), which requires the exec bit; `curl -o` writes the tmp file
|
|
242
|
+
// as 0644 (no exec bit), so reading the version first fails with EACCES →
|
|
243
|
+
// null → false, which silently wedged every download path. rename preserves
|
|
244
|
+
// the mode, so the promoted dst ends up 0755.
|
|
245
|
+
if (os.platform() !== 'win32') {
|
|
246
|
+
fs.chmodSync(binaryTmp, 0o755);
|
|
247
|
+
}
|
|
248
|
+
|
|
225
249
|
const actualVersion = readBinaryVersion(binaryTmp);
|
|
226
250
|
if (!actualVersion || (expectedVersion && actualVersion !== expectedVersion)) {
|
|
227
251
|
return false;
|
|
228
252
|
}
|
|
229
253
|
|
|
230
254
|
fs.renameSync(binaryTmp, binaryDst);
|
|
231
|
-
if (os.platform() !== 'win32') {
|
|
232
|
-
fs.chmodSync(binaryDst, 0o755);
|
|
233
|
-
}
|
|
234
255
|
clearBinaryCache();
|
|
235
256
|
return true;
|
|
236
257
|
} catch {
|
|
@@ -392,12 +413,13 @@ async function checkForUpdate({ installMissing = false } = {}) {
|
|
|
392
413
|
};
|
|
393
414
|
}
|
|
394
415
|
|
|
395
|
-
// No update
|
|
396
|
-
//
|
|
397
|
-
//
|
|
398
|
-
//
|
|
416
|
+
// No plugin-shell update — but self-heal the native binary if it is missing
|
|
417
|
+
// OR stale. The shell version (manifest.version) can match latest while the
|
|
418
|
+
// cached binary lags (a previous download failed silently, the cache was
|
|
419
|
+
// wiped, an `npm install -g` optionalDependency dropped the platform package,
|
|
420
|
+
// or the marketplace bumped the shell without re-fetching the binary).
|
|
399
421
|
let selfHealedBinary = false;
|
|
400
|
-
if (latest
|
|
422
|
+
if (cachedBinaryNeedsUpdate(latest)) {
|
|
401
423
|
selfHealedBinary = await downloadBinary(latest);
|
|
402
424
|
}
|
|
403
425
|
|
|
@@ -424,7 +446,7 @@ module.exports = {
|
|
|
424
446
|
getExtractedPluginVersion, readBinaryVersion, promoteVerifiedBinary,
|
|
425
447
|
isSilentMode, isInstallMissingMode,
|
|
426
448
|
requestJson, parseLatestRelease, fetchLatestRelease,
|
|
427
|
-
downloadBinary, cachedBinaryPath,
|
|
449
|
+
downloadBinary, cachedBinaryPath, cachedBinaryNeedsUpdate,
|
|
428
450
|
};
|
|
429
451
|
|
|
430
452
|
// CLI: node auto-update.js [check|status] [--silent] [--install-missing]
|
|
@@ -13,6 +13,7 @@ const {
|
|
|
13
13
|
readBinaryVersion,
|
|
14
14
|
promoteVerifiedBinary,
|
|
15
15
|
cachedBinaryPath,
|
|
16
|
+
cachedBinaryNeedsUpdate,
|
|
16
17
|
downloadBinary,
|
|
17
18
|
isInstallMissingMode,
|
|
18
19
|
isSilentMode,
|
|
@@ -32,7 +33,7 @@ test('getExtractedPluginVersion reads extracted plugin manifest version', (t) =>
|
|
|
32
33
|
assert.equal(getExtractedPluginVersion(root), '1.2.3');
|
|
33
34
|
});
|
|
34
35
|
|
|
35
|
-
function writeFakeBinary(filePath, version) {
|
|
36
|
+
function writeFakeBinary(filePath, version, mode = 0o755) {
|
|
36
37
|
const script = [
|
|
37
38
|
'#!/usr/bin/env bash',
|
|
38
39
|
'if [ "$1" = "--version" ]; then',
|
|
@@ -44,7 +45,7 @@ function writeFakeBinary(filePath, version) {
|
|
|
44
45
|
'',
|
|
45
46
|
].join('\n');
|
|
46
47
|
fs.writeFileSync(filePath, script);
|
|
47
|
-
fs.chmodSync(filePath,
|
|
48
|
+
fs.chmodSync(filePath, mode);
|
|
48
49
|
}
|
|
49
50
|
|
|
50
51
|
test('promoteVerifiedBinary accepts a runnable binary with the expected version', (t) => {
|
|
@@ -70,6 +71,51 @@ test('promoteVerifiedBinary rejects binaries with mismatched version', (t) => {
|
|
|
70
71
|
assert.equal(fs.existsSync(dst), false);
|
|
71
72
|
});
|
|
72
73
|
|
|
74
|
+
test('promoteVerifiedBinary promotes a non-executable (0644) download — curl -o regression', (t) => {
|
|
75
|
+
// `curl -o` writes the tmp file as 0644 (no exec bit). promoteVerifiedBinary
|
|
76
|
+
// must chmod before reading the version (readBinaryVersion executes the
|
|
77
|
+
// binary), otherwise the version read fails with EACCES → null → false and
|
|
78
|
+
// every download path silently wedges. Regression for the binary-stuck-at-old
|
|
79
|
+
// -version deadlock.
|
|
80
|
+
if (process.platform === 'win32') return; // no exec bit on win32
|
|
81
|
+
const dir = mkDir(t, 'code-graph-bin-');
|
|
82
|
+
const tmp = path.join(dir, 'code-graph-mcp.tmp');
|
|
83
|
+
const dst = path.join(dir, 'code-graph-mcp');
|
|
84
|
+
writeFakeBinary(tmp, '1.2.3', 0o644);
|
|
85
|
+
|
|
86
|
+
assert.equal(readBinaryVersion(tmp), null, 'precondition: 0644 binary is not executable');
|
|
87
|
+
assert.equal(promoteVerifiedBinary(tmp, dst, '1.2.3'), true);
|
|
88
|
+
assert.equal(fs.existsSync(dst), true);
|
|
89
|
+
assert.equal(fs.statSync(dst).mode & 0o111, 0o111, 'promoted binary is executable');
|
|
90
|
+
assert.equal(readBinaryVersion(dst), '1.2.3');
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test('cachedBinaryNeedsUpdate is version-aware, not existence-only', (t) => {
|
|
94
|
+
const dir = mkDir(t, 'code-graph-heal-');
|
|
95
|
+
const binaryPath = path.join(dir, 'code-graph-mcp');
|
|
96
|
+
const latest = { version: '0.45.0', binaryUrl: 'https://example.com/bin' };
|
|
97
|
+
|
|
98
|
+
// missing binary → needs update
|
|
99
|
+
assert.equal(cachedBinaryNeedsUpdate(latest, { binaryPath }), true);
|
|
100
|
+
|
|
101
|
+
// present but stale (the actual deadlock: shell at 0.45.0, binary at 0.16.6)
|
|
102
|
+
fs.writeFileSync(binaryPath, 'x');
|
|
103
|
+
assert.equal(
|
|
104
|
+
cachedBinaryNeedsUpdate(latest, { binaryPath, readVersion: () => '0.16.6' }),
|
|
105
|
+
true,
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
// present and current → no update
|
|
109
|
+
assert.equal(
|
|
110
|
+
cachedBinaryNeedsUpdate(latest, { binaryPath, readVersion: () => '0.45.0' }),
|
|
111
|
+
false,
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
// no binaryUrl / null latest → no-op (nothing to download)
|
|
115
|
+
assert.equal(cachedBinaryNeedsUpdate({ version: '0.45.0', binaryUrl: null }, { binaryPath }), false);
|
|
116
|
+
assert.equal(cachedBinaryNeedsUpdate(null, { binaryPath }), false);
|
|
117
|
+
});
|
|
118
|
+
|
|
73
119
|
test('parseLatestRelease selects the matching platform asset', () => {
|
|
74
120
|
const latest = parseLatestRelease({
|
|
75
121
|
tag_name: 'v1.2.3',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sdsrs/code-graph",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.45.1",
|
|
4
4
|
"description": "MCP server that indexes codebases into an AST knowledge graph with semantic search, call graph traversal, and HTTP route tracing",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -35,10 +35,10 @@
|
|
|
35
35
|
"node": ">=16"
|
|
36
36
|
},
|
|
37
37
|
"optionalDependencies": {
|
|
38
|
-
"@sdsrs/code-graph-linux-x64": "0.
|
|
39
|
-
"@sdsrs/code-graph-linux-arm64": "0.
|
|
40
|
-
"@sdsrs/code-graph-darwin-x64": "0.
|
|
41
|
-
"@sdsrs/code-graph-darwin-arm64": "0.
|
|
42
|
-
"@sdsrs/code-graph-win32-x64": "0.
|
|
38
|
+
"@sdsrs/code-graph-linux-x64": "0.45.1",
|
|
39
|
+
"@sdsrs/code-graph-linux-arm64": "0.45.1",
|
|
40
|
+
"@sdsrs/code-graph-darwin-x64": "0.45.1",
|
|
41
|
+
"@sdsrs/code-graph-darwin-arm64": "0.45.1",
|
|
42
|
+
"@sdsrs/code-graph-win32-x64": "0.45.1"
|
|
43
43
|
}
|
|
44
44
|
}
|