@sdsrs/code-graph 0.45.0 → 0.45.2
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,35 @@ 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
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Throttle-bypass predicate: is a *present* cached binary stale relative to the
|
|
202
|
+
* last known latest release (`state.latestVersion`, set on the previous fetch —
|
|
203
|
+
* no network here)? Used so a present-but-stale binary skips the time-based
|
|
204
|
+
* throttle instead of staying pinned for up to a full check interval. Returns
|
|
205
|
+
* false when there is no prior latestVersion (first run fetches anyway) or the
|
|
206
|
+
* binary is missing (handled by the separate `binaryMissing` bypass).
|
|
207
|
+
*/
|
|
208
|
+
function cachedBinaryStaleVsState(state, { binaryPath = cachedBinaryPath(), readVersion = readBinaryVersion } = {}) {
|
|
209
|
+
if (!state || !state.latestVersion) return false;
|
|
210
|
+
if (!fs.existsSync(binaryPath)) return false;
|
|
211
|
+
return readVersion(binaryPath) !== state.latestVersion;
|
|
212
|
+
}
|
|
213
|
+
|
|
185
214
|
/**
|
|
186
215
|
* Download just the platform binary from a GitHub release into the cache.
|
|
187
216
|
* Used in two paths:
|
|
@@ -222,15 +251,21 @@ function promoteVerifiedBinary(binaryTmp, binaryDst, expectedVersion) {
|
|
|
222
251
|
const stat = fs.statSync(binaryTmp);
|
|
223
252
|
if (stat.size <= 1_000_000) return false;
|
|
224
253
|
|
|
254
|
+
// chmod BEFORE reading the version. readBinaryVersion executes the binary
|
|
255
|
+
// (`--version`), which requires the exec bit; `curl -o` writes the tmp file
|
|
256
|
+
// as 0644 (no exec bit), so reading the version first fails with EACCES →
|
|
257
|
+
// null → false, which silently wedged every download path. rename preserves
|
|
258
|
+
// the mode, so the promoted dst ends up 0755.
|
|
259
|
+
if (os.platform() !== 'win32') {
|
|
260
|
+
fs.chmodSync(binaryTmp, 0o755);
|
|
261
|
+
}
|
|
262
|
+
|
|
225
263
|
const actualVersion = readBinaryVersion(binaryTmp);
|
|
226
264
|
if (!actualVersion || (expectedVersion && actualVersion !== expectedVersion)) {
|
|
227
265
|
return false;
|
|
228
266
|
}
|
|
229
267
|
|
|
230
268
|
fs.renameSync(binaryTmp, binaryDst);
|
|
231
|
-
if (os.platform() !== 'win32') {
|
|
232
|
-
fs.chmodSync(binaryDst, 0o755);
|
|
233
|
-
}
|
|
234
269
|
clearBinaryCache();
|
|
235
270
|
return true;
|
|
236
271
|
} catch {
|
|
@@ -344,11 +379,14 @@ async function checkForUpdate({ installMissing = false } = {}) {
|
|
|
344
379
|
// bypasses auto-update.js, so re-sync state.installedVersion every call.
|
|
345
380
|
const installedVersion = readManifest().version || '0.0.0';
|
|
346
381
|
|
|
347
|
-
// Time-based throttle.
|
|
348
|
-
// cannot start)
|
|
349
|
-
//
|
|
382
|
+
// Time-based throttle. Two conditions override it: a missing cache binary
|
|
383
|
+
// (launcher cannot start) and a present-but-stale binary (otherwise it stays
|
|
384
|
+
// pinned to the old version for up to a full check interval — the binary
|
|
385
|
+
// self-heal would never run inside the throttle window). Both bypass to the
|
|
386
|
+
// fetch + self-heal path below.
|
|
350
387
|
const binaryMissing = !fs.existsSync(cachedBinaryPath());
|
|
351
|
-
|
|
388
|
+
const binaryStale = cachedBinaryStaleVsState(state);
|
|
389
|
+
if (!binaryMissing && !binaryStale && !shouldCheck(state)) {
|
|
352
390
|
if (state.installedVersion !== installedVersion) {
|
|
353
391
|
saveState({ ...state, installedVersion });
|
|
354
392
|
}
|
|
@@ -392,12 +430,13 @@ async function checkForUpdate({ installMissing = false } = {}) {
|
|
|
392
430
|
};
|
|
393
431
|
}
|
|
394
432
|
|
|
395
|
-
// No update
|
|
396
|
-
//
|
|
397
|
-
//
|
|
398
|
-
//
|
|
433
|
+
// No plugin-shell update — but self-heal the native binary if it is missing
|
|
434
|
+
// OR stale. The shell version (manifest.version) can match latest while the
|
|
435
|
+
// cached binary lags (a previous download failed silently, the cache was
|
|
436
|
+
// wiped, an `npm install -g` optionalDependency dropped the platform package,
|
|
437
|
+
// or the marketplace bumped the shell without re-fetching the binary).
|
|
399
438
|
let selfHealedBinary = false;
|
|
400
|
-
if (latest
|
|
439
|
+
if (cachedBinaryNeedsUpdate(latest)) {
|
|
401
440
|
selfHealedBinary = await downloadBinary(latest);
|
|
402
441
|
}
|
|
403
442
|
|
|
@@ -424,7 +463,7 @@ module.exports = {
|
|
|
424
463
|
getExtractedPluginVersion, readBinaryVersion, promoteVerifiedBinary,
|
|
425
464
|
isSilentMode, isInstallMissingMode,
|
|
426
465
|
requestJson, parseLatestRelease, fetchLatestRelease,
|
|
427
|
-
downloadBinary, cachedBinaryPath,
|
|
466
|
+
downloadBinary, cachedBinaryPath, cachedBinaryNeedsUpdate, cachedBinaryStaleVsState,
|
|
428
467
|
};
|
|
429
468
|
|
|
430
469
|
// CLI: node auto-update.js [check|status] [--silent] [--install-missing]
|
|
@@ -13,6 +13,8 @@ const {
|
|
|
13
13
|
readBinaryVersion,
|
|
14
14
|
promoteVerifiedBinary,
|
|
15
15
|
cachedBinaryPath,
|
|
16
|
+
cachedBinaryNeedsUpdate,
|
|
17
|
+
cachedBinaryStaleVsState,
|
|
16
18
|
downloadBinary,
|
|
17
19
|
isInstallMissingMode,
|
|
18
20
|
isSilentMode,
|
|
@@ -32,7 +34,7 @@ test('getExtractedPluginVersion reads extracted plugin manifest version', (t) =>
|
|
|
32
34
|
assert.equal(getExtractedPluginVersion(root), '1.2.3');
|
|
33
35
|
});
|
|
34
36
|
|
|
35
|
-
function writeFakeBinary(filePath, version) {
|
|
37
|
+
function writeFakeBinary(filePath, version, mode = 0o755) {
|
|
36
38
|
const script = [
|
|
37
39
|
'#!/usr/bin/env bash',
|
|
38
40
|
'if [ "$1" = "--version" ]; then',
|
|
@@ -44,7 +46,7 @@ function writeFakeBinary(filePath, version) {
|
|
|
44
46
|
'',
|
|
45
47
|
].join('\n');
|
|
46
48
|
fs.writeFileSync(filePath, script);
|
|
47
|
-
fs.chmodSync(filePath,
|
|
49
|
+
fs.chmodSync(filePath, mode);
|
|
48
50
|
}
|
|
49
51
|
|
|
50
52
|
test('promoteVerifiedBinary accepts a runnable binary with the expected version', (t) => {
|
|
@@ -70,6 +72,77 @@ test('promoteVerifiedBinary rejects binaries with mismatched version', (t) => {
|
|
|
70
72
|
assert.equal(fs.existsSync(dst), false);
|
|
71
73
|
});
|
|
72
74
|
|
|
75
|
+
test('promoteVerifiedBinary promotes a non-executable (0644) download — curl -o regression', (t) => {
|
|
76
|
+
// `curl -o` writes the tmp file as 0644 (no exec bit). promoteVerifiedBinary
|
|
77
|
+
// must chmod before reading the version (readBinaryVersion executes the
|
|
78
|
+
// binary), otherwise the version read fails with EACCES → null → false and
|
|
79
|
+
// every download path silently wedges. Regression for the binary-stuck-at-old
|
|
80
|
+
// -version deadlock.
|
|
81
|
+
if (process.platform === 'win32') return; // no exec bit on win32
|
|
82
|
+
const dir = mkDir(t, 'code-graph-bin-');
|
|
83
|
+
const tmp = path.join(dir, 'code-graph-mcp.tmp');
|
|
84
|
+
const dst = path.join(dir, 'code-graph-mcp');
|
|
85
|
+
writeFakeBinary(tmp, '1.2.3', 0o644);
|
|
86
|
+
|
|
87
|
+
assert.equal(readBinaryVersion(tmp), null, 'precondition: 0644 binary is not executable');
|
|
88
|
+
assert.equal(promoteVerifiedBinary(tmp, dst, '1.2.3'), true);
|
|
89
|
+
assert.equal(fs.existsSync(dst), true);
|
|
90
|
+
assert.equal(fs.statSync(dst).mode & 0o111, 0o111, 'promoted binary is executable');
|
|
91
|
+
assert.equal(readBinaryVersion(dst), '1.2.3');
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test('cachedBinaryNeedsUpdate is version-aware, not existence-only', (t) => {
|
|
95
|
+
const dir = mkDir(t, 'code-graph-heal-');
|
|
96
|
+
const binaryPath = path.join(dir, 'code-graph-mcp');
|
|
97
|
+
const latest = { version: '0.45.0', binaryUrl: 'https://example.com/bin' };
|
|
98
|
+
|
|
99
|
+
// missing binary → needs update
|
|
100
|
+
assert.equal(cachedBinaryNeedsUpdate(latest, { binaryPath }), true);
|
|
101
|
+
|
|
102
|
+
// present but stale (the actual deadlock: shell at 0.45.0, binary at 0.16.6)
|
|
103
|
+
fs.writeFileSync(binaryPath, 'x');
|
|
104
|
+
assert.equal(
|
|
105
|
+
cachedBinaryNeedsUpdate(latest, { binaryPath, readVersion: () => '0.16.6' }),
|
|
106
|
+
true,
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
// present and current → no update
|
|
110
|
+
assert.equal(
|
|
111
|
+
cachedBinaryNeedsUpdate(latest, { binaryPath, readVersion: () => '0.45.0' }),
|
|
112
|
+
false,
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
// no binaryUrl / null latest → no-op (nothing to download)
|
|
116
|
+
assert.equal(cachedBinaryNeedsUpdate({ version: '0.45.0', binaryUrl: null }, { binaryPath }), false);
|
|
117
|
+
assert.equal(cachedBinaryNeedsUpdate(null, { binaryPath }), false);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test('cachedBinaryStaleVsState bypasses throttle only for a present-but-stale binary', (t) => {
|
|
121
|
+
const dir = mkDir(t, 'code-graph-throttle-');
|
|
122
|
+
const binaryPath = path.join(dir, 'code-graph-mcp');
|
|
123
|
+
fs.writeFileSync(binaryPath, 'x'); // present
|
|
124
|
+
|
|
125
|
+
// no prior latestVersion → don't bypass (first run fetches anyway)
|
|
126
|
+
assert.equal(cachedBinaryStaleVsState({}, { binaryPath }), false);
|
|
127
|
+
assert.equal(cachedBinaryStaleVsState(null, { binaryPath }), false);
|
|
128
|
+
|
|
129
|
+
// present + stale vs last known latest → bypass throttle (the 6h-gap fix)
|
|
130
|
+
assert.equal(
|
|
131
|
+
cachedBinaryStaleVsState({ latestVersion: '0.45.1' }, { binaryPath, readVersion: () => '0.16.6' }),
|
|
132
|
+
true,
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
// present + current → stay throttled
|
|
136
|
+
assert.equal(
|
|
137
|
+
cachedBinaryStaleVsState({ latestVersion: '0.45.1' }, { binaryPath, readVersion: () => '0.45.1' }),
|
|
138
|
+
false,
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
// missing binary → false here (the separate binaryMissing bypass handles it)
|
|
142
|
+
fs.rmSync(binaryPath);
|
|
143
|
+
assert.equal(cachedBinaryStaleVsState({ latestVersion: '0.45.1' }, { binaryPath }), false);
|
|
144
|
+
});
|
|
145
|
+
|
|
73
146
|
test('parseLatestRelease selects the matching platform asset', () => {
|
|
74
147
|
const latest = parseLatestRelease({
|
|
75
148
|
tag_name: 'v1.2.3',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sdsrs/code-graph",
|
|
3
|
-
"version": "0.45.
|
|
3
|
+
"version": "0.45.2",
|
|
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.45.
|
|
39
|
-
"@sdsrs/code-graph-linux-arm64": "0.45.
|
|
40
|
-
"@sdsrs/code-graph-darwin-x64": "0.45.
|
|
41
|
-
"@sdsrs/code-graph-darwin-arm64": "0.45.
|
|
42
|
-
"@sdsrs/code-graph-win32-x64": "0.45.
|
|
38
|
+
"@sdsrs/code-graph-linux-x64": "0.45.2",
|
|
39
|
+
"@sdsrs/code-graph-linux-arm64": "0.45.2",
|
|
40
|
+
"@sdsrs/code-graph-darwin-x64": "0.45.2",
|
|
41
|
+
"@sdsrs/code-graph-darwin-arm64": "0.45.2",
|
|
42
|
+
"@sdsrs/code-graph-win32-x64": "0.45.2"
|
|
43
43
|
}
|
|
44
44
|
}
|