@sdsrs/code-graph 0.16.6 → 0.16.7
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/claude-plugin/.claude-plugin/plugin.json +1 -1
- package/claude-plugin/scripts/auto-update.js +63 -23
- package/claude-plugin/scripts/auto-update.test.js +20 -0
- package/claude-plugin/scripts/find-binary.js +80 -7
- package/claude-plugin/scripts/find-binary.test.js +110 -0
- package/claude-plugin/scripts/mcp-launcher.js +20 -1
- package/package.json +6 -6
|
@@ -172,6 +172,46 @@ function getExtractedPluginVersion(pluginSrc) {
|
|
|
172
172
|
return manifest && typeof manifest.version === 'string' ? manifest.version : null;
|
|
173
173
|
}
|
|
174
174
|
|
|
175
|
+
function cachedBinaryPath() {
|
|
176
|
+
const name = os.platform() === 'win32' ? 'code-graph-mcp.exe' : 'code-graph-mcp';
|
|
177
|
+
return path.join(BINARY_CACHE_DIR, name);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Download just the platform binary from a GitHub release into the cache.
|
|
182
|
+
* Used in two paths:
|
|
183
|
+
* 1. As part of `downloadAndInstall` after a plugin tarball update.
|
|
184
|
+
* 2. As a standalone self-heal when the cached binary is missing but the
|
|
185
|
+
* installed plugin version already matches `latest` (e.g. previous
|
|
186
|
+
* download failed silently, cache was wiped, optionalDependency
|
|
187
|
+
* install dropped the platform package).
|
|
188
|
+
*
|
|
189
|
+
* Returns true on successful promote, false otherwise. Never throws.
|
|
190
|
+
*/
|
|
191
|
+
async function downloadBinary(latest) {
|
|
192
|
+
if (!latest || !latest.binaryUrl) return false;
|
|
193
|
+
if (!commandExists('curl')) {
|
|
194
|
+
console.error('[code-graph] Binary download skipped: curl not on PATH.');
|
|
195
|
+
return false;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const binaryDst = cachedBinaryPath();
|
|
199
|
+
const binaryTmp = binaryDst + '.tmp.' + process.pid;
|
|
200
|
+
|
|
201
|
+
try {
|
|
202
|
+
fs.mkdirSync(BINARY_CACHE_DIR, { recursive: true });
|
|
203
|
+
execFileSync('curl', [
|
|
204
|
+
'-sL', '-o', binaryTmp,
|
|
205
|
+
latest.binaryUrl,
|
|
206
|
+
], { timeout: 60000, stdio: 'pipe' });
|
|
207
|
+
|
|
208
|
+
return promoteVerifiedBinary(binaryTmp, binaryDst, latest.version);
|
|
209
|
+
} catch (e) {
|
|
210
|
+
console.error(`[code-graph] Binary download failed: ${e.message}`);
|
|
211
|
+
return false;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
175
215
|
function promoteVerifiedBinary(binaryTmp, binaryDst, expectedVersion) {
|
|
176
216
|
try {
|
|
177
217
|
const stat = fs.statSync(binaryTmp);
|
|
@@ -272,25 +312,8 @@ async function downloadAndInstall(latest) {
|
|
|
272
312
|
}
|
|
273
313
|
|
|
274
314
|
// ── Step 2: Download platform binary directly from GitHub release ──
|
|
275
|
-
if (latest
|
|
276
|
-
|
|
277
|
-
const binaryName = os.platform() === 'win32' ? 'code-graph-mcp.exe' : 'code-graph-mcp';
|
|
278
|
-
const binaryDst = path.join(BINARY_CACHE_DIR, binaryName);
|
|
279
|
-
const binaryTmp = binaryDst + '.tmp.' + process.pid;
|
|
280
|
-
|
|
281
|
-
fs.mkdirSync(BINARY_CACHE_DIR, { recursive: true });
|
|
282
|
-
execFileSync('curl', [
|
|
283
|
-
'-sL', '-o', binaryTmp,
|
|
284
|
-
latest.binaryUrl,
|
|
285
|
-
], { timeout: 60000, stdio: 'pipe' });
|
|
286
|
-
|
|
287
|
-
if (promoteVerifiedBinary(binaryTmp, binaryDst, latest.version)) {
|
|
288
|
-
binaryUpdated = true;
|
|
289
|
-
}
|
|
290
|
-
} catch (e) {
|
|
291
|
-
// Binary download failed — plugin update still counts as success
|
|
292
|
-
console.error(`[code-graph] Binary download failed: ${e.message}`);
|
|
293
|
-
}
|
|
315
|
+
if (await downloadBinary(latest)) {
|
|
316
|
+
binaryUpdated = true;
|
|
294
317
|
}
|
|
295
318
|
|
|
296
319
|
return { pluginUpdated, binaryUpdated };
|
|
@@ -314,8 +337,11 @@ async function checkForUpdate() {
|
|
|
314
337
|
// bypasses auto-update.js, so re-sync state.installedVersion every call.
|
|
315
338
|
const installedVersion = readManifest().version || '0.0.0';
|
|
316
339
|
|
|
317
|
-
// Time-based throttle
|
|
318
|
-
|
|
340
|
+
// Time-based throttle. A missing cache binary is a hard failure (launcher
|
|
341
|
+
// cannot start) so it overrides the throttle — without this bypass the
|
|
342
|
+
// session wedges for up to 6h waiting for the next check window.
|
|
343
|
+
const binaryMissing = !fs.existsSync(cachedBinaryPath());
|
|
344
|
+
if (!binaryMissing && !shouldCheck(state)) {
|
|
319
345
|
if (state.installedVersion !== installedVersion) {
|
|
320
346
|
saveState({ ...state, installedVersion });
|
|
321
347
|
}
|
|
@@ -359,7 +385,15 @@ async function checkForUpdate() {
|
|
|
359
385
|
};
|
|
360
386
|
}
|
|
361
387
|
|
|
362
|
-
// No update needed
|
|
388
|
+
// No update needed — but self-heal if cache binary is missing.
|
|
389
|
+
// State file alone is not authoritative; previous download may have failed
|
|
390
|
+
// silently, cache may have been wiped, or `npm install -g` optionalDependency
|
|
391
|
+
// may have dropped the platform package.
|
|
392
|
+
let selfHealedBinary = false;
|
|
393
|
+
if (latest.binaryUrl && !fs.existsSync(cachedBinaryPath())) {
|
|
394
|
+
selfHealedBinary = await downloadBinary(latest);
|
|
395
|
+
}
|
|
396
|
+
|
|
363
397
|
saveState({
|
|
364
398
|
...state,
|
|
365
399
|
installedVersion,
|
|
@@ -367,8 +401,11 @@ async function checkForUpdate() {
|
|
|
367
401
|
latestVersion: latest.version,
|
|
368
402
|
updateAvailable: false,
|
|
369
403
|
rateLimited: false,
|
|
404
|
+
binaryUpdated: selfHealedBinary || state.binaryUpdated,
|
|
370
405
|
});
|
|
371
|
-
return
|
|
406
|
+
return selfHealedBinary
|
|
407
|
+
? { updated: false, binaryUpdated: true, from: installedVersion, to: installedVersion }
|
|
408
|
+
: null;
|
|
372
409
|
} catch {
|
|
373
410
|
// Silent failure — never block session
|
|
374
411
|
return null;
|
|
@@ -379,6 +416,7 @@ module.exports = {
|
|
|
379
416
|
checkForUpdate, commandExists, isDevMode, readState, compareVersions,
|
|
380
417
|
getExtractedPluginVersion, readBinaryVersion, promoteVerifiedBinary, isSilentMode,
|
|
381
418
|
requestJson, parseLatestRelease, fetchLatestRelease,
|
|
419
|
+
downloadBinary, cachedBinaryPath,
|
|
382
420
|
};
|
|
383
421
|
|
|
384
422
|
// CLI: node auto-update.js [check|status]
|
|
@@ -398,6 +436,8 @@ if (require.main === module) {
|
|
|
398
436
|
console.log(`Updated: v${result.from} → v${result.to} (binary: ${result.binaryUpdated ? 'yes' : 'no'})`);
|
|
399
437
|
} else if (result && result.updateAvailable) {
|
|
400
438
|
console.log(`Update available: v${result.to} (auto-install failed)`);
|
|
439
|
+
} else if (result && result.binaryUpdated) {
|
|
440
|
+
console.log(`Repaired binary cache (v${result.to})`);
|
|
401
441
|
} else if (isDevMode()) {
|
|
402
442
|
console.log('Dev mode — auto-update skipped');
|
|
403
443
|
} else {
|
|
@@ -12,6 +12,8 @@ const {
|
|
|
12
12
|
parseLatestRelease,
|
|
13
13
|
readBinaryVersion,
|
|
14
14
|
promoteVerifiedBinary,
|
|
15
|
+
cachedBinaryPath,
|
|
16
|
+
downloadBinary,
|
|
15
17
|
} = require('./auto-update');
|
|
16
18
|
|
|
17
19
|
function mkDir(t, prefix) {
|
|
@@ -93,6 +95,24 @@ test('commandExists returns false for a non-existent command', () => {
|
|
|
93
95
|
assert.equal(commandExists('__nonexistent_cmd_xyz_12345__'), false);
|
|
94
96
|
});
|
|
95
97
|
|
|
98
|
+
test('cachedBinaryPath returns expected platform binary path', () => {
|
|
99
|
+
const p = cachedBinaryPath();
|
|
100
|
+
const expectedName = process.platform === 'win32' ? 'code-graph-mcp.exe' : 'code-graph-mcp';
|
|
101
|
+
assert.equal(path.basename(p), expectedName);
|
|
102
|
+
assert.ok(p.includes('.cache') && p.includes('code-graph'),
|
|
103
|
+
`expected cache path to live under ~/.cache/code-graph: ${p}`);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test('downloadBinary returns false for missing binaryUrl (no-op safety)', async () => {
|
|
107
|
+
const result = await downloadBinary({ version: '1.0.0', binaryUrl: null });
|
|
108
|
+
assert.equal(result, false);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test('downloadBinary returns false when latest is null', async () => {
|
|
112
|
+
const result = await downloadBinary(null);
|
|
113
|
+
assert.equal(result, false);
|
|
114
|
+
});
|
|
115
|
+
|
|
96
116
|
test('fetchLatestRelease parses JSON without relying on global fetch', async () => {
|
|
97
117
|
const latest = await fetchLatestRelease(async () => ({
|
|
98
118
|
statusCode: 200,
|
|
@@ -9,6 +9,53 @@ const PLATFORM = os.platform();
|
|
|
9
9
|
const ARCH = os.arch();
|
|
10
10
|
const CACHE_FILE = path.join(os.homedir(), '.cache', 'code-graph', 'binary-path');
|
|
11
11
|
const BINARY_NAME = PLATFORM === 'win32' ? 'code-graph-mcp.exe' : 'code-graph-mcp';
|
|
12
|
+
const PLATFORM_PKG = `@sdsrs/code-graph-${PLATFORM}-${ARCH}`;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Candidate paths for npm global `node_modules`.
|
|
16
|
+
*
|
|
17
|
+
* `require.resolve` only searches `node_modules` walking up from the requiring
|
|
18
|
+
* file — it does NOT search global installations (no NODE_PATH set in default
|
|
19
|
+
* Node setups, including nvm). When a user runs `npm install -g`, the platform
|
|
20
|
+
* package lands somewhere we have to discover ourselves.
|
|
21
|
+
*/
|
|
22
|
+
function globalNodeModulesCandidates() {
|
|
23
|
+
const out = [];
|
|
24
|
+
const nodeBinDir = path.dirname(process.execPath);
|
|
25
|
+
|
|
26
|
+
// 1. Derive from process.execPath. Works for nvm + standard Unix prefixes
|
|
27
|
+
// (`<prefix>/bin/node` → globals at `<prefix>/lib/node_modules`); on
|
|
28
|
+
// Windows globals sit next to `node.exe`.
|
|
29
|
+
if (PLATFORM === 'win32') {
|
|
30
|
+
out.push(path.join(nodeBinDir, 'node_modules'));
|
|
31
|
+
} else {
|
|
32
|
+
out.push(path.resolve(nodeBinDir, '..', 'lib', 'node_modules'));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// 2. NPM_CONFIG_PREFIX env override (set by users using `~/.npm-global` etc.)
|
|
36
|
+
const envPrefix = process.env.NPM_CONFIG_PREFIX || process.env.npm_config_prefix;
|
|
37
|
+
if (envPrefix) {
|
|
38
|
+
out.push(PLATFORM === 'win32'
|
|
39
|
+
? path.join(envPrefix, 'node_modules')
|
|
40
|
+
: path.join(envPrefix, 'lib', 'node_modules'));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// 3. Common no-sudo user prefix
|
|
44
|
+
out.push(path.join(os.homedir(), '.npm-global', 'lib', 'node_modules'));
|
|
45
|
+
|
|
46
|
+
// 4. Last resort: ask npm directly. Slow (~50-200ms) but most accurate when
|
|
47
|
+
// user has a non-standard prefix. Cached at the disk-cache layer above.
|
|
48
|
+
try {
|
|
49
|
+
const root = execFileSync('npm', ['root', '-g'], {
|
|
50
|
+
timeout: 2000,
|
|
51
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
52
|
+
encoding: 'utf8',
|
|
53
|
+
}).trim();
|
|
54
|
+
if (root) out.push(root);
|
|
55
|
+
} catch { /* npm not on PATH or timed out */ }
|
|
56
|
+
|
|
57
|
+
return [...new Set(out)];
|
|
58
|
+
}
|
|
12
59
|
|
|
13
60
|
function isNativeBinary(candidate) {
|
|
14
61
|
if (!candidate) return false;
|
|
@@ -60,6 +107,32 @@ function isDevRepo(rootDir) {
|
|
|
60
107
|
return fs.existsSync(path.join(rootDir, 'Cargo.toml'));
|
|
61
108
|
}
|
|
62
109
|
|
|
110
|
+
/**
|
|
111
|
+
* Locate the platform-specific binary in npm package layouts.
|
|
112
|
+
* - First via `require.resolve` (parent-walk node_modules / linked / npx).
|
|
113
|
+
* - Then by explicit probe of npm global `node_modules` candidates.
|
|
114
|
+
*
|
|
115
|
+
* `require.resolve` does NOT search global installs (no NODE_PATH on
|
|
116
|
+
* nvm/standard setups), so a working `npm install -g @sdsrs/code-graph` can
|
|
117
|
+
* still be invisible without the fallback.
|
|
118
|
+
*/
|
|
119
|
+
function findPlatformBinary() {
|
|
120
|
+
// Fast path: standard module resolution.
|
|
121
|
+
try {
|
|
122
|
+
const pkgPath = require.resolve(`${PLATFORM_PKG}/package.json`);
|
|
123
|
+
const bin = path.join(path.dirname(pkgPath), BINARY_NAME);
|
|
124
|
+
if (isNativeBinary(bin)) return bin;
|
|
125
|
+
} catch { /* not in node_modules walk-up */ }
|
|
126
|
+
|
|
127
|
+
// Slow path: explicit global node_modules probe.
|
|
128
|
+
for (const globalRoot of globalNodeModulesCandidates()) {
|
|
129
|
+
const bin = path.join(globalRoot, '@sdsrs', `code-graph-${PLATFORM}-${ARCH}`, BINARY_NAME);
|
|
130
|
+
if (isNativeBinary(bin)) return bin;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
|
|
63
136
|
function findBinaryUncached() {
|
|
64
137
|
// --- Dev mode: always prefer cargo build output when running from source repo ---
|
|
65
138
|
// This covers: npm link, direct invocation from repo, CLAUDE_PROJECT_DIR set to repo
|
|
@@ -88,12 +161,8 @@ function findBinaryUncached() {
|
|
|
88
161
|
if (isNativeBinary(autoUpdateBin)) return autoUpdateBin;
|
|
89
162
|
|
|
90
163
|
// --- Platform-specific npm package (@sdsrs/code-graph-{os}-{arch}) ---
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
const pkgPath = require.resolve(`${platformPkg}/package.json`);
|
|
94
|
-
const bin = path.join(path.dirname(pkgPath), BINARY_NAME);
|
|
95
|
-
if (isNativeBinary(bin)) return bin;
|
|
96
|
-
} catch { /* not installed via npm */ }
|
|
164
|
+
const platformBin = findPlatformBinary();
|
|
165
|
+
if (platformBin) return platformBin;
|
|
97
166
|
|
|
98
167
|
// --- Bundled binary (in same directory as cli.js or plugin scripts) ---
|
|
99
168
|
// Check bin/ directory of the npm package
|
|
@@ -140,7 +209,11 @@ function clearCache() {
|
|
|
140
209
|
try { fs.unlinkSync(CACHE_FILE); } catch { /* ok */ }
|
|
141
210
|
}
|
|
142
211
|
|
|
143
|
-
module.exports = {
|
|
212
|
+
module.exports = {
|
|
213
|
+
findBinary, findBinaryUncached, clearCache,
|
|
214
|
+
globalNodeModulesCandidates, findPlatformBinary,
|
|
215
|
+
CACHE_FILE, BINARY_NAME, PLATFORM_PKG,
|
|
216
|
+
};
|
|
144
217
|
|
|
145
218
|
// Allow direct invocation for testing
|
|
146
219
|
if (require.main === module) {
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const test = require('node:test');
|
|
3
|
+
const assert = require('node:assert/strict');
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const os = require('os');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
|
|
8
|
+
const { globalNodeModulesCandidates, findPlatformBinary, BINARY_NAME } = require('./find-binary');
|
|
9
|
+
|
|
10
|
+
function mkDir(t, prefix) {
|
|
11
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), prefix));
|
|
12
|
+
t.after(() => fs.rmSync(dir, { recursive: true, force: true }));
|
|
13
|
+
return dir;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
test('globalNodeModulesCandidates includes derivation from process.execPath', () => {
|
|
17
|
+
const candidates = globalNodeModulesCandidates();
|
|
18
|
+
assert.ok(candidates.length > 0, 'at least one candidate path');
|
|
19
|
+
|
|
20
|
+
const nodeBinDir = path.dirname(process.execPath);
|
|
21
|
+
const expected = process.platform === 'win32'
|
|
22
|
+
? path.join(nodeBinDir, 'node_modules')
|
|
23
|
+
: path.resolve(nodeBinDir, '..', 'lib', 'node_modules');
|
|
24
|
+
assert.ok(candidates.includes(expected), `expected ${expected} in ${JSON.stringify(candidates)}`);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test('globalNodeModulesCandidates honors NPM_CONFIG_PREFIX', (t) => {
|
|
28
|
+
const original = process.env.NPM_CONFIG_PREFIX;
|
|
29
|
+
process.env.NPM_CONFIG_PREFIX = '/tmp/fake-npm-prefix';
|
|
30
|
+
t.after(() => {
|
|
31
|
+
if (original === undefined) delete process.env.NPM_CONFIG_PREFIX;
|
|
32
|
+
else process.env.NPM_CONFIG_PREFIX = original;
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
const candidates = globalNodeModulesCandidates();
|
|
36
|
+
const expected = process.platform === 'win32'
|
|
37
|
+
? path.join('/tmp/fake-npm-prefix', 'node_modules')
|
|
38
|
+
: path.join('/tmp/fake-npm-prefix', 'lib', 'node_modules');
|
|
39
|
+
assert.ok(candidates.includes(expected),
|
|
40
|
+
`expected NPM_CONFIG_PREFIX-derived path in candidates: ${JSON.stringify(candidates)}`);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test('globalNodeModulesCandidates dedupes overlapping paths', (t) => {
|
|
44
|
+
const original = process.env.NPM_CONFIG_PREFIX;
|
|
45
|
+
// Force NPM_CONFIG_PREFIX to match the execPath-derived prefix
|
|
46
|
+
const nodeBinDir = path.dirname(process.execPath);
|
|
47
|
+
const matchedPrefix = process.platform === 'win32'
|
|
48
|
+
? nodeBinDir
|
|
49
|
+
: path.resolve(nodeBinDir, '..');
|
|
50
|
+
process.env.NPM_CONFIG_PREFIX = matchedPrefix;
|
|
51
|
+
t.after(() => {
|
|
52
|
+
if (original === undefined) delete process.env.NPM_CONFIG_PREFIX;
|
|
53
|
+
else process.env.NPM_CONFIG_PREFIX = original;
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
const candidates = globalNodeModulesCandidates();
|
|
57
|
+
const seen = new Set();
|
|
58
|
+
for (const c of candidates) {
|
|
59
|
+
assert.ok(!seen.has(c), `duplicate candidate: ${c}`);
|
|
60
|
+
seen.add(c);
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test('findPlatformBinary locates platform pkg in NPM_CONFIG_PREFIX-derived global node_modules', (t) => {
|
|
65
|
+
// Mirror what `npm install -g` produces for @sdsrs/code-graph-{platform}-{arch}.
|
|
66
|
+
const fakePrefix = mkDir(t, 'find-binary-test-');
|
|
67
|
+
const platDir = process.platform === 'win32'
|
|
68
|
+
? path.join(fakePrefix, 'node_modules', '@sdsrs', `code-graph-${process.platform}-${process.arch}`)
|
|
69
|
+
: path.join(fakePrefix, 'lib', 'node_modules', '@sdsrs', `code-graph-${process.platform}-${process.arch}`);
|
|
70
|
+
fs.mkdirSync(platDir, { recursive: true });
|
|
71
|
+
|
|
72
|
+
// Copy node executable so realpathSync(candidate)'s basename === BINARY_NAME
|
|
73
|
+
// (isNativeBinary check). Plain copy, not symlink, so basename matches.
|
|
74
|
+
const fakeBinary = path.join(platDir, BINARY_NAME);
|
|
75
|
+
fs.copyFileSync(process.execPath, fakeBinary);
|
|
76
|
+
if (process.platform !== 'win32') fs.chmodSync(fakeBinary, 0o755);
|
|
77
|
+
|
|
78
|
+
const original = process.env.NPM_CONFIG_PREFIX;
|
|
79
|
+
process.env.NPM_CONFIG_PREFIX = fakePrefix;
|
|
80
|
+
t.after(() => {
|
|
81
|
+
if (original === undefined) delete process.env.NPM_CONFIG_PREFIX;
|
|
82
|
+
else process.env.NPM_CONFIG_PREFIX = original;
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
const found = findPlatformBinary();
|
|
86
|
+
assert.equal(found, fakeBinary, `expected ${fakeBinary}, got ${found}`);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test('findPlatformBinary returns null when no platform pkg installed anywhere reachable', (t) => {
|
|
90
|
+
// Point NPM_CONFIG_PREFIX at an empty dir so global probe cannot match.
|
|
91
|
+
const fakePrefix = mkDir(t, 'find-binary-empty-');
|
|
92
|
+
const original = process.env.NPM_CONFIG_PREFIX;
|
|
93
|
+
process.env.NPM_CONFIG_PREFIX = fakePrefix;
|
|
94
|
+
t.after(() => {
|
|
95
|
+
if (original === undefined) delete process.env.NPM_CONFIG_PREFIX;
|
|
96
|
+
else process.env.NPM_CONFIG_PREFIX = original;
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
// Note: this test only proves the negative if no real install of the platform
|
|
100
|
+
// package is reachable via require.resolve OR any other candidate path. On a
|
|
101
|
+
// dev machine that has `@sdsrs/code-graph-linux-x64` installed globally, this
|
|
102
|
+
// assertion will fail — that's not a defect of the helper but of test setup.
|
|
103
|
+
// Skip if a real install is detected.
|
|
104
|
+
const real = findPlatformBinary();
|
|
105
|
+
if (real && !real.startsWith(fakePrefix)) {
|
|
106
|
+
t.skip(`real platform pkg installed at ${real}, cannot test the null path here`);
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
assert.equal(real, null);
|
|
110
|
+
});
|
|
@@ -42,10 +42,29 @@ if (!binary) {
|
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
+
// Fallback: npm install may have succeeded but optionalDependencies for the
|
|
46
|
+
// platform binary can fail silently (npm tolerates OS-mismatch + flaky
|
|
47
|
+
// registry). Pull the platform binary directly from the GitHub release.
|
|
48
|
+
if (!binary) {
|
|
49
|
+
process.stderr.write('[code-graph] Falling back to GitHub release download...\n');
|
|
50
|
+
try {
|
|
51
|
+
execFileSync(process.execPath, [path.join(__dirname, 'auto-update.js'), '--silent'], {
|
|
52
|
+
timeout: 90000, stdio: 'pipe',
|
|
53
|
+
});
|
|
54
|
+
clearCache();
|
|
55
|
+
binary = findBinary();
|
|
56
|
+
if (binary) {
|
|
57
|
+
process.stderr.write(`[code-graph] Installed at ${binary}\n`);
|
|
58
|
+
}
|
|
59
|
+
} catch { /* fall through to manual-install message */ }
|
|
60
|
+
}
|
|
61
|
+
|
|
45
62
|
if (!binary) {
|
|
46
63
|
process.stderr.write(
|
|
47
64
|
'[code-graph] Binary not found. Install manually:\n' +
|
|
48
|
-
' npm install -g @sdsrs/code-graph\n'
|
|
65
|
+
' npm install -g @sdsrs/code-graph\n' +
|
|
66
|
+
' # or\n' +
|
|
67
|
+
' npm install -g @sdsrs/code-graph-' + process.platform + '-' + process.arch + '\n'
|
|
49
68
|
);
|
|
50
69
|
process.exit(1);
|
|
51
70
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sdsrs/code-graph",
|
|
3
|
-
"version": "0.16.
|
|
3
|
+
"version": "0.16.7",
|
|
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": {
|
|
@@ -34,10 +34,10 @@
|
|
|
34
34
|
"node": ">=16"
|
|
35
35
|
},
|
|
36
36
|
"optionalDependencies": {
|
|
37
|
-
"@sdsrs/code-graph-linux-x64": "0.16.
|
|
38
|
-
"@sdsrs/code-graph-linux-arm64": "0.16.
|
|
39
|
-
"@sdsrs/code-graph-darwin-x64": "0.16.
|
|
40
|
-
"@sdsrs/code-graph-darwin-arm64": "0.16.
|
|
41
|
-
"@sdsrs/code-graph-win32-x64": "0.16.
|
|
37
|
+
"@sdsrs/code-graph-linux-x64": "0.16.7",
|
|
38
|
+
"@sdsrs/code-graph-linux-arm64": "0.16.7",
|
|
39
|
+
"@sdsrs/code-graph-darwin-x64": "0.16.7",
|
|
40
|
+
"@sdsrs/code-graph-darwin-arm64": "0.16.7",
|
|
41
|
+
"@sdsrs/code-graph-win32-x64": "0.16.7"
|
|
42
42
|
}
|
|
43
43
|
}
|