typegraph-mcp 0.9.37 → 0.9.39

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.
@@ -117,7 +117,7 @@ function distToSource(resolvedPath, projectRoot) {
117
117
  }
118
118
  return resolvedPath;
119
119
  }
120
- function resolveImport(resolver, fromDir, specifier, projectRoot) {
120
+ function resolveProjectImport(resolver, fromDir, specifier, projectRoot) {
121
121
  try {
122
122
  const result = resolver.sync(fromDir, specifier);
123
123
  if (result.path && !result.path.includes("node_modules")) {
@@ -168,7 +168,7 @@ function buildForwardEdges(files, resolver, projectRoot) {
168
168
  const edges = [];
169
169
  const fromDir = path.dirname(filePath);
170
170
  for (const raw of rawImports) {
171
- const target = resolveImport(resolver, fromDir, raw.specifier, projectRoot);
171
+ const target = resolveProjectImport(resolver, fromDir, raw.specifier, projectRoot);
172
172
  if (target) {
173
173
  edges.push({
174
174
  target,
@@ -249,7 +249,7 @@ function updateFile(graph, filePath, resolver, projectRoot) {
249
249
  const fromDir = path.dirname(filePath);
250
250
  const newEdges = [];
251
251
  for (const raw of rawImports) {
252
- const target = resolveImport(resolver, fromDir, raw.specifier, projectRoot);
252
+ const target = resolveProjectImport(resolver, fromDir, raw.specifier, projectRoot);
253
253
  if (target) {
254
254
  newEdges.push({
255
255
  target,
@@ -331,6 +331,7 @@ export {
331
331
  createResolver,
332
332
  discoverFiles,
333
333
  removeFile,
334
+ resolveProjectImport,
334
335
  startWatcher,
335
336
  updateFile
336
337
  };
package/dist/server.js CHANGED
@@ -2,6 +2,7 @@
2
2
  // server.ts
3
3
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
4
4
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
5
+ import { parseSync as parseSync2 } from "oxc-parser";
5
6
  import { z } from "zod";
6
7
 
7
8
  // tsserver-client.ts
@@ -387,7 +388,7 @@ function distToSource(resolvedPath, projectRoot2) {
387
388
  }
388
389
  return resolvedPath;
389
390
  }
390
- function resolveImport(resolver, fromDir, specifier, projectRoot2) {
391
+ function resolveProjectImport(resolver, fromDir, specifier, projectRoot2) {
391
392
  try {
392
393
  const result = resolver.sync(fromDir, specifier);
393
394
  if (result.path && !result.path.includes("node_modules")) {
@@ -438,7 +439,7 @@ function buildForwardEdges(files, resolver, projectRoot2) {
438
439
  const edges = [];
439
440
  const fromDir = path2.dirname(filePath);
440
441
  for (const raw of rawImports) {
441
- const target = resolveImport(resolver, fromDir, raw.specifier, projectRoot2);
442
+ const target = resolveProjectImport(resolver, fromDir, raw.specifier, projectRoot2);
442
443
  if (target) {
443
444
  edges.push({
444
445
  target,
@@ -519,7 +520,7 @@ function updateFile(graph, filePath, resolver, projectRoot2) {
519
520
  const fromDir = path2.dirname(filePath);
520
521
  const newEdges = [];
521
522
  for (const raw of rawImports) {
522
- const target = resolveImport(resolver, fromDir, raw.specifier, projectRoot2);
523
+ const target = resolveProjectImport(resolver, fromDir, raw.specifier, projectRoot2);
523
524
  if (target) {
524
525
  newEdges.push({
525
526
  target,
@@ -889,6 +890,7 @@ var { projectRoot, tsconfigPath } = resolveConfig(import.meta.dirname);
889
890
  var log3 = (...args) => console.error("[typegraph]", ...args);
890
891
  var client = new TsServerClient(projectRoot, tsconfigPath);
891
892
  var moduleGraph;
893
+ var moduleResolver;
892
894
  var mcpServer = new McpServer({
893
895
  name: "typegraph",
894
896
  version: "1.0.0"
@@ -960,6 +962,237 @@ async function resolveParams(params) {
960
962
  }
961
963
  return { error: "Either line+column or symbol must be provided" };
962
964
  }
965
+ var exportKinds = /* @__PURE__ */ new Set([
966
+ "function",
967
+ "const",
968
+ "class",
969
+ "interface",
970
+ "type",
971
+ "enum",
972
+ "var",
973
+ "let",
974
+ "method"
975
+ ]);
976
+ function exportPriority(source) {
977
+ switch (source) {
978
+ case "local":
979
+ return 3;
980
+ case "re-export":
981
+ return 2;
982
+ case "star-re-export":
983
+ return 1;
984
+ }
985
+ }
986
+ function exportKey(item) {
987
+ return `${item.symbol}:${item.exportKind}`;
988
+ }
989
+ function sameExportOrigin(a, b) {
990
+ return a.symbol === b.symbol && a.exportKind === b.exportKind && a.from === b.from && a.definedIn === b.definedIn && a.definedLine === b.definedLine;
991
+ }
992
+ function kindImpliesTypeOnly(kind) {
993
+ return kind === "type" || kind === "interface";
994
+ }
995
+ function normalizeExportKindLabel(kind, exportKind) {
996
+ if (exportKind === "type" && !kindImpliesTypeOnly(kind)) {
997
+ return "type";
998
+ }
999
+ return kind;
1000
+ }
1001
+ function upsertExport(map, conflicts, nextExport) {
1002
+ const key = exportKey(nextExport);
1003
+ if (conflicts.has(key)) {
1004
+ if (nextExport.source === "star-re-export") return;
1005
+ conflicts.delete(key);
1006
+ map.set(key, nextExport);
1007
+ return;
1008
+ }
1009
+ const existing = map.get(key);
1010
+ if (existing && existing.source === "star-re-export" && nextExport.source === "star-re-export" && !sameExportOrigin(existing, nextExport)) {
1011
+ map.delete(key);
1012
+ conflicts.add(key);
1013
+ return;
1014
+ }
1015
+ if (!existing || exportPriority(nextExport.source) > exportPriority(existing.source)) {
1016
+ map.set(key, nextExport);
1017
+ }
1018
+ }
1019
+ function offsetToLineColumn(source, offset) {
1020
+ const safeOffset = Math.max(0, Math.min(offset ?? 0, source.length));
1021
+ const prefix = source.slice(0, safeOffset);
1022
+ const lines = prefix.split("\n");
1023
+ return {
1024
+ line: lines.length,
1025
+ column: (lines.at(-1)?.length ?? 0) + 1
1026
+ };
1027
+ }
1028
+ function normalizeExistingPath(filePath) {
1029
+ const resolved = path5.resolve(filePath);
1030
+ try {
1031
+ return fs4.realpathSync.native(resolved);
1032
+ } catch {
1033
+ return resolved;
1034
+ }
1035
+ }
1036
+ var normalizedProjectRoot = normalizeExistingPath(projectRoot);
1037
+ function projectPath(file) {
1038
+ return path5.isAbsolute(file) ? relPath(file) : file;
1039
+ }
1040
+ function exportSymbol(entry) {
1041
+ if (entry.exportName.kind === "Default") return "default";
1042
+ return entry.exportName.name ?? entry.localName.name ?? entry.importName.name;
1043
+ }
1044
+ function exportLookupOffset(entry) {
1045
+ if (entry.moduleRequest) {
1046
+ return entry.importName.start ?? entry.exportName.start ?? entry.start;
1047
+ }
1048
+ if (entry.exportName.kind === "Default") {
1049
+ return entry.localName.start ?? entry.exportName.start ?? entry.start;
1050
+ }
1051
+ return entry.exportName.start ?? entry.localName.start ?? entry.start;
1052
+ }
1053
+ async function resolveExportMetadata(file, line, column, fallbackKind) {
1054
+ const defs = await client.definition(file, line, column);
1055
+ const def = defs[0] ?? null;
1056
+ let info = await client.quickinfo(file, line, column);
1057
+ if ((!info || info.kind === "alias") && def) {
1058
+ info = await client.quickinfo(def.file, def.start.line, def.start.offset) ?? info;
1059
+ }
1060
+ return {
1061
+ kind: info?.kind ?? fallbackKind,
1062
+ type: info?.displayString ?? null,
1063
+ definedIn: projectPath(def?.file ?? file),
1064
+ definedLine: def?.start.line ?? null
1065
+ };
1066
+ }
1067
+ async function getModuleExports(file, visited = /* @__PURE__ */ new Set()) {
1068
+ const relFile = path5.isAbsolute(file) ? relPath(file) : file;
1069
+ const absFile = normalizeExistingPath(client.resolvePath(relFile));
1070
+ if (visited.has(absFile)) return [];
1071
+ const nextVisited = new Set(visited);
1072
+ nextVisited.add(absFile);
1073
+ const exportMap = /* @__PURE__ */ new Map();
1074
+ const conflictingStarExports = /* @__PURE__ */ new Set();
1075
+ let source;
1076
+ try {
1077
+ source = fs4.readFileSync(absFile, "utf-8");
1078
+ } catch {
1079
+ return [...exportMap.values()];
1080
+ }
1081
+ let parsed;
1082
+ try {
1083
+ parsed = parseSync2(absFile, source);
1084
+ } catch {
1085
+ return [...exportMap.values()];
1086
+ }
1087
+ for (const exp of parsed.module.staticExports) {
1088
+ for (const entry of exp.entries) {
1089
+ const moduleRequest = entry.moduleRequest;
1090
+ if (!moduleRequest) continue;
1091
+ const targetFile = resolveProjectImport(
1092
+ moduleResolver,
1093
+ path5.dirname(absFile),
1094
+ moduleRequest.value,
1095
+ projectRoot
1096
+ );
1097
+ const exportLoc = offsetToLineColumn(
1098
+ source,
1099
+ entry.exportName.start ?? entry.localName.start ?? entry.importName.start ?? entry.start
1100
+ );
1101
+ const importKind = entry.importName.kind;
1102
+ const exportKind = entry.exportName.kind;
1103
+ if (importKind === "AllButDefault" && exportKind === "None") {
1104
+ if (!targetFile) continue;
1105
+ const nestedExports = await getModuleExports(targetFile, nextVisited);
1106
+ for (const nested of nestedExports) {
1107
+ if (nested.symbol === "default") continue;
1108
+ const starExportKind = entry.isType ? "type" : nested.exportKind;
1109
+ upsertExport(exportMap, conflictingStarExports, {
1110
+ ...nested,
1111
+ line: exportLoc.line,
1112
+ exportKind: starExportKind,
1113
+ isTypeOnly: starExportKind === "type",
1114
+ source: "star-re-export",
1115
+ from: relPath(targetFile)
1116
+ });
1117
+ }
1118
+ continue;
1119
+ }
1120
+ const symbol = exportSymbol(entry);
1121
+ if (!symbol) continue;
1122
+ const importedSymbol = importKind === "Default" ? "default" : importKind === "Name" ? entry.importName.name : null;
1123
+ const nestedMatch = targetFile && importedSymbol ? (await getModuleExports(targetFile, nextVisited)).find(
1124
+ (item) => item.symbol === importedSymbol
1125
+ ) ?? null : null;
1126
+ const lookupLoc = offsetToLineColumn(
1127
+ source,
1128
+ exportLookupOffset(entry)
1129
+ );
1130
+ const metadata = await resolveExportMetadata(
1131
+ relFile,
1132
+ lookupLoc.line,
1133
+ lookupLoc.column,
1134
+ importKind === "All" ? "namespace" : "alias"
1135
+ );
1136
+ const resolvedExportKind = entry.isType || nestedMatch?.exportKind === "type" || kindImpliesTypeOnly(nestedMatch?.kind ?? metadata.kind) ? "type" : "value";
1137
+ const resolvedKind = normalizeExportKindLabel(
1138
+ nestedMatch?.kind ?? metadata.kind,
1139
+ resolvedExportKind
1140
+ );
1141
+ upsertExport(exportMap, conflictingStarExports, {
1142
+ symbol,
1143
+ kind: resolvedKind,
1144
+ line: exportLoc.line,
1145
+ type: nestedMatch?.type ?? metadata.type,
1146
+ exportKind: resolvedExportKind,
1147
+ isTypeOnly: resolvedExportKind === "type",
1148
+ isNamespace: importKind === "All",
1149
+ source: "re-export",
1150
+ from: targetFile ? relPath(targetFile) : moduleRequest.value,
1151
+ definedIn: nestedMatch?.definedIn ?? metadata.definedIn,
1152
+ definedLine: nestedMatch?.definedLine ?? metadata.definedLine
1153
+ });
1154
+ continue;
1155
+ }
1156
+ for (const entry of exp.entries) {
1157
+ const moduleRequest = entry.moduleRequest;
1158
+ if (moduleRequest) continue;
1159
+ const symbol = exportSymbol(entry);
1160
+ if (!symbol) continue;
1161
+ const exportLoc = offsetToLineColumn(
1162
+ source,
1163
+ entry.exportName.start ?? entry.localName.start ?? entry.start
1164
+ );
1165
+ const lookupLoc = offsetToLineColumn(source, exportLookupOffset(entry));
1166
+ const metadata = await resolveExportMetadata(
1167
+ relFile,
1168
+ lookupLoc.line,
1169
+ lookupLoc.column,
1170
+ entry.isType ? "type" : "value"
1171
+ );
1172
+ const resolvedExportKind = entry.isType || kindImpliesTypeOnly(metadata.kind) ? "type" : "value";
1173
+ const resolvedKind = normalizeExportKindLabel(metadata.kind, resolvedExportKind);
1174
+ if (resolvedExportKind === "value" && symbol !== "default" && !exportKinds.has(resolvedKind) && resolvedKind !== "namespace" && resolvedKind !== "class") {
1175
+ continue;
1176
+ }
1177
+ upsertExport(exportMap, conflictingStarExports, {
1178
+ symbol,
1179
+ kind: resolvedKind,
1180
+ line: exportLoc.line,
1181
+ type: metadata.type,
1182
+ exportKind: resolvedExportKind,
1183
+ isTypeOnly: resolvedExportKind === "type",
1184
+ isNamespace: false,
1185
+ source: "local",
1186
+ from: null,
1187
+ definedIn: relFile,
1188
+ definedLine: resolvedExportKind === "type" ? exportLoc.line : metadata.definedLine
1189
+ });
1190
+ }
1191
+ }
1192
+ return [...exportMap.values()].sort(
1193
+ (a, b) => a.line - b.line || a.symbol.localeCompare(b.symbol)
1194
+ );
1195
+ }
963
1196
  mcpServer.tool(
964
1197
  "ts_find_symbol",
965
1198
  "Find a symbol's location in a file by name. Entry point for navigating without exact coordinates.",
@@ -1225,60 +1458,44 @@ mcpServer.tool(
1225
1458
  );
1226
1459
  mcpServer.tool(
1227
1460
  "ts_module_exports",
1228
- "List all exported symbols from a module with their resolved types. Gives an at-a-glance understanding of what a file provides.",
1461
+ "List all exported symbols from a module with their resolved types, including re-exports when possible. Gives an at-a-glance understanding of what a file provides.",
1229
1462
  {
1230
1463
  file: z.string().describe("File to inspect")
1231
1464
  },
1232
1465
  async ({ file }) => {
1233
- const bar = await client.navbar(file);
1234
- if (bar.length === 0) {
1235
- return {
1236
- content: [
1237
- {
1238
- type: "text",
1239
- text: JSON.stringify({ error: `No symbols found in ${file}` })
1240
- }
1241
- ]
1242
- };
1243
- }
1244
- const moduleItem = bar.find((item) => item.kind === "module");
1245
- const topItems = moduleItem?.childItems ?? bar;
1246
- const exportKinds = /* @__PURE__ */ new Set([
1247
- "function",
1248
- "const",
1249
- "class",
1250
- "interface",
1251
- "type",
1252
- "enum",
1253
- "var",
1254
- "let",
1255
- "method"
1256
- ]);
1257
- const candidates = topItems.filter((item) => exportKinds.has(item.kind));
1258
- const exports = [];
1259
- for (const item of candidates) {
1260
- if (!item.spans[0]) continue;
1261
- const span = item.spans[0];
1262
- const info = await client.quickinfo(file, span.start.line, span.start.offset);
1263
- exports.push({
1264
- symbol: item.text,
1265
- kind: item.kind,
1266
- line: span.start.line,
1267
- type: info?.displayString ?? null
1268
- });
1269
- }
1466
+ const exports = await getModuleExports(file);
1467
+ const localCount = exports.filter((item) => item.source === "local").length;
1468
+ const reExportCount = exports.length - localCount;
1469
+ const typeOnlyCount = exports.filter((item) => item.isTypeOnly).length;
1470
+ const valueCount = exports.length - typeOnlyCount;
1471
+ const namespaceExportCount = exports.filter((item) => item.isNamespace).length;
1472
+ const hasLocalRuntimeExports = exports.some(
1473
+ (item) => item.source === "local" && !item.isTypeOnly
1474
+ );
1475
+ const isPrimarilyBarrel = exports.length > 0 && localCount < reExportCount;
1270
1476
  return {
1271
1477
  content: [
1272
1478
  {
1273
1479
  type: "text",
1274
- text: JSON.stringify({ file, exports, count: exports.length })
1480
+ text: JSON.stringify({
1481
+ file,
1482
+ exports,
1483
+ count: exports.length,
1484
+ localCount,
1485
+ reExportCount,
1486
+ typeOnlyCount,
1487
+ valueCount,
1488
+ namespaceExportCount,
1489
+ hasLocalRuntimeExports,
1490
+ isPrimarilyBarrel
1491
+ })
1275
1492
  }
1276
1493
  ]
1277
1494
  };
1278
1495
  }
1279
1496
  );
1280
1497
  function relPath(absPath2) {
1281
- return path5.relative(projectRoot, absPath2);
1498
+ return path5.relative(normalizedProjectRoot, normalizeExistingPath(absPath2));
1282
1499
  }
1283
1500
  function absPath(file) {
1284
1501
  return path5.isAbsolute(file) ? file : path5.resolve(projectRoot, file);
@@ -1459,6 +1676,7 @@ async function main() {
1459
1676
  buildGraph(projectRoot, tsconfigPath)
1460
1677
  ]);
1461
1678
  moduleGraph = graphResult.graph;
1679
+ moduleResolver = graphResult.resolver;
1462
1680
  startWatcher(projectRoot, moduleGraph, graphResult.resolver);
1463
1681
  const transport = new StdioServerTransport();
1464
1682
  await mcpServer.connect(transport);
@@ -386,7 +386,7 @@ function distToSource(resolvedPath, projectRoot) {
386
386
  }
387
387
  return resolvedPath;
388
388
  }
389
- function resolveImport(resolver, fromDir, specifier, projectRoot) {
389
+ function resolveProjectImport(resolver, fromDir, specifier, projectRoot) {
390
390
  try {
391
391
  const result = resolver.sync(fromDir, specifier);
392
392
  if (result.path && !result.path.includes("node_modules")) {
@@ -437,7 +437,7 @@ function buildForwardEdges(files, resolver, projectRoot) {
437
437
  const edges = [];
438
438
  const fromDir = path2.dirname(filePath);
439
439
  for (const raw of rawImports) {
440
- const target = resolveImport(resolver, fromDir, raw.specifier, projectRoot);
440
+ const target = resolveProjectImport(resolver, fromDir, raw.specifier, projectRoot);
441
441
  if (target) {
442
442
  edges.push({
443
443
  target,
@@ -0,0 +1,202 @@
1
+ #!/usr/bin/env npx tsx
2
+
3
+ import * as assert from "node:assert/strict";
4
+ import * as fs from "node:fs";
5
+ import * as os from "node:os";
6
+ import * as path from "node:path";
7
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
8
+ import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
9
+ import { CallToolResultSchema } from "@modelcontextprotocol/sdk/types.js";
10
+
11
+ type ModuleExportRecord = {
12
+ symbol: string;
13
+ kind: string;
14
+ line: number;
15
+ type: string | null;
16
+ exportKind: "value" | "type";
17
+ isTypeOnly: boolean;
18
+ isNamespace: boolean;
19
+ source: "local" | "re-export" | "star-re-export";
20
+ from: string | null;
21
+ definedIn: string;
22
+ definedLine: number | null;
23
+ };
24
+
25
+ type ModuleExportsResult = {
26
+ file: string;
27
+ exports: ModuleExportRecord[];
28
+ count: number;
29
+ localCount: number;
30
+ reExportCount: number;
31
+ typeOnlyCount: number;
32
+ valueCount: number;
33
+ namespaceExportCount: number;
34
+ hasLocalRuntimeExports: boolean;
35
+ isPrimarilyBarrel: boolean;
36
+ };
37
+
38
+ function copyDir(src: string, dest: string): void {
39
+ fs.mkdirSync(dest, { recursive: true });
40
+ for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
41
+ const srcPath = path.join(src, entry.name);
42
+ const destPath = path.join(dest, entry.name);
43
+ if (entry.isDirectory()) {
44
+ copyDir(srcPath, destPath);
45
+ } else {
46
+ fs.copyFileSync(srcPath, destPath);
47
+ }
48
+ }
49
+ }
50
+
51
+ function findExport(
52
+ result: ModuleExportsResult,
53
+ symbol: string,
54
+ exportKind: "value" | "type"
55
+ ): ModuleExportRecord {
56
+ const found = result.exports.find(
57
+ (item) => item.symbol === symbol && item.exportKind === exportKind
58
+ );
59
+ assert.ok(found, `Expected ${symbol}:${exportKind} in ${result.file}`);
60
+ return found;
61
+ }
62
+
63
+ function assertProjectPath(actual: string | null, expected: string): void {
64
+ assert.ok(actual, `Expected path ${expected}`);
65
+ const normalized = actual.replaceAll("\\", "/");
66
+ assert.equal(normalized, expected, `Expected ${normalized} to equal ${expected}`);
67
+ }
68
+
69
+ async function main(): Promise<void> {
70
+ const repoRoot = import.meta.dirname;
71
+ const fixtureRoot = path.join(repoRoot, ".fixtures/export-surface");
72
+ const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "typegraph-export-surface-"));
73
+ const projectRoot = path.join(tempRoot, "project");
74
+
75
+ copyDir(fixtureRoot, projectRoot);
76
+ fs.mkdirSync(path.join(projectRoot, "node_modules"), { recursive: true });
77
+ fs.symlinkSync(
78
+ path.join(repoRoot, "node_modules/typescript"),
79
+ path.join(projectRoot, "node_modules/typescript"),
80
+ "dir"
81
+ );
82
+
83
+ const client = new Client({ name: "export-surface-test", version: "1.0.0" });
84
+ const transport = new StdioClientTransport({
85
+ command: path.join(repoRoot, "node_modules/.bin/tsx"),
86
+ args: [path.join(repoRoot, "server.ts")],
87
+ cwd: projectRoot,
88
+ env: {
89
+ TYPEGRAPH_PROJECT_ROOT: projectRoot,
90
+ TYPEGRAPH_TSCONFIG: path.join(projectRoot, "tsconfig.json"),
91
+ },
92
+ });
93
+
94
+ try {
95
+ await client.connect(transport);
96
+
97
+ async function moduleExports(file: string): Promise<ModuleExportsResult> {
98
+ const result = await client.request(
99
+ {
100
+ method: "tools/call",
101
+ params: {
102
+ name: "ts_module_exports",
103
+ arguments: { file },
104
+ },
105
+ },
106
+ CallToolResultSchema
107
+ );
108
+
109
+ const content = result.content[0];
110
+ assert.ok(content?.type === "text", `Expected text response for ${file}`);
111
+ return JSON.parse(content.text) as ModuleExportsResult;
112
+ }
113
+
114
+ const source = await moduleExports("src/source.ts");
115
+ const defaultExport = findExport(source, "default", "value");
116
+ assert.equal(defaultExport.source, "local");
117
+ assert.equal(defaultExport.kind, "function");
118
+ assert.equal(defaultExport.definedIn, "src/source.ts");
119
+ assert.equal(defaultExport.definedLine, 1);
120
+
121
+ const defaultExpression = await moduleExports("src/default-expression.ts");
122
+ const expressionDefault = findExport(defaultExpression, "default", "value");
123
+ assert.equal(expressionDefault.source, "local");
124
+ assert.equal(expressionDefault.definedIn, "src/default-expression.ts");
125
+
126
+ const barrel = await moduleExports("src/barrel.ts");
127
+ const barrelValue = findExport(barrel, "value", "value");
128
+ const barrelUserShape = findExport(barrel, "UserShape", "type");
129
+ assert.equal(barrelValue.source, "star-re-export");
130
+ assert.equal(barrelUserShape.source, "star-re-export");
131
+ assert.equal(barrel.exports.some((item) => item.symbol === "default"), false);
132
+ assert.equal(barrel.exports.some((item) => item.symbol === "buildUser"), false);
133
+ assert.equal(barrelUserShape.isTypeOnly, true);
134
+ assert.equal(barrel.typeOnlyCount, 2);
135
+ assert.equal(barrel.isPrimarilyBarrel, true);
136
+ assert.equal(barrel.hasLocalRuntimeExports, false);
137
+
138
+ const typeReExport = await moduleExports("src/reexport-type.ts");
139
+ const userShape = findExport(typeReExport, "UserShape", "type");
140
+ assert.equal(userShape.source, "re-export");
141
+ assert.equal(userShape.isTypeOnly, true);
142
+ assert.equal(userShape.exportKind, "type");
143
+ assert.equal(typeReExport.typeOnlyCount, 1);
144
+ assert.equal(typeReExport.valueCount, 0);
145
+
146
+ const namedReExport = await moduleExports("src/named-reexport.ts");
147
+ const aliasedValue = findExport(namedReExport, "aliasedValue", "value");
148
+ assert.equal(aliasedValue.source, "re-export");
149
+ assertProjectPath(aliasedValue.from, "src/source.ts");
150
+ assert.equal(aliasedValue.definedIn, "src/source.ts");
151
+ assert.equal(namedReExport.namespaceExportCount, 0);
152
+
153
+ const namespaceReExport = await moduleExports("src/namespace-reexport.ts");
154
+ const models = findExport(namespaceReExport, "Models", "value");
155
+ assert.equal(models.source, "re-export");
156
+ assert.equal(models.isNamespace, true);
157
+ assert.equal(namespaceReExport.namespaceExportCount, 1);
158
+
159
+ const mixed = await moduleExports("src/mixed.ts");
160
+ const localValue = findExport(mixed, "SessionId", "value");
161
+ const localType = findExport(mixed, "SessionId", "type");
162
+ const externalValue = findExport(mixed, "externalValue", "value");
163
+ const externalUserShape = findExport(mixed, "ExternalUserShape", "type");
164
+ assert.equal(localValue.source, "local");
165
+ assert.equal(localType.source, "local");
166
+ assert.equal(localType.kind, "type");
167
+ assert.equal(localType.definedLine, 2);
168
+ assert.equal(externalValue.source, "re-export");
169
+ assert.equal(externalUserShape.source, "re-export");
170
+ assert.equal(externalUserShape.isTypeOnly, true);
171
+ assert.equal(mixed.localCount, 2);
172
+ assert.equal(mixed.reExportCount, 2);
173
+ assert.equal(mixed.typeOnlyCount, 2);
174
+ assert.equal(mixed.hasLocalRuntimeExports, true);
175
+ assert.equal(mixed.isPrimarilyBarrel, false);
176
+
177
+ const collision = await moduleExports("src/collision-barrel.ts");
178
+ assert.equal(collision.exports.some((item) => item.symbol === "dup"), false);
179
+ assert.equal(collision.count, 0);
180
+
181
+ console.log("");
182
+ console.log("typegraph-mcp Export Surface Test");
183
+ console.log("=================================");
184
+ console.log(" ✓ local default exports are reported as default");
185
+ console.log(" ✓ anonymous default exports stay visible");
186
+ console.log(" ✓ barrel star re-exports");
187
+ console.log(" ✓ barrel star re-exports exclude default exports");
188
+ console.log(" ✓ type-only named re-exports");
189
+ console.log(" ✓ named alias re-exports");
190
+ console.log(" ✓ namespace re-exports");
191
+ console.log(" ✓ mixed local + re-export modules");
192
+ console.log(" ✓ conflicting star re-exports stay hidden");
193
+ } finally {
194
+ await transport.close().catch(() => {});
195
+ fs.rmSync(tempRoot, { recursive: true, force: true });
196
+ }
197
+ }
198
+
199
+ main().catch((err) => {
200
+ console.error(err);
201
+ process.exit(1);
202
+ });