typegraph-mcp 0.9.1 → 0.9.3
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/plugin.json +1 -1
- package/.cursor-plugin/plugin.json +1 -1
- package/README.md +4 -4
- package/check.ts +7 -7
- package/cli.ts +4 -2
- package/dist/check.js +376 -0
- package/dist/cli.js +2842 -0
- package/dist/config.js +13 -0
- package/dist/graph-queries.js +279 -0
- package/dist/module-graph.js +336 -0
- package/dist/server.js +1479 -0
- package/dist/smoke-test.js +1148 -0
- package/dist/tsserver-client.js +266 -0
- package/package.json +10 -6
- package/scripts/ensure-deps.sh +2 -5
- package/tsup.config.ts +39 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "typegraph",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.3",
|
|
4
4
|
"description": "Type-aware TypeScript navigation — 14 MCP tools for go-to-definition, find-references, dependency graphs, cycle detection, and impact analysis",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Owen Jones"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "typegraph",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.3",
|
|
4
4
|
"description": "Type-aware TypeScript navigation — 14 MCP tools for go-to-definition, find-references, dependency graphs, cycle detection, and impact analysis",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Owen Jones"
|
package/README.md
CHANGED
|
@@ -48,7 +48,7 @@ Agent: ts_trace_chain({ file: "src/handlers.ts", symbol: "createUser" })
|
|
|
48
48
|
```bash
|
|
49
49
|
# Clone and install
|
|
50
50
|
git clone https://github.com/guyowen/typegraph-mcp.git ~/typegraph-mcp
|
|
51
|
-
cd ~/typegraph-mcp &&
|
|
51
|
+
cd ~/typegraph-mcp && npm install
|
|
52
52
|
|
|
53
53
|
# Load the plugin
|
|
54
54
|
claude --plugin-dir ~/typegraph-mcp
|
|
@@ -65,7 +65,7 @@ The plugin auto-configures everything:
|
|
|
65
65
|
```bash
|
|
66
66
|
# Clone and install
|
|
67
67
|
git clone https://github.com/guyowen/typegraph-mcp.git ~/typegraph-mcp
|
|
68
|
-
cd ~/typegraph-mcp &&
|
|
68
|
+
cd ~/typegraph-mcp && npm install
|
|
69
69
|
|
|
70
70
|
# Run setup from your project root
|
|
71
71
|
cd /path/to/your-ts-project
|
|
@@ -115,7 +115,7 @@ First query takes ~2s (tsserver warmup). Subsequent queries: 1–60ms.
|
|
|
115
115
|
|
|
116
116
|
- **Node.js** >= 18
|
|
117
117
|
- **TypeScript** >= 5.0 in the target project (`node_modules`)
|
|
118
|
-
- **
|
|
118
|
+
- **npm** for installing typegraph-mcp dependencies
|
|
119
119
|
|
|
120
120
|
## CLI
|
|
121
121
|
|
|
@@ -435,7 +435,7 @@ npx tsx ~/typegraph-mcp/cli.ts check
|
|
|
435
435
|
|
|
436
436
|
| Symptom | Likely cause | Fix |
|
|
437
437
|
|---|---|---|
|
|
438
|
-
| Server won't start | Dependencies missing | `cd /path/to/typegraph-mcp &&
|
|
438
|
+
| Server won't start | Dependencies missing | `cd /path/to/typegraph-mcp && npm install` |
|
|
439
439
|
| "TypeScript not found" | Target project missing TS | Add `typescript` to devDependencies |
|
|
440
440
|
| Tools return empty results | tsconfig misconfigured | Check `TYPEGRAPH_TSCONFIG` points to the right file |
|
|
441
441
|
| MCP registration not found | Wrong path in config | Verify the `args` path to `server.ts` is absolute |
|
package/check.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* npx tsx plugins/typegraph-mcp/check.ts
|
|
7
7
|
*
|
|
8
8
|
* Or from plugins/typegraph-mcp/:
|
|
9
|
-
*
|
|
9
|
+
* npm run check
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import * as fs from "node:fs";
|
|
@@ -170,7 +170,7 @@ export async function main(configOverride?: TypegraphConfig): Promise<CheckResul
|
|
|
170
170
|
} catch {
|
|
171
171
|
fail(
|
|
172
172
|
"TypeScript not found in project",
|
|
173
|
-
"Add `typescript` to devDependencies and run `
|
|
173
|
+
"Add `typescript` to devDependencies and run `npm install`"
|
|
174
174
|
);
|
|
175
175
|
}
|
|
176
176
|
|
|
@@ -251,10 +251,10 @@ export async function main(configOverride?: TypegraphConfig): Promise<CheckResul
|
|
|
251
251
|
if (missing.length === 0) {
|
|
252
252
|
pass(`Dependencies installed (${requiredPkgs.length} packages)`);
|
|
253
253
|
} else {
|
|
254
|
-
fail(`Missing packages: ${missing.join(", ")}`, `Run \`cd ${toolRelPath} &&
|
|
254
|
+
fail(`Missing packages: ${missing.join(", ")}`, `Run \`cd ${toolRelPath} && npm install\``);
|
|
255
255
|
}
|
|
256
256
|
} else {
|
|
257
|
-
fail("typegraph-mcp dependencies not installed", `Run \`cd ${toolRelPath} &&
|
|
257
|
+
fail("typegraph-mcp dependencies not installed", `Run \`cd ${toolRelPath} && npm install\``);
|
|
258
258
|
}
|
|
259
259
|
|
|
260
260
|
// 7. oxc-parser smoke test
|
|
@@ -267,13 +267,13 @@ export async function main(configOverride?: TypegraphConfig): Promise<CheckResul
|
|
|
267
267
|
} else {
|
|
268
268
|
fail(
|
|
269
269
|
"oxc-parser parseSync returned unexpected result",
|
|
270
|
-
`Reinstall: \`cd ${toolRelPath} && rm -rf node_modules &&
|
|
270
|
+
`Reinstall: \`cd ${toolRelPath} && rm -rf node_modules && npm install\``
|
|
271
271
|
);
|
|
272
272
|
}
|
|
273
273
|
} catch (err) {
|
|
274
274
|
fail(
|
|
275
275
|
`oxc-parser failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
276
|
-
`Reinstall: \`cd ${toolRelPath} && rm -rf node_modules &&
|
|
276
|
+
`Reinstall: \`cd ${toolRelPath} && rm -rf node_modules && npm install\``
|
|
277
277
|
);
|
|
278
278
|
}
|
|
279
279
|
|
|
@@ -307,7 +307,7 @@ export async function main(configOverride?: TypegraphConfig): Promise<CheckResul
|
|
|
307
307
|
} catch (err) {
|
|
308
308
|
fail(
|
|
309
309
|
`oxc-resolver failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
310
|
-
`Reinstall: \`cd ${toolRelPath} && rm -rf node_modules &&
|
|
310
|
+
`Reinstall: \`cd ${toolRelPath} && rm -rf node_modules && npm install\``
|
|
311
311
|
);
|
|
312
312
|
}
|
|
313
313
|
|
package/cli.ts
CHANGED
|
@@ -111,7 +111,7 @@ const CORE_FILES = [
|
|
|
111
111
|
"smoke-test.ts",
|
|
112
112
|
"cli.ts",
|
|
113
113
|
"package.json",
|
|
114
|
-
"
|
|
114
|
+
"package-lock.json",
|
|
115
115
|
];
|
|
116
116
|
|
|
117
117
|
/** Skill files inside plugin dir (Claude Code + Cursor discover from skills/) */
|
|
@@ -356,7 +356,9 @@ async function selectAgents(projectRoot: string, yes: boolean): Promise<AgentId[
|
|
|
356
356
|
// ─── Setup Command ───────────────────────────────────────────────────────────
|
|
357
357
|
|
|
358
358
|
async function setup(yes: boolean): Promise<void> {
|
|
359
|
-
const sourceDir = import.meta.dirname
|
|
359
|
+
const sourceDir = path.basename(import.meta.dirname) === "dist"
|
|
360
|
+
? path.resolve(import.meta.dirname, "..")
|
|
361
|
+
: import.meta.dirname;
|
|
360
362
|
const projectRoot = process.cwd();
|
|
361
363
|
|
|
362
364
|
process.stdout.write("\x1Bc"); // Clear terminal
|
package/dist/check.js
ADDED
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
|
|
2
|
+
// check.ts
|
|
3
|
+
import * as fs from "fs";
|
|
4
|
+
import * as path2 from "path";
|
|
5
|
+
import { createRequire } from "module";
|
|
6
|
+
import { spawn } from "child_process";
|
|
7
|
+
|
|
8
|
+
// config.ts
|
|
9
|
+
import * as path from "path";
|
|
10
|
+
function resolveConfig(toolDir) {
|
|
11
|
+
const cwd = process.cwd();
|
|
12
|
+
const projectRoot = process.env["TYPEGRAPH_PROJECT_ROOT"] ? path.resolve(cwd, process.env["TYPEGRAPH_PROJECT_ROOT"]) : path.basename(path.dirname(toolDir)) === "plugins" ? path.resolve(toolDir, "../..") : cwd;
|
|
13
|
+
const tsconfigPath = process.env["TYPEGRAPH_TSCONFIG"] || "./tsconfig.json";
|
|
14
|
+
const toolIsEmbedded = toolDir.startsWith(projectRoot + path.sep);
|
|
15
|
+
const toolRelPath = toolIsEmbedded ? path.relative(projectRoot, toolDir) : toolDir;
|
|
16
|
+
return { projectRoot, tsconfigPath, toolDir, toolIsEmbedded, toolRelPath };
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// check.ts
|
|
20
|
+
function findFirstTsFile(dir) {
|
|
21
|
+
const skipDirs = /* @__PURE__ */ new Set(["node_modules", "dist", ".git", ".wrangler", "coverage"]);
|
|
22
|
+
try {
|
|
23
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
24
|
+
if (entry.isFile() && entry.name.endsWith(".ts") && !entry.name.endsWith(".d.ts")) {
|
|
25
|
+
return path2.join(dir, entry.name);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
29
|
+
if (entry.isDirectory() && !skipDirs.has(entry.name) && !entry.name.startsWith(".")) {
|
|
30
|
+
const found = findFirstTsFile(path2.join(dir, entry.name));
|
|
31
|
+
if (found) return found;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
} catch {
|
|
35
|
+
}
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
function testTsserver(projectRoot) {
|
|
39
|
+
return new Promise((resolve3) => {
|
|
40
|
+
let tsserverPath;
|
|
41
|
+
try {
|
|
42
|
+
const require2 = createRequire(path2.resolve(projectRoot, "package.json"));
|
|
43
|
+
tsserverPath = require2.resolve("typescript/lib/tsserver.js");
|
|
44
|
+
} catch {
|
|
45
|
+
resolve3(false);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
const child = spawn("node", [tsserverPath, "--disableAutomaticTypingAcquisition"], {
|
|
49
|
+
cwd: projectRoot,
|
|
50
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
51
|
+
});
|
|
52
|
+
const timeout = setTimeout(() => {
|
|
53
|
+
child.kill();
|
|
54
|
+
resolve3(false);
|
|
55
|
+
}, 1e4);
|
|
56
|
+
let buffer = "";
|
|
57
|
+
child.stdout.on("data", (chunk) => {
|
|
58
|
+
buffer += chunk.toString();
|
|
59
|
+
if (buffer.includes('"success":true')) {
|
|
60
|
+
clearTimeout(timeout);
|
|
61
|
+
child.kill();
|
|
62
|
+
resolve3(true);
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
child.on("error", () => {
|
|
66
|
+
clearTimeout(timeout);
|
|
67
|
+
resolve3(false);
|
|
68
|
+
});
|
|
69
|
+
child.on("exit", () => {
|
|
70
|
+
clearTimeout(timeout);
|
|
71
|
+
});
|
|
72
|
+
const request = JSON.stringify({
|
|
73
|
+
seq: 1,
|
|
74
|
+
type: "request",
|
|
75
|
+
command: "configure",
|
|
76
|
+
arguments: {
|
|
77
|
+
preferences: { disableSuggestions: true }
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
child.stdin.write(request + "\n");
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
async function main(configOverride) {
|
|
84
|
+
const { projectRoot, tsconfigPath, toolDir, toolIsEmbedded, toolRelPath } = configOverride ?? resolveConfig(import.meta.dirname);
|
|
85
|
+
let passed = 0;
|
|
86
|
+
let failed = 0;
|
|
87
|
+
let warned = 0;
|
|
88
|
+
function pass(msg) {
|
|
89
|
+
console.log(` \u2713 ${msg}`);
|
|
90
|
+
passed++;
|
|
91
|
+
}
|
|
92
|
+
function fail(msg, fix) {
|
|
93
|
+
console.log(` \u2717 ${msg}`);
|
|
94
|
+
console.log(` Fix: ${fix}`);
|
|
95
|
+
failed++;
|
|
96
|
+
}
|
|
97
|
+
function warn(msg, note) {
|
|
98
|
+
console.log(` ! ${msg}`);
|
|
99
|
+
console.log(` ${note}`);
|
|
100
|
+
warned++;
|
|
101
|
+
}
|
|
102
|
+
function skip(msg) {
|
|
103
|
+
console.log(` - ${msg} (skipped)`);
|
|
104
|
+
}
|
|
105
|
+
console.log("");
|
|
106
|
+
console.log("typegraph-mcp Health Check");
|
|
107
|
+
console.log("=======================");
|
|
108
|
+
console.log(`Project root: ${projectRoot}`);
|
|
109
|
+
console.log("");
|
|
110
|
+
const nodeVersion = process.version;
|
|
111
|
+
const nodeMajor = parseInt(nodeVersion.slice(1).split(".")[0], 10);
|
|
112
|
+
if (nodeMajor >= 18) {
|
|
113
|
+
pass(`Node.js ${nodeVersion} (>= 18 required)`);
|
|
114
|
+
} else {
|
|
115
|
+
fail(`Node.js ${nodeVersion} is too old`, "Upgrade Node.js to >= 18");
|
|
116
|
+
}
|
|
117
|
+
const tsxInRoot = fs.existsSync(path2.join(projectRoot, "node_modules/.bin/tsx"));
|
|
118
|
+
const tsxInTool = fs.existsSync(path2.join(toolDir, "node_modules/.bin/tsx"));
|
|
119
|
+
if (tsxInRoot || tsxInTool) {
|
|
120
|
+
pass(`tsx available (in ${tsxInRoot ? "project" : "tool"} node_modules)`);
|
|
121
|
+
} else {
|
|
122
|
+
pass("tsx available (via npx/global)");
|
|
123
|
+
}
|
|
124
|
+
let tsVersion = null;
|
|
125
|
+
try {
|
|
126
|
+
const require2 = createRequire(path2.resolve(projectRoot, "package.json"));
|
|
127
|
+
const tsserverPath = require2.resolve("typescript/lib/tsserver.js");
|
|
128
|
+
const tsPkgPath = path2.resolve(path2.dirname(tsserverPath), "..", "package.json");
|
|
129
|
+
const tsPkg = JSON.parse(fs.readFileSync(tsPkgPath, "utf-8"));
|
|
130
|
+
tsVersion = tsPkg.version;
|
|
131
|
+
pass(`TypeScript found (v${tsVersion})`);
|
|
132
|
+
} catch {
|
|
133
|
+
fail(
|
|
134
|
+
"TypeScript not found in project",
|
|
135
|
+
"Add `typescript` to devDependencies and run `npm install`"
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
const tsconfigAbs = path2.resolve(projectRoot, tsconfigPath);
|
|
139
|
+
if (fs.existsSync(tsconfigAbs)) {
|
|
140
|
+
pass(`tsconfig.json exists at ${tsconfigPath}`);
|
|
141
|
+
} else {
|
|
142
|
+
fail(`tsconfig.json not found at ${tsconfigPath}`, `Create a tsconfig.json at ${tsconfigPath}`);
|
|
143
|
+
}
|
|
144
|
+
const pluginMcpPath = path2.join(toolDir, ".mcp.json");
|
|
145
|
+
const hasPluginMcp = fs.existsSync(pluginMcpPath) && fs.existsSync(path2.join(toolDir, ".claude-plugin/plugin.json"));
|
|
146
|
+
if (process.env.CLAUDE_PLUGIN_ROOT) {
|
|
147
|
+
pass("MCP registered via plugin (CLAUDE_PLUGIN_ROOT set)");
|
|
148
|
+
} else if (hasPluginMcp) {
|
|
149
|
+
pass("MCP registered via plugin (.mcp.json + .claude-plugin/ present)");
|
|
150
|
+
} else {
|
|
151
|
+
const mcpJsonPath = path2.resolve(projectRoot, ".claude/mcp.json");
|
|
152
|
+
if (fs.existsSync(mcpJsonPath)) {
|
|
153
|
+
try {
|
|
154
|
+
const mcpJson = JSON.parse(fs.readFileSync(mcpJsonPath, "utf-8"));
|
|
155
|
+
const tsNav = mcpJson?.mcpServers?.["typegraph"];
|
|
156
|
+
if (tsNav) {
|
|
157
|
+
const hasCommand = tsNav.command === "npx";
|
|
158
|
+
const hasArgs = Array.isArray(tsNav.args) && tsNav.args.includes("tsx");
|
|
159
|
+
const hasEnv = tsNav.env?.["TYPEGRAPH_PROJECT_ROOT"] && tsNav.env?.["TYPEGRAPH_TSCONFIG"];
|
|
160
|
+
if (hasCommand && hasArgs && hasEnv) {
|
|
161
|
+
pass("MCP registered in .claude/mcp.json");
|
|
162
|
+
} else {
|
|
163
|
+
const issues = [];
|
|
164
|
+
if (!hasCommand) issues.push("command should be 'npx'");
|
|
165
|
+
if (!hasArgs) issues.push("args should include 'tsx'");
|
|
166
|
+
if (!hasEnv) issues.push("env should set TYPEGRAPH_PROJECT_ROOT and TYPEGRAPH_TSCONFIG");
|
|
167
|
+
fail(
|
|
168
|
+
`MCP registration incomplete: ${issues.join(", ")}`,
|
|
169
|
+
"See README for correct .claude/mcp.json format"
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
} else {
|
|
173
|
+
const serverPath = toolIsEmbedded ? `./${toolRelPath}/server.ts` : path2.resolve(toolDir, "server.ts");
|
|
174
|
+
fail(
|
|
175
|
+
"MCP entry 'typegraph' not found in .claude/mcp.json",
|
|
176
|
+
`Add to .claude/mcp.json:
|
|
177
|
+
{
|
|
178
|
+
"mcpServers": {
|
|
179
|
+
"typegraph": {
|
|
180
|
+
"command": "npx",
|
|
181
|
+
"args": ["tsx", "${serverPath}"],
|
|
182
|
+
"env": { "TYPEGRAPH_PROJECT_ROOT": ".", "TYPEGRAPH_TSCONFIG": "./tsconfig.json" }
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}`
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
} catch (err) {
|
|
189
|
+
fail(
|
|
190
|
+
"Failed to parse .claude/mcp.json",
|
|
191
|
+
`Check JSON syntax: ${err instanceof Error ? err.message : String(err)}`
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
} else {
|
|
195
|
+
fail(".claude/mcp.json not found", `Create .claude/mcp.json with typegraph server registration`);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
const toolNodeModules = path2.join(toolDir, "node_modules");
|
|
199
|
+
if (fs.existsSync(toolNodeModules)) {
|
|
200
|
+
const requiredPkgs = ["@modelcontextprotocol/sdk", "oxc-parser", "oxc-resolver", "zod"];
|
|
201
|
+
const missing = requiredPkgs.filter(
|
|
202
|
+
(pkg) => !fs.existsSync(path2.join(toolNodeModules, ...pkg.split("/")))
|
|
203
|
+
);
|
|
204
|
+
if (missing.length === 0) {
|
|
205
|
+
pass(`Dependencies installed (${requiredPkgs.length} packages)`);
|
|
206
|
+
} else {
|
|
207
|
+
fail(`Missing packages: ${missing.join(", ")}`, `Run \`cd ${toolRelPath} && npm install\``);
|
|
208
|
+
}
|
|
209
|
+
} else {
|
|
210
|
+
fail("typegraph-mcp dependencies not installed", `Run \`cd ${toolRelPath} && npm install\``);
|
|
211
|
+
}
|
|
212
|
+
try {
|
|
213
|
+
const oxcParserReq = createRequire(path2.join(toolDir, "package.json"));
|
|
214
|
+
const { parseSync } = await import(oxcParserReq.resolve("oxc-parser"));
|
|
215
|
+
const result = parseSync("test.ts", 'import { x } from "./y";');
|
|
216
|
+
if (result.module?.staticImports?.length === 1) {
|
|
217
|
+
pass("oxc-parser working");
|
|
218
|
+
} else {
|
|
219
|
+
fail(
|
|
220
|
+
"oxc-parser parseSync returned unexpected result",
|
|
221
|
+
`Reinstall: \`cd ${toolRelPath} && rm -rf node_modules && npm install\``
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
} catch (err) {
|
|
225
|
+
fail(
|
|
226
|
+
`oxc-parser failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
227
|
+
`Reinstall: \`cd ${toolRelPath} && rm -rf node_modules && npm install\``
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
try {
|
|
231
|
+
const oxcResolverReq = createRequire(path2.join(toolDir, "package.json"));
|
|
232
|
+
const { ResolverFactory } = await import(oxcResolverReq.resolve("oxc-resolver"));
|
|
233
|
+
const resolver = new ResolverFactory({
|
|
234
|
+
tsconfig: { configFile: tsconfigAbs, references: "auto" },
|
|
235
|
+
extensions: [".ts", ".tsx", ".js"],
|
|
236
|
+
extensionAlias: { ".js": [".ts", ".tsx", ".js"] }
|
|
237
|
+
});
|
|
238
|
+
let resolveOk = false;
|
|
239
|
+
const testFile = findFirstTsFile(projectRoot);
|
|
240
|
+
if (testFile) {
|
|
241
|
+
const dir = path2.dirname(testFile);
|
|
242
|
+
const base = "./" + path2.basename(testFile);
|
|
243
|
+
const result = resolver.sync(dir, base);
|
|
244
|
+
resolveOk = !!result.path;
|
|
245
|
+
}
|
|
246
|
+
if (resolveOk) {
|
|
247
|
+
pass("oxc-resolver working");
|
|
248
|
+
} else {
|
|
249
|
+
warn(
|
|
250
|
+
"oxc-resolver loaded but couldn't resolve a test import",
|
|
251
|
+
"Check tsconfig.json is valid and has correct `references`"
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
} catch (err) {
|
|
255
|
+
fail(
|
|
256
|
+
`oxc-resolver failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
257
|
+
`Reinstall: \`cd ${toolRelPath} && rm -rf node_modules && npm install\``
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
if (tsVersion) {
|
|
261
|
+
try {
|
|
262
|
+
const ok = await testTsserver(projectRoot);
|
|
263
|
+
if (ok) {
|
|
264
|
+
pass("tsserver responds to configure");
|
|
265
|
+
} else {
|
|
266
|
+
fail(
|
|
267
|
+
"tsserver did not respond",
|
|
268
|
+
"Verify `typescript` is installed and tsconfig.json is valid"
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
} catch (err) {
|
|
272
|
+
fail(
|
|
273
|
+
`tsserver failed to start: ${err instanceof Error ? err.message : String(err)}`,
|
|
274
|
+
"Verify `typescript` is installed and tsconfig.json is valid"
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
} else {
|
|
278
|
+
skip("tsserver test (TypeScript not found)");
|
|
279
|
+
}
|
|
280
|
+
try {
|
|
281
|
+
const { buildGraph } = await import(path2.resolve(toolDir, "module-graph.js"));
|
|
282
|
+
const start = performance.now();
|
|
283
|
+
const { graph } = await buildGraph(projectRoot, tsconfigPath);
|
|
284
|
+
const elapsed = (performance.now() - start).toFixed(0);
|
|
285
|
+
const edgeCount = [...graph.forward.values()].reduce(
|
|
286
|
+
(s, e) => s + e.length,
|
|
287
|
+
0
|
|
288
|
+
);
|
|
289
|
+
if (graph.files.size > 0 && edgeCount > 0) {
|
|
290
|
+
pass(`Module graph: ${graph.files.size} files, ${edgeCount} edges [${elapsed}ms]`);
|
|
291
|
+
} else if (graph.files.size > 0) {
|
|
292
|
+
warn(
|
|
293
|
+
`Module graph: ${graph.files.size} files but 0 edges`,
|
|
294
|
+
"Files found but no internal imports resolved. Check tsconfig references."
|
|
295
|
+
);
|
|
296
|
+
} else {
|
|
297
|
+
fail(
|
|
298
|
+
"Module graph: 0 files discovered",
|
|
299
|
+
"Check tsconfig.json includes source files and project root is correct"
|
|
300
|
+
);
|
|
301
|
+
}
|
|
302
|
+
} catch (err) {
|
|
303
|
+
fail(
|
|
304
|
+
`Module graph build failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
305
|
+
"Check that oxc-parser and oxc-resolver are installed correctly"
|
|
306
|
+
);
|
|
307
|
+
}
|
|
308
|
+
if (toolIsEmbedded) {
|
|
309
|
+
const eslintConfigPath = path2.resolve(projectRoot, "eslint.config.mjs");
|
|
310
|
+
if (fs.existsSync(eslintConfigPath)) {
|
|
311
|
+
const eslintContent = fs.readFileSync(eslintConfigPath, "utf-8");
|
|
312
|
+
const parentDir = path2.basename(path2.dirname(toolDir));
|
|
313
|
+
const parentIgnorePattern = new RegExp(`["']${parentDir}\\/\\*\\*["']`);
|
|
314
|
+
const hasParentIgnore = parentIgnorePattern.test(eslintContent);
|
|
315
|
+
if (hasParentIgnore) {
|
|
316
|
+
pass(`ESLint ignores ${parentDir}/`);
|
|
317
|
+
} else {
|
|
318
|
+
fail(
|
|
319
|
+
`ESLint missing ignore: "${parentDir}/**"`,
|
|
320
|
+
`Add to the ignores array in eslint.config.mjs:
|
|
321
|
+
"${parentDir}/**",`
|
|
322
|
+
);
|
|
323
|
+
}
|
|
324
|
+
} else {
|
|
325
|
+
skip("ESLint config check (no eslint.config.mjs)");
|
|
326
|
+
}
|
|
327
|
+
} else {
|
|
328
|
+
skip("ESLint config check (typegraph-mcp is external to project)");
|
|
329
|
+
}
|
|
330
|
+
const gitignorePath = path2.resolve(projectRoot, ".gitignore");
|
|
331
|
+
if (fs.existsSync(gitignorePath)) {
|
|
332
|
+
const gitignoreContent = fs.readFileSync(gitignorePath, "utf-8");
|
|
333
|
+
const lines = gitignoreContent.split("\n").map((l) => l.trim()).filter((l) => l && !l.startsWith("#"));
|
|
334
|
+
const ignoresClaude = lines.some(
|
|
335
|
+
(l) => l === ".claude/" || l === ".claude" || l === "/.claude"
|
|
336
|
+
);
|
|
337
|
+
const parentDir = toolIsEmbedded ? path2.basename(path2.dirname(toolDir)) : null;
|
|
338
|
+
const ignoresParent = parentDir && lines.some((l) => l === `${parentDir}/` || l === parentDir || l === `/${parentDir}`);
|
|
339
|
+
if (!ignoresParent && !ignoresClaude) {
|
|
340
|
+
pass(".gitignore does not exclude .claude/" + (parentDir ? ` or ${parentDir}/` : ""));
|
|
341
|
+
} else {
|
|
342
|
+
const excluded = [];
|
|
343
|
+
if (ignoresParent) excluded.push(`${parentDir}/`);
|
|
344
|
+
if (ignoresClaude) excluded.push(".claude/");
|
|
345
|
+
warn(
|
|
346
|
+
`.gitignore excludes ${excluded.join(" and ")}`,
|
|
347
|
+
"Remove these entries so MCP config and tool source are tracked in git"
|
|
348
|
+
);
|
|
349
|
+
}
|
|
350
|
+
} else {
|
|
351
|
+
skip(".gitignore check (no .gitignore)");
|
|
352
|
+
}
|
|
353
|
+
console.log("");
|
|
354
|
+
const total = passed + failed;
|
|
355
|
+
if (failed === 0) {
|
|
356
|
+
console.log(
|
|
357
|
+
`${passed}/${total} checks passed` + (warned > 0 ? ` (${warned} warning${warned > 1 ? "s" : ""})` : "") + " -- typegraph-mcp is ready"
|
|
358
|
+
);
|
|
359
|
+
} else {
|
|
360
|
+
console.log(
|
|
361
|
+
`${passed}/${total} checks passed, ${failed} failed` + (warned > 0 ? `, ${warned} warning${warned > 1 ? "s" : ""}` : "") + " -- fix issues above"
|
|
362
|
+
);
|
|
363
|
+
}
|
|
364
|
+
console.log("");
|
|
365
|
+
return { passed, failed, warned };
|
|
366
|
+
}
|
|
367
|
+
var isDirectRun = process.argv[1] && fs.realpathSync(process.argv[1]) === fs.realpathSync(new URL(import.meta.url).pathname);
|
|
368
|
+
if (isDirectRun) {
|
|
369
|
+
main().then((result) => process.exit(result.failed > 0 ? 1 : 0)).catch((err) => {
|
|
370
|
+
console.error("Fatal error:", err);
|
|
371
|
+
process.exit(1);
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
export {
|
|
375
|
+
main
|
|
376
|
+
};
|