typegraph-mcp 0.9.47 → 0.9.48
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/README.md +12 -3
- package/benchmark.ts +12 -3
- package/biome-config.ts +76 -0
- package/check.ts +67 -83
- package/cli.ts +43 -62
- package/commands/check.md +1 -1
- package/dist/benchmark.js +56 -19
- package/dist/check.js +213 -153
- package/dist/cli.js +1310 -1219
- package/dist/module-graph.js +22 -9
- package/dist/server.js +82 -38
- package/dist/smoke-test.js +66 -25
- package/dist/tsserver-client.js +35 -10
- package/module-graph.ts +29 -9
- package/package.json +5 -4
- package/scripts/self-install.ts +78 -0
- package/server.ts +26 -19
- package/smoke-test.ts +15 -6
- package/tsserver-client.ts +49 -11
- package/engine-sync-test.ts +0 -262
- package/export-surface-test.ts +0 -202
- package/install-oxlint-test.ts +0 -116
- package/smoke-test-selection-test.ts +0 -67
package/dist/module-graph.js
CHANGED
|
@@ -18,8 +18,19 @@ var SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
|
18
18
|
"coverage"
|
|
19
19
|
]);
|
|
20
20
|
var SKIP_FILES = /* @__PURE__ */ new Set(["routeTree.gen.ts"]);
|
|
21
|
-
function
|
|
21
|
+
function normalizeExcludedPaths(rootDir, excludedPaths) {
|
|
22
|
+
return excludedPaths.map(
|
|
23
|
+
(excludedPath) => path.resolve(rootDir, excludedPath)
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
function isExcluded(filePath, excludedPaths) {
|
|
27
|
+
return excludedPaths.some(
|
|
28
|
+
(excludedPath) => filePath === excludedPath || filePath.startsWith(excludedPath + path.sep)
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
function discoverFiles(rootDir, excludedPaths = []) {
|
|
22
32
|
const files = [];
|
|
33
|
+
const normalizedExclusions = normalizeExcludedPaths(rootDir, excludedPaths);
|
|
23
34
|
function walk(dir) {
|
|
24
35
|
let entries;
|
|
25
36
|
try {
|
|
@@ -31,7 +42,9 @@ function discoverFiles(rootDir) {
|
|
|
31
42
|
if (entry.isDirectory()) {
|
|
32
43
|
if (SKIP_DIRS.has(entry.name)) continue;
|
|
33
44
|
if (entry.name.startsWith(".") && dir !== rootDir) continue;
|
|
34
|
-
|
|
45
|
+
const childDir = path.join(dir, entry.name);
|
|
46
|
+
if (isExcluded(childDir, normalizedExclusions)) continue;
|
|
47
|
+
walk(childDir);
|
|
35
48
|
} else if (entry.isFile()) {
|
|
36
49
|
const name = entry.name;
|
|
37
50
|
if (SKIP_FILES.has(name)) continue;
|
|
@@ -132,11 +145,9 @@ function resolveProjectImport(resolver, fromDir, specifier, projectRoot) {
|
|
|
132
145
|
return null;
|
|
133
146
|
}
|
|
134
147
|
function createResolver(projectRoot, tsconfigPath) {
|
|
148
|
+
const configFile = path.resolve(projectRoot, tsconfigPath);
|
|
135
149
|
return new ResolverFactory({
|
|
136
|
-
tsconfig: {
|
|
137
|
-
configFile: path.resolve(projectRoot, tsconfigPath),
|
|
138
|
-
references: "auto"
|
|
139
|
-
},
|
|
150
|
+
...fs.existsSync(configFile) ? { tsconfig: { configFile, references: "auto" } } : {},
|
|
140
151
|
extensions: [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"],
|
|
141
152
|
extensionAlias: {
|
|
142
153
|
".js": [".ts", ".tsx", ".js"],
|
|
@@ -202,10 +213,10 @@ function buildReverseMap(forward) {
|
|
|
202
213
|
}
|
|
203
214
|
return reverse;
|
|
204
215
|
}
|
|
205
|
-
async function buildGraph(projectRoot, tsconfigPath) {
|
|
216
|
+
async function buildGraph(projectRoot, tsconfigPath, excludedPaths = []) {
|
|
206
217
|
const startTime = performance.now();
|
|
207
218
|
const resolver = createResolver(projectRoot, tsconfigPath);
|
|
208
|
-
const fileList = discoverFiles(projectRoot);
|
|
219
|
+
const fileList = discoverFiles(projectRoot, excludedPaths);
|
|
209
220
|
log(`Discovered ${fileList.length} source files`);
|
|
210
221
|
const { forward, parseFailures } = buildForwardEdges(fileList, resolver, projectRoot);
|
|
211
222
|
const reverse = buildReverseMap(forward);
|
|
@@ -297,8 +308,9 @@ function removeFile(graph, filePath) {
|
|
|
297
308
|
graph.reverse.delete(filePath);
|
|
298
309
|
graph.files.delete(filePath);
|
|
299
310
|
}
|
|
300
|
-
function startWatcher(projectRoot, graph, resolver, hooks) {
|
|
311
|
+
function startWatcher(projectRoot, graph, resolver, hooks, excludedPaths = []) {
|
|
301
312
|
try {
|
|
313
|
+
const normalizedExclusions = normalizeExcludedPaths(projectRoot, excludedPaths);
|
|
302
314
|
const watcher = fs.watch(
|
|
303
315
|
projectRoot,
|
|
304
316
|
{ recursive: true },
|
|
@@ -312,6 +324,7 @@ function startWatcher(projectRoot, graph, resolver, hooks) {
|
|
|
312
324
|
if (filename.endsWith(".d.ts") || filename.endsWith(".d.mts") || filename.endsWith(".d.cts"))
|
|
313
325
|
return;
|
|
314
326
|
const absPath = path.resolve(projectRoot, filename);
|
|
327
|
+
if (isExcluded(absPath, normalizedExclusions)) return;
|
|
315
328
|
if (fs.existsSync(absPath)) {
|
|
316
329
|
updateFile(graph, absPath, resolver, projectRoot);
|
|
317
330
|
void hooks?.onFileUpdated?.(absPath);
|
package/dist/server.js
CHANGED
|
@@ -12,6 +12,30 @@ import * as fs from "fs";
|
|
|
12
12
|
import { createRequire } from "module";
|
|
13
13
|
var log = (...args) => console.error("[typegraph/tsserver]", ...args);
|
|
14
14
|
var REQUEST_TIMEOUT_MS = 1e4;
|
|
15
|
+
function readPackageVersion(packagePath) {
|
|
16
|
+
const pkg = JSON.parse(fs.readFileSync(packagePath, "utf-8"));
|
|
17
|
+
return pkg.version ?? "unknown";
|
|
18
|
+
}
|
|
19
|
+
function resolveTsServer(projectRoot2) {
|
|
20
|
+
const projectRequire = createRequire(path.resolve(projectRoot2, "package.json"));
|
|
21
|
+
let projectVersion;
|
|
22
|
+
try {
|
|
23
|
+
const packagePath2 = projectRequire.resolve("typescript/package.json");
|
|
24
|
+
projectVersion = readPackageVersion(packagePath2);
|
|
25
|
+
const serverPath2 = projectRequire.resolve("typescript/lib/tsserver.js");
|
|
26
|
+
return { path: serverPath2, version: projectVersion, source: "project" };
|
|
27
|
+
} catch {
|
|
28
|
+
}
|
|
29
|
+
const toolRequire = createRequire(import.meta.url);
|
|
30
|
+
const packagePath = toolRequire.resolve("typescript/package.json");
|
|
31
|
+
const serverPath = toolRequire.resolve("typescript/lib/tsserver.js");
|
|
32
|
+
return {
|
|
33
|
+
path: serverPath,
|
|
34
|
+
version: readPackageVersion(packagePath),
|
|
35
|
+
source: "typegraph",
|
|
36
|
+
projectVersion
|
|
37
|
+
};
|
|
38
|
+
}
|
|
15
39
|
var TsServerClient = class {
|
|
16
40
|
constructor(projectRoot2, tsconfigPath2 = "./tsconfig.json") {
|
|
17
41
|
this.projectRoot = projectRoot2;
|
|
@@ -48,9 +72,12 @@ var TsServerClient = class {
|
|
|
48
72
|
// ─── Lifecycle ──────────────────────────────────────────────────────────
|
|
49
73
|
async start() {
|
|
50
74
|
if (this.child) return;
|
|
51
|
-
const
|
|
52
|
-
const tsserverPath =
|
|
53
|
-
|
|
75
|
+
const resolution = resolveTsServer(this.projectRoot);
|
|
76
|
+
const tsserverPath = resolution.path;
|
|
77
|
+
const fallbackNote = resolution.projectVersion ? `; project TypeScript ${resolution.projectVersion} has no legacy tsserver` : "";
|
|
78
|
+
log(
|
|
79
|
+
`Spawning tsserver: ${tsserverPath} (TypeScript ${resolution.version}, ${resolution.source}${fallbackNote})`
|
|
80
|
+
);
|
|
54
81
|
log(`Project root: ${this.projectRoot}`);
|
|
55
82
|
log(`tsconfig: ${this.tsconfigPath}`);
|
|
56
83
|
this.child = spawn("node", [tsserverPath, "--disableAutomaticTypingAcquisition"], {
|
|
@@ -79,12 +106,9 @@ var TsServerClient = class {
|
|
|
79
106
|
}
|
|
80
107
|
});
|
|
81
108
|
const warmStart = performance.now();
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
options: { allowJs: true, checkJs: false }
|
|
86
|
-
});
|
|
87
|
-
}
|
|
109
|
+
await this.sendRequest("compilerOptionsForInferredProjects", {
|
|
110
|
+
options: { allowJs: true, checkJs: false }
|
|
111
|
+
});
|
|
88
112
|
this.ready = true;
|
|
89
113
|
log(`Ready [${(performance.now() - warmStart).toFixed(0)}ms configure]`);
|
|
90
114
|
}
|
|
@@ -328,8 +352,19 @@ var SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
|
328
352
|
"coverage"
|
|
329
353
|
]);
|
|
330
354
|
var SKIP_FILES = /* @__PURE__ */ new Set(["routeTree.gen.ts"]);
|
|
331
|
-
function
|
|
355
|
+
function normalizeExcludedPaths(rootDir, excludedPaths2) {
|
|
356
|
+
return excludedPaths2.map(
|
|
357
|
+
(excludedPath) => path2.resolve(rootDir, excludedPath)
|
|
358
|
+
);
|
|
359
|
+
}
|
|
360
|
+
function isExcluded(filePath, excludedPaths2) {
|
|
361
|
+
return excludedPaths2.some(
|
|
362
|
+
(excludedPath) => filePath === excludedPath || filePath.startsWith(excludedPath + path2.sep)
|
|
363
|
+
);
|
|
364
|
+
}
|
|
365
|
+
function discoverFiles(rootDir, excludedPaths2 = []) {
|
|
332
366
|
const files = [];
|
|
367
|
+
const normalizedExclusions = normalizeExcludedPaths(rootDir, excludedPaths2);
|
|
333
368
|
function walk(dir) {
|
|
334
369
|
let entries;
|
|
335
370
|
try {
|
|
@@ -341,7 +376,9 @@ function discoverFiles(rootDir) {
|
|
|
341
376
|
if (entry.isDirectory()) {
|
|
342
377
|
if (SKIP_DIRS.has(entry.name)) continue;
|
|
343
378
|
if (entry.name.startsWith(".") && dir !== rootDir) continue;
|
|
344
|
-
|
|
379
|
+
const childDir = path2.join(dir, entry.name);
|
|
380
|
+
if (isExcluded(childDir, normalizedExclusions)) continue;
|
|
381
|
+
walk(childDir);
|
|
345
382
|
} else if (entry.isFile()) {
|
|
346
383
|
const name = entry.name;
|
|
347
384
|
if (SKIP_FILES.has(name)) continue;
|
|
@@ -442,11 +479,9 @@ function resolveProjectImport(resolver, fromDir, specifier, projectRoot2) {
|
|
|
442
479
|
return null;
|
|
443
480
|
}
|
|
444
481
|
function createResolver(projectRoot2, tsconfigPath2) {
|
|
482
|
+
const configFile = path2.resolve(projectRoot2, tsconfigPath2);
|
|
445
483
|
return new ResolverFactory({
|
|
446
|
-
tsconfig: {
|
|
447
|
-
configFile: path2.resolve(projectRoot2, tsconfigPath2),
|
|
448
|
-
references: "auto"
|
|
449
|
-
},
|
|
484
|
+
...fs2.existsSync(configFile) ? { tsconfig: { configFile, references: "auto" } } : {},
|
|
450
485
|
extensions: [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"],
|
|
451
486
|
extensionAlias: {
|
|
452
487
|
".js": [".ts", ".tsx", ".js"],
|
|
@@ -512,10 +547,10 @@ function buildReverseMap(forward) {
|
|
|
512
547
|
}
|
|
513
548
|
return reverse;
|
|
514
549
|
}
|
|
515
|
-
async function buildGraph(projectRoot2, tsconfigPath2) {
|
|
550
|
+
async function buildGraph(projectRoot2, tsconfigPath2, excludedPaths2 = []) {
|
|
516
551
|
const startTime = performance.now();
|
|
517
552
|
const resolver = createResolver(projectRoot2, tsconfigPath2);
|
|
518
|
-
const fileList = discoverFiles(projectRoot2);
|
|
553
|
+
const fileList = discoverFiles(projectRoot2, excludedPaths2);
|
|
519
554
|
log2(`Discovered ${fileList.length} source files`);
|
|
520
555
|
const { forward, parseFailures } = buildForwardEdges(fileList, resolver, projectRoot2);
|
|
521
556
|
const reverse = buildReverseMap(forward);
|
|
@@ -607,8 +642,9 @@ function removeFile(graph, filePath) {
|
|
|
607
642
|
graph.reverse.delete(filePath);
|
|
608
643
|
graph.files.delete(filePath);
|
|
609
644
|
}
|
|
610
|
-
function startWatcher(projectRoot2, graph, resolver, hooks) {
|
|
645
|
+
function startWatcher(projectRoot2, graph, resolver, hooks, excludedPaths2 = []) {
|
|
611
646
|
try {
|
|
647
|
+
const normalizedExclusions = normalizeExcludedPaths(projectRoot2, excludedPaths2);
|
|
612
648
|
const watcher = fs2.watch(
|
|
613
649
|
projectRoot2,
|
|
614
650
|
{ recursive: true },
|
|
@@ -622,6 +658,7 @@ function startWatcher(projectRoot2, graph, resolver, hooks) {
|
|
|
622
658
|
if (filename.endsWith(".d.ts") || filename.endsWith(".d.mts") || filename.endsWith(".d.cts"))
|
|
623
659
|
return;
|
|
624
660
|
const absPath2 = path2.resolve(projectRoot2, filename);
|
|
661
|
+
if (isExcluded(absPath2, normalizedExclusions)) return;
|
|
625
662
|
if (fs2.existsSync(absPath2)) {
|
|
626
663
|
updateFile(graph, absPath2, resolver, projectRoot2);
|
|
627
664
|
void hooks?.onFileUpdated?.(absPath2);
|
|
@@ -917,17 +954,18 @@ import * as path5 from "path";
|
|
|
917
954
|
|
|
918
955
|
// config.ts
|
|
919
956
|
import * as path4 from "path";
|
|
920
|
-
function resolveConfig(
|
|
957
|
+
function resolveConfig(toolDir2) {
|
|
921
958
|
const cwd = process.cwd();
|
|
922
|
-
const projectRoot2 = process.env["TYPEGRAPH_PROJECT_ROOT"] ? path4.resolve(cwd, process.env["TYPEGRAPH_PROJECT_ROOT"]) : path4.basename(path4.dirname(
|
|
959
|
+
const projectRoot2 = process.env["TYPEGRAPH_PROJECT_ROOT"] ? path4.resolve(cwd, process.env["TYPEGRAPH_PROJECT_ROOT"]) : path4.basename(path4.dirname(toolDir2)) === "plugins" ? path4.resolve(toolDir2, "../..") : cwd;
|
|
923
960
|
const tsconfigPath2 = process.env["TYPEGRAPH_TSCONFIG"] || "./tsconfig.json";
|
|
924
|
-
const
|
|
925
|
-
const toolRelPath =
|
|
926
|
-
return { projectRoot: projectRoot2, tsconfigPath: tsconfigPath2, toolDir, toolIsEmbedded, toolRelPath };
|
|
961
|
+
const toolIsEmbedded2 = toolDir2.startsWith(projectRoot2 + path4.sep);
|
|
962
|
+
const toolRelPath = toolIsEmbedded2 ? path4.relative(projectRoot2, toolDir2) : toolDir2;
|
|
963
|
+
return { projectRoot: projectRoot2, tsconfigPath: tsconfigPath2, toolDir: toolDir2, toolIsEmbedded: toolIsEmbedded2, toolRelPath };
|
|
927
964
|
}
|
|
928
965
|
|
|
929
966
|
// server.ts
|
|
930
|
-
var { projectRoot, tsconfigPath } = resolveConfig(import.meta.dirname);
|
|
967
|
+
var { projectRoot, tsconfigPath, toolDir, toolIsEmbedded } = resolveConfig(import.meta.dirname);
|
|
968
|
+
var excludedPaths = toolIsEmbedded ? [toolDir] : [];
|
|
931
969
|
var log3 = (...args) => console.error("[typegraph]", ...args);
|
|
932
970
|
var client = new TsServerClient(projectRoot, tsconfigPath);
|
|
933
971
|
var moduleGraph;
|
|
@@ -1714,25 +1752,31 @@ async function main() {
|
|
|
1714
1752
|
log3(`tsconfig: ${tsconfigPath}`);
|
|
1715
1753
|
const [, graphResult] = await Promise.all([
|
|
1716
1754
|
client.start(),
|
|
1717
|
-
buildGraph(projectRoot, tsconfigPath)
|
|
1755
|
+
buildGraph(projectRoot, tsconfigPath, excludedPaths)
|
|
1718
1756
|
]);
|
|
1719
1757
|
moduleGraph = graphResult.graph;
|
|
1720
1758
|
moduleResolver = graphResult.resolver;
|
|
1721
|
-
startWatcher(
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
(
|
|
1759
|
+
startWatcher(
|
|
1760
|
+
projectRoot,
|
|
1761
|
+
moduleGraph,
|
|
1762
|
+
graphResult.resolver,
|
|
1763
|
+
{
|
|
1764
|
+
onFileUpdated: (filePath) => client.reloadOpenFile(filePath).then(
|
|
1765
|
+
(wasOpen) => {
|
|
1766
|
+
if (!wasOpen) client.markProjectsDirty();
|
|
1767
|
+
},
|
|
1768
|
+
(err) => {
|
|
1769
|
+
client.markProjectsDirty();
|
|
1770
|
+
log3(`Failed to reload open file ${relPath(filePath)}:`, err);
|
|
1771
|
+
}
|
|
1772
|
+
),
|
|
1773
|
+
onFileDeleted: (filePath) => {
|
|
1774
|
+
client.closeFile(filePath);
|
|
1727
1775
|
client.markProjectsDirty();
|
|
1728
|
-
log3(`Failed to reload open file ${relPath(filePath)}:`, err);
|
|
1729
1776
|
}
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
client.markProjectsDirty();
|
|
1734
|
-
}
|
|
1735
|
-
});
|
|
1777
|
+
},
|
|
1778
|
+
excludedPaths
|
|
1779
|
+
);
|
|
1736
1780
|
const transport = new StdioServerTransport();
|
|
1737
1781
|
await mcpServer.connect(transport);
|
|
1738
1782
|
log3("MCP server connected and ready");
|
package/dist/smoke-test.js
CHANGED
|
@@ -10,6 +10,30 @@ import * as fs from "fs";
|
|
|
10
10
|
import { createRequire } from "module";
|
|
11
11
|
var log = (...args) => console.error("[typegraph/tsserver]", ...args);
|
|
12
12
|
var REQUEST_TIMEOUT_MS = 1e4;
|
|
13
|
+
function readPackageVersion(packagePath) {
|
|
14
|
+
const pkg = JSON.parse(fs.readFileSync(packagePath, "utf-8"));
|
|
15
|
+
return pkg.version ?? "unknown";
|
|
16
|
+
}
|
|
17
|
+
function resolveTsServer(projectRoot) {
|
|
18
|
+
const projectRequire = createRequire(path.resolve(projectRoot, "package.json"));
|
|
19
|
+
let projectVersion;
|
|
20
|
+
try {
|
|
21
|
+
const packagePath2 = projectRequire.resolve("typescript/package.json");
|
|
22
|
+
projectVersion = readPackageVersion(packagePath2);
|
|
23
|
+
const serverPath2 = projectRequire.resolve("typescript/lib/tsserver.js");
|
|
24
|
+
return { path: serverPath2, version: projectVersion, source: "project" };
|
|
25
|
+
} catch {
|
|
26
|
+
}
|
|
27
|
+
const toolRequire = createRequire(import.meta.url);
|
|
28
|
+
const packagePath = toolRequire.resolve("typescript/package.json");
|
|
29
|
+
const serverPath = toolRequire.resolve("typescript/lib/tsserver.js");
|
|
30
|
+
return {
|
|
31
|
+
path: serverPath,
|
|
32
|
+
version: readPackageVersion(packagePath),
|
|
33
|
+
source: "typegraph",
|
|
34
|
+
projectVersion
|
|
35
|
+
};
|
|
36
|
+
}
|
|
13
37
|
var TsServerClient = class {
|
|
14
38
|
constructor(projectRoot, tsconfigPath = "./tsconfig.json") {
|
|
15
39
|
this.projectRoot = projectRoot;
|
|
@@ -46,9 +70,12 @@ var TsServerClient = class {
|
|
|
46
70
|
// ─── Lifecycle ──────────────────────────────────────────────────────────
|
|
47
71
|
async start() {
|
|
48
72
|
if (this.child) return;
|
|
49
|
-
const
|
|
50
|
-
const tsserverPath =
|
|
51
|
-
|
|
73
|
+
const resolution = resolveTsServer(this.projectRoot);
|
|
74
|
+
const tsserverPath = resolution.path;
|
|
75
|
+
const fallbackNote = resolution.projectVersion ? `; project TypeScript ${resolution.projectVersion} has no legacy tsserver` : "";
|
|
76
|
+
log(
|
|
77
|
+
`Spawning tsserver: ${tsserverPath} (TypeScript ${resolution.version}, ${resolution.source}${fallbackNote})`
|
|
78
|
+
);
|
|
52
79
|
log(`Project root: ${this.projectRoot}`);
|
|
53
80
|
log(`tsconfig: ${this.tsconfigPath}`);
|
|
54
81
|
this.child = spawn("node", [tsserverPath, "--disableAutomaticTypingAcquisition"], {
|
|
@@ -77,12 +104,9 @@ var TsServerClient = class {
|
|
|
77
104
|
}
|
|
78
105
|
});
|
|
79
106
|
const warmStart = performance.now();
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
options: { allowJs: true, checkJs: false }
|
|
84
|
-
});
|
|
85
|
-
}
|
|
107
|
+
await this.sendRequest("compilerOptionsForInferredProjects", {
|
|
108
|
+
options: { allowJs: true, checkJs: false }
|
|
109
|
+
});
|
|
86
110
|
this.ready = true;
|
|
87
111
|
log(`Ready [${(performance.now() - warmStart).toFixed(0)}ms configure]`);
|
|
88
112
|
}
|
|
@@ -177,12 +201,12 @@ var TsServerClient = class {
|
|
|
177
201
|
command,
|
|
178
202
|
arguments: args
|
|
179
203
|
};
|
|
180
|
-
return new Promise((
|
|
204
|
+
return new Promise((resolve6, reject) => {
|
|
181
205
|
const timer = setTimeout(() => {
|
|
182
206
|
this.pending.delete(seq);
|
|
183
207
|
reject(new Error(`tsserver ${command} timed out after ${REQUEST_TIMEOUT_MS}ms`));
|
|
184
208
|
}, REQUEST_TIMEOUT_MS);
|
|
185
|
-
this.pending.set(seq, { resolve:
|
|
209
|
+
this.pending.set(seq, { resolve: resolve6, reject, timer, command });
|
|
186
210
|
this.child.stdin.write(JSON.stringify(request) + "\n");
|
|
187
211
|
});
|
|
188
212
|
}
|
|
@@ -326,8 +350,19 @@ var SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
|
326
350
|
"coverage"
|
|
327
351
|
]);
|
|
328
352
|
var SKIP_FILES = /* @__PURE__ */ new Set(["routeTree.gen.ts"]);
|
|
329
|
-
function
|
|
353
|
+
function normalizeExcludedPaths(rootDir, excludedPaths) {
|
|
354
|
+
return excludedPaths.map(
|
|
355
|
+
(excludedPath) => path2.resolve(rootDir, excludedPath)
|
|
356
|
+
);
|
|
357
|
+
}
|
|
358
|
+
function isExcluded(filePath, excludedPaths) {
|
|
359
|
+
return excludedPaths.some(
|
|
360
|
+
(excludedPath) => filePath === excludedPath || filePath.startsWith(excludedPath + path2.sep)
|
|
361
|
+
);
|
|
362
|
+
}
|
|
363
|
+
function discoverFiles(rootDir, excludedPaths = []) {
|
|
330
364
|
const files = [];
|
|
365
|
+
const normalizedExclusions = normalizeExcludedPaths(rootDir, excludedPaths);
|
|
331
366
|
function walk(dir) {
|
|
332
367
|
let entries;
|
|
333
368
|
try {
|
|
@@ -339,7 +374,9 @@ function discoverFiles(rootDir) {
|
|
|
339
374
|
if (entry.isDirectory()) {
|
|
340
375
|
if (SKIP_DIRS.has(entry.name)) continue;
|
|
341
376
|
if (entry.name.startsWith(".") && dir !== rootDir) continue;
|
|
342
|
-
|
|
377
|
+
const childDir = path2.join(dir, entry.name);
|
|
378
|
+
if (isExcluded(childDir, normalizedExclusions)) continue;
|
|
379
|
+
walk(childDir);
|
|
343
380
|
} else if (entry.isFile()) {
|
|
344
381
|
const name = entry.name;
|
|
345
382
|
if (SKIP_FILES.has(name)) continue;
|
|
@@ -440,11 +477,9 @@ function resolveProjectImport(resolver, fromDir, specifier, projectRoot) {
|
|
|
440
477
|
return null;
|
|
441
478
|
}
|
|
442
479
|
function createResolver(projectRoot, tsconfigPath) {
|
|
480
|
+
const configFile = path2.resolve(projectRoot, tsconfigPath);
|
|
443
481
|
return new ResolverFactory({
|
|
444
|
-
tsconfig: {
|
|
445
|
-
configFile: path2.resolve(projectRoot, tsconfigPath),
|
|
446
|
-
references: "auto"
|
|
447
|
-
},
|
|
482
|
+
...fs2.existsSync(configFile) ? { tsconfig: { configFile, references: "auto" } } : {},
|
|
448
483
|
extensions: [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"],
|
|
449
484
|
extensionAlias: {
|
|
450
485
|
".js": [".ts", ".tsx", ".js"],
|
|
@@ -510,10 +545,10 @@ function buildReverseMap(forward) {
|
|
|
510
545
|
}
|
|
511
546
|
return reverse;
|
|
512
547
|
}
|
|
513
|
-
async function buildGraph(projectRoot, tsconfigPath) {
|
|
548
|
+
async function buildGraph(projectRoot, tsconfigPath, excludedPaths = []) {
|
|
514
549
|
const startTime = performance.now();
|
|
515
550
|
const resolver = createResolver(projectRoot, tsconfigPath);
|
|
516
|
-
const fileList = discoverFiles(projectRoot);
|
|
551
|
+
const fileList = discoverFiles(projectRoot, excludedPaths);
|
|
517
552
|
log2(`Discovered ${fileList.length} source files`);
|
|
518
553
|
const { forward, parseFailures } = buildForwardEdges(fileList, resolver, projectRoot);
|
|
519
554
|
const reverse = buildReverseMap(forward);
|
|
@@ -877,8 +912,9 @@ var SKIP_DIRS2 = /* @__PURE__ */ new Set([
|
|
|
877
912
|
"coverage",
|
|
878
913
|
"out"
|
|
879
914
|
]);
|
|
880
|
-
function findTestFile(rootDir) {
|
|
915
|
+
function findTestFile(rootDir, excludedPaths = []) {
|
|
881
916
|
const candidates = [];
|
|
917
|
+
const normalizedExclusions = excludedPaths.map((excludedPath) => path5.resolve(excludedPath));
|
|
882
918
|
function walk(dir, depth) {
|
|
883
919
|
if (depth > 5 || candidates.length >= 30) return;
|
|
884
920
|
let entries;
|
|
@@ -890,7 +926,11 @@ function findTestFile(rootDir) {
|
|
|
890
926
|
for (const entry of entries) {
|
|
891
927
|
if (entry.isDirectory()) {
|
|
892
928
|
if (SKIP_DIRS2.has(entry.name) || entry.name.startsWith(".")) continue;
|
|
893
|
-
|
|
929
|
+
const childDir = path5.join(dir, entry.name);
|
|
930
|
+
if (normalizedExclusions.some(
|
|
931
|
+
(excludedPath) => childDir === excludedPath || childDir.startsWith(excludedPath + path5.sep)
|
|
932
|
+
)) continue;
|
|
933
|
+
walk(childDir, depth + 1);
|
|
894
934
|
} else if (entry.isFile()) {
|
|
895
935
|
const name = entry.name;
|
|
896
936
|
if (name.endsWith(".d.ts") || name.endsWith(".test.ts") || name.endsWith(".spec.ts"))
|
|
@@ -898,7 +938,7 @@ function findTestFile(rootDir) {
|
|
|
898
938
|
if (!name.endsWith(".ts") && !name.endsWith(".tsx")) continue;
|
|
899
939
|
try {
|
|
900
940
|
const stat = fs4.statSync(path5.join(dir, name));
|
|
901
|
-
if (stat.size >
|
|
941
|
+
if (stat.size > 0 && stat.size < 5e4) {
|
|
902
942
|
candidates.push({ file: path5.join(dir, name), size: stat.size });
|
|
903
943
|
}
|
|
904
944
|
} catch {
|
|
@@ -922,7 +962,8 @@ function findImporter(graph, file) {
|
|
|
922
962
|
return (preferred ?? revEdges[0]).target;
|
|
923
963
|
}
|
|
924
964
|
async function main(configOverride) {
|
|
925
|
-
const { projectRoot, tsconfigPath } = configOverride ?? resolveConfig(import.meta.dirname);
|
|
965
|
+
const { projectRoot, tsconfigPath, toolDir, toolIsEmbedded } = configOverride ?? resolveConfig(import.meta.dirname);
|
|
966
|
+
const excludedPaths = toolIsEmbedded ? [toolDir] : [];
|
|
926
967
|
let passed = 0;
|
|
927
968
|
let failed = 0;
|
|
928
969
|
let skipped = 0;
|
|
@@ -945,7 +986,7 @@ async function main(configOverride) {
|
|
|
945
986
|
console.log("=====================");
|
|
946
987
|
console.log(`Project root: ${projectRoot}`);
|
|
947
988
|
console.log("");
|
|
948
|
-
const testFile = findTestFile(projectRoot);
|
|
989
|
+
const testFile = findTestFile(projectRoot, excludedPaths);
|
|
949
990
|
if (!testFile) {
|
|
950
991
|
console.log(" No suitable .ts file found in project. Cannot run smoke tests.");
|
|
951
992
|
return { passed, failed: failed + 1, skipped };
|
|
@@ -958,7 +999,7 @@ async function main(configOverride) {
|
|
|
958
999
|
let t0;
|
|
959
1000
|
t0 = performance.now();
|
|
960
1001
|
try {
|
|
961
|
-
const result = await buildGraph(projectRoot, tsconfigPath);
|
|
1002
|
+
const result = await buildGraph(projectRoot, tsconfigPath, excludedPaths);
|
|
962
1003
|
graph = result.graph;
|
|
963
1004
|
const ms = performance.now() - t0;
|
|
964
1005
|
const edgeCount = [...graph.forward.values()].reduce((s, e) => s + e.length, 0);
|
package/dist/tsserver-client.js
CHANGED
|
@@ -5,6 +5,30 @@ import * as fs from "fs";
|
|
|
5
5
|
import { createRequire } from "module";
|
|
6
6
|
var log = (...args) => console.error("[typegraph/tsserver]", ...args);
|
|
7
7
|
var REQUEST_TIMEOUT_MS = 1e4;
|
|
8
|
+
function readPackageVersion(packagePath) {
|
|
9
|
+
const pkg = JSON.parse(fs.readFileSync(packagePath, "utf-8"));
|
|
10
|
+
return pkg.version ?? "unknown";
|
|
11
|
+
}
|
|
12
|
+
function resolveTsServer(projectRoot) {
|
|
13
|
+
const projectRequire = createRequire(path.resolve(projectRoot, "package.json"));
|
|
14
|
+
let projectVersion;
|
|
15
|
+
try {
|
|
16
|
+
const packagePath2 = projectRequire.resolve("typescript/package.json");
|
|
17
|
+
projectVersion = readPackageVersion(packagePath2);
|
|
18
|
+
const serverPath2 = projectRequire.resolve("typescript/lib/tsserver.js");
|
|
19
|
+
return { path: serverPath2, version: projectVersion, source: "project" };
|
|
20
|
+
} catch {
|
|
21
|
+
}
|
|
22
|
+
const toolRequire = createRequire(import.meta.url);
|
|
23
|
+
const packagePath = toolRequire.resolve("typescript/package.json");
|
|
24
|
+
const serverPath = toolRequire.resolve("typescript/lib/tsserver.js");
|
|
25
|
+
return {
|
|
26
|
+
path: serverPath,
|
|
27
|
+
version: readPackageVersion(packagePath),
|
|
28
|
+
source: "typegraph",
|
|
29
|
+
projectVersion
|
|
30
|
+
};
|
|
31
|
+
}
|
|
8
32
|
var TsServerClient = class {
|
|
9
33
|
constructor(projectRoot, tsconfigPath = "./tsconfig.json") {
|
|
10
34
|
this.projectRoot = projectRoot;
|
|
@@ -41,9 +65,12 @@ var TsServerClient = class {
|
|
|
41
65
|
// ─── Lifecycle ──────────────────────────────────────────────────────────
|
|
42
66
|
async start() {
|
|
43
67
|
if (this.child) return;
|
|
44
|
-
const
|
|
45
|
-
const tsserverPath =
|
|
46
|
-
|
|
68
|
+
const resolution = resolveTsServer(this.projectRoot);
|
|
69
|
+
const tsserverPath = resolution.path;
|
|
70
|
+
const fallbackNote = resolution.projectVersion ? `; project TypeScript ${resolution.projectVersion} has no legacy tsserver` : "";
|
|
71
|
+
log(
|
|
72
|
+
`Spawning tsserver: ${tsserverPath} (TypeScript ${resolution.version}, ${resolution.source}${fallbackNote})`
|
|
73
|
+
);
|
|
47
74
|
log(`Project root: ${this.projectRoot}`);
|
|
48
75
|
log(`tsconfig: ${this.tsconfigPath}`);
|
|
49
76
|
this.child = spawn("node", [tsserverPath, "--disableAutomaticTypingAcquisition"], {
|
|
@@ -72,12 +99,9 @@ var TsServerClient = class {
|
|
|
72
99
|
}
|
|
73
100
|
});
|
|
74
101
|
const warmStart = performance.now();
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
options: { allowJs: true, checkJs: false }
|
|
79
|
-
});
|
|
80
|
-
}
|
|
102
|
+
await this.sendRequest("compilerOptionsForInferredProjects", {
|
|
103
|
+
options: { allowJs: true, checkJs: false }
|
|
104
|
+
});
|
|
81
105
|
this.ready = true;
|
|
82
106
|
log(`Ready [${(performance.now() - warmStart).toFixed(0)}ms configure]`);
|
|
83
107
|
}
|
|
@@ -301,5 +325,6 @@ var TsServerClient = class {
|
|
|
301
325
|
}
|
|
302
326
|
};
|
|
303
327
|
export {
|
|
304
|
-
TsServerClient
|
|
328
|
+
TsServerClient,
|
|
329
|
+
resolveTsServer
|
|
305
330
|
};
|
package/module-graph.ts
CHANGED
|
@@ -55,8 +55,22 @@ const SKIP_FILES = new Set(["routeTree.gen.ts"]);
|
|
|
55
55
|
|
|
56
56
|
// ─── File Discovery ──────────────────────────────────────────────────────────
|
|
57
57
|
|
|
58
|
-
|
|
58
|
+
function normalizeExcludedPaths(rootDir: string, excludedPaths: string[]): string[] {
|
|
59
|
+
return excludedPaths.map((excludedPath) =>
|
|
60
|
+
path.resolve(rootDir, excludedPath)
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function isExcluded(filePath: string, excludedPaths: string[]): boolean {
|
|
65
|
+
return excludedPaths.some(
|
|
66
|
+
(excludedPath) =>
|
|
67
|
+
filePath === excludedPath || filePath.startsWith(excludedPath + path.sep)
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function discoverFiles(rootDir: string, excludedPaths: string[] = []): string[] {
|
|
59
72
|
const files: string[] = [];
|
|
73
|
+
const normalizedExclusions = normalizeExcludedPaths(rootDir, excludedPaths);
|
|
60
74
|
|
|
61
75
|
function walk(dir: string): void {
|
|
62
76
|
let entries: fs.Dirent[];
|
|
@@ -71,7 +85,9 @@ export function discoverFiles(rootDir: string): string[] {
|
|
|
71
85
|
if (SKIP_DIRS.has(entry.name)) continue;
|
|
72
86
|
// Skip hidden directories (except the root)
|
|
73
87
|
if (entry.name.startsWith(".") && dir !== rootDir) continue;
|
|
74
|
-
|
|
88
|
+
const childDir = path.join(dir, entry.name);
|
|
89
|
+
if (isExcluded(childDir, normalizedExclusions)) continue;
|
|
90
|
+
walk(childDir);
|
|
75
91
|
} else if (entry.isFile()) {
|
|
76
92
|
const name = entry.name;
|
|
77
93
|
if (SKIP_FILES.has(name)) continue;
|
|
@@ -240,11 +256,11 @@ export function resolveProjectImport(
|
|
|
240
256
|
}
|
|
241
257
|
|
|
242
258
|
export function createResolver(projectRoot: string, tsconfigPath: string): ResolverFactory {
|
|
259
|
+
const configFile = path.resolve(projectRoot, tsconfigPath);
|
|
243
260
|
return new ResolverFactory({
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
},
|
|
261
|
+
...(fs.existsSync(configFile)
|
|
262
|
+
? { tsconfig: { configFile, references: "auto" as const } }
|
|
263
|
+
: {}),
|
|
248
264
|
extensions: [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"],
|
|
249
265
|
extensionAlias: {
|
|
250
266
|
".js": [".ts", ".tsx", ".js"],
|
|
@@ -328,12 +344,13 @@ function buildReverseMap(forward: Map<string, ImportEdge[]>): Map<string, Import
|
|
|
328
344
|
|
|
329
345
|
export async function buildGraph(
|
|
330
346
|
projectRoot: string,
|
|
331
|
-
tsconfigPath: string
|
|
347
|
+
tsconfigPath: string,
|
|
348
|
+
excludedPaths: string[] = []
|
|
332
349
|
): Promise<BuildGraphResult> {
|
|
333
350
|
const startTime = performance.now();
|
|
334
351
|
|
|
335
352
|
const resolver = createResolver(projectRoot, tsconfigPath);
|
|
336
|
-
const fileList = discoverFiles(projectRoot);
|
|
353
|
+
const fileList = discoverFiles(projectRoot, excludedPaths);
|
|
337
354
|
|
|
338
355
|
log(`Discovered ${fileList.length} source files`);
|
|
339
356
|
|
|
@@ -464,9 +481,11 @@ export function startWatcher(
|
|
|
464
481
|
hooks?: {
|
|
465
482
|
onFileUpdated?: (filePath: string) => void | Promise<void>;
|
|
466
483
|
onFileDeleted?: (filePath: string) => void | Promise<void>;
|
|
467
|
-
}
|
|
484
|
+
},
|
|
485
|
+
excludedPaths: string[] = []
|
|
468
486
|
): void {
|
|
469
487
|
try {
|
|
488
|
+
const normalizedExclusions = normalizeExcludedPaths(projectRoot, excludedPaths);
|
|
470
489
|
const watcher = fs.watch(
|
|
471
490
|
projectRoot,
|
|
472
491
|
{ recursive: true },
|
|
@@ -489,6 +508,7 @@ export function startWatcher(
|
|
|
489
508
|
return;
|
|
490
509
|
|
|
491
510
|
const absPath = path.resolve(projectRoot, filename);
|
|
511
|
+
if (isExcluded(absPath, normalizedExclusions)) return;
|
|
492
512
|
|
|
493
513
|
if (fs.existsSync(absPath)) {
|
|
494
514
|
// File created or modified
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "typegraph-mcp",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.48",
|
|
4
4
|
"description": "Type-aware codebase navigation for AI coding agents — 14 MCP tools powered by tsserver + oxc",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -33,8 +33,9 @@
|
|
|
33
33
|
],
|
|
34
34
|
"scripts": {
|
|
35
35
|
"build": "tsup",
|
|
36
|
+
"setup:self": "tsx scripts/self-install.ts",
|
|
36
37
|
"start": "tsx server.ts",
|
|
37
|
-
"test": "tsx smoke-test-selection-test.ts && tsx export-surface-test.ts && tsx engine-sync-test.ts && tsx install-oxlint-test.ts",
|
|
38
|
+
"test": "tsx tests/smoke-test-selection-test.ts && tsx tests/biome-config-test.ts && tsx tests/self-install-test.ts && tsx tests/tsserver-resolution-test.ts && tsx tests/export-surface-test.ts && tsx tests/engine-sync-test.ts && tsx tests/install-oxlint-test.ts",
|
|
38
39
|
"check": "tsx check.ts"
|
|
39
40
|
},
|
|
40
41
|
"dependencies": {
|
|
@@ -42,12 +43,12 @@
|
|
|
42
43
|
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
43
44
|
"oxc-parser": "^0.114.0",
|
|
44
45
|
"oxc-resolver": "^11.17.1",
|
|
46
|
+
"typescript": "^5.9.3",
|
|
45
47
|
"zod": "^4.3.6"
|
|
46
48
|
},
|
|
47
49
|
"devDependencies": {
|
|
48
50
|
"@types/node": "^25.3.0",
|
|
49
51
|
"tsup": "^8.5.0",
|
|
50
|
-
"tsx": "^4.21.0"
|
|
51
|
-
"typescript": "^5.8.0"
|
|
52
|
+
"tsx": "^4.21.0"
|
|
52
53
|
}
|
|
53
54
|
}
|