@shvmgyl15/tsgraph 0.1.0

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.
Files changed (187) hide show
  1. package/AGENTS.md +64 -0
  2. package/README.md +128 -0
  3. package/TODOS.md +61 -0
  4. package/dist/analysis/analysis.test.d.ts +2 -0
  5. package/dist/analysis/analysis.test.d.ts.map +1 -0
  6. package/dist/analysis/analysis.test.js +359 -0
  7. package/dist/analysis/analysis.test.js.map +1 -0
  8. package/dist/analysis/complexity.d.ts +8 -0
  9. package/dist/analysis/complexity.d.ts.map +1 -0
  10. package/dist/analysis/complexity.js +88 -0
  11. package/dist/analysis/complexity.js.map +1 -0
  12. package/dist/analysis/coupling.d.ts +17 -0
  13. package/dist/analysis/coupling.d.ts.map +1 -0
  14. package/dist/analysis/coupling.js +71 -0
  15. package/dist/analysis/coupling.js.map +1 -0
  16. package/dist/analysis/hotspot.d.ts +10 -0
  17. package/dist/analysis/hotspot.d.ts.map +1 -0
  18. package/dist/analysis/hotspot.js +33 -0
  19. package/dist/analysis/hotspot.js.map +1 -0
  20. package/dist/analysis/index.d.ts +9 -0
  21. package/dist/analysis/index.d.ts.map +1 -0
  22. package/dist/analysis/index.js +5 -0
  23. package/dist/analysis/index.js.map +1 -0
  24. package/dist/boundaries/index.d.ts +25 -0
  25. package/dist/boundaries/index.d.ts.map +1 -0
  26. package/dist/boundaries/index.js +103 -0
  27. package/dist/boundaries/index.js.map +1 -0
  28. package/dist/boundaries/index.test.d.ts +2 -0
  29. package/dist/boundaries/index.test.d.ts.map +1 -0
  30. package/dist/boundaries/index.test.js +293 -0
  31. package/dist/boundaries/index.test.js.map +1 -0
  32. package/dist/changes/index.d.ts +28 -0
  33. package/dist/changes/index.d.ts.map +1 -0
  34. package/dist/changes/index.js +48 -0
  35. package/dist/changes/index.js.map +1 -0
  36. package/dist/changes/index.test.d.ts +2 -0
  37. package/dist/changes/index.test.d.ts.map +1 -0
  38. package/dist/changes/index.test.js +104 -0
  39. package/dist/changes/index.test.js.map +1 -0
  40. package/dist/cli/index.d.ts +3 -0
  41. package/dist/cli/index.d.ts.map +1 -0
  42. package/dist/cli/index.js +659 -0
  43. package/dist/cli/index.js.map +1 -0
  44. package/dist/git/index.d.ts +16 -0
  45. package/dist/git/index.d.ts.map +1 -0
  46. package/dist/git/index.js +73 -0
  47. package/dist/git/index.js.map +1 -0
  48. package/dist/git/index.test.d.ts +2 -0
  49. package/dist/git/index.test.d.ts.map +1 -0
  50. package/dist/git/index.test.js +78 -0
  51. package/dist/git/index.test.js.map +1 -0
  52. package/dist/graph/types.d.ts +156 -0
  53. package/dist/graph/types.d.ts.map +1 -0
  54. package/dist/graph/types.js +166 -0
  55. package/dist/graph/types.js.map +1 -0
  56. package/dist/graph/types.test.d.ts +2 -0
  57. package/dist/graph/types.test.d.ts.map +1 -0
  58. package/dist/graph/types.test.js +326 -0
  59. package/dist/graph/types.test.js.map +1 -0
  60. package/dist/mcp/mcp.test.d.ts +2 -0
  61. package/dist/mcp/mcp.test.d.ts.map +1 -0
  62. package/dist/mcp/mcp.test.js +151 -0
  63. package/dist/mcp/mcp.test.js.map +1 -0
  64. package/dist/mcp/server.d.ts +2 -0
  65. package/dist/mcp/server.d.ts.map +1 -0
  66. package/dist/mcp/server.js +209 -0
  67. package/dist/mcp/server.js.map +1 -0
  68. package/dist/nextjs/index.d.ts +8 -0
  69. package/dist/nextjs/index.d.ts.map +1 -0
  70. package/dist/nextjs/index.js +16 -0
  71. package/dist/nextjs/index.js.map +1 -0
  72. package/dist/nextjs/nextjs.test.d.ts +2 -0
  73. package/dist/nextjs/nextjs.test.d.ts.map +1 -0
  74. package/dist/nextjs/nextjs.test.js +190 -0
  75. package/dist/nextjs/nextjs.test.js.map +1 -0
  76. package/dist/nextjs/pages.d.ts +4 -0
  77. package/dist/nextjs/pages.d.ts.map +1 -0
  78. package/dist/nextjs/pages.js +36 -0
  79. package/dist/nextjs/pages.js.map +1 -0
  80. package/dist/nextjs/react.d.ts +3 -0
  81. package/dist/nextjs/react.d.ts.map +1 -0
  82. package/dist/nextjs/react.js +86 -0
  83. package/dist/nextjs/react.js.map +1 -0
  84. package/dist/nextjs/router.d.ts +4 -0
  85. package/dist/nextjs/router.d.ts.map +1 -0
  86. package/dist/nextjs/router.js +86 -0
  87. package/dist/nextjs/router.js.map +1 -0
  88. package/dist/nextjs/routes.d.ts +4 -0
  89. package/dist/nextjs/routes.d.ts.map +1 -0
  90. package/dist/nextjs/routes.js +58 -0
  91. package/dist/nextjs/routes.js.map +1 -0
  92. package/dist/opencode/index.d.ts +7 -0
  93. package/dist/opencode/index.d.ts.map +1 -0
  94. package/dist/opencode/index.js +71 -0
  95. package/dist/opencode/index.js.map +1 -0
  96. package/dist/opencode/index.test.d.ts +2 -0
  97. package/dist/opencode/index.test.d.ts.map +1 -0
  98. package/dist/opencode/index.test.js +71 -0
  99. package/dist/opencode/index.test.js.map +1 -0
  100. package/dist/parser/index.d.ts +4 -0
  101. package/dist/parser/index.d.ts.map +1 -0
  102. package/dist/parser/index.js +282 -0
  103. package/dist/parser/index.js.map +1 -0
  104. package/dist/parser/parser.test.d.ts +2 -0
  105. package/dist/parser/parser.test.d.ts.map +1 -0
  106. package/dist/parser/parser.test.js +225 -0
  107. package/dist/parser/parser.test.js.map +1 -0
  108. package/dist/plan/index.d.ts +32 -0
  109. package/dist/plan/index.d.ts.map +1 -0
  110. package/dist/plan/index.js +107 -0
  111. package/dist/plan/index.js.map +1 -0
  112. package/dist/plan/index.test.d.ts +2 -0
  113. package/dist/plan/index.test.d.ts.map +1 -0
  114. package/dist/plan/index.test.js +143 -0
  115. package/dist/plan/index.test.js.map +1 -0
  116. package/dist/report/index.d.ts +9 -0
  117. package/dist/report/index.d.ts.map +1 -0
  118. package/dist/report/index.js +108 -0
  119. package/dist/report/index.js.map +1 -0
  120. package/dist/scanner/index.d.ts +13 -0
  121. package/dist/scanner/index.d.ts.map +1 -0
  122. package/dist/scanner/index.js +78 -0
  123. package/dist/scanner/index.js.map +1 -0
  124. package/dist/scanner/scanner.test.d.ts +2 -0
  125. package/dist/scanner/scanner.test.d.ts.map +1 -0
  126. package/dist/scanner/scanner.test.js +113 -0
  127. package/dist/scanner/scanner.test.js.map +1 -0
  128. package/dist/search/index.d.ts +32 -0
  129. package/dist/search/index.d.ts.map +1 -0
  130. package/dist/search/index.js +97 -0
  131. package/dist/search/index.js.map +1 -0
  132. package/dist/search/search.test.d.ts +2 -0
  133. package/dist/search/search.test.d.ts.map +1 -0
  134. package/dist/search/search.test.js +446 -0
  135. package/dist/search/search.test.js.map +1 -0
  136. package/dist/traversal/index.d.ts +5 -0
  137. package/dist/traversal/index.d.ts.map +1 -0
  138. package/dist/traversal/index.js +3 -0
  139. package/dist/traversal/index.js.map +1 -0
  140. package/dist/traversal/traversal.d.ts +31 -0
  141. package/dist/traversal/traversal.d.ts.map +1 -0
  142. package/dist/traversal/traversal.js +130 -0
  143. package/dist/traversal/traversal.js.map +1 -0
  144. package/dist/traversal/traversal.test.d.ts +2 -0
  145. package/dist/traversal/traversal.test.d.ts.map +1 -0
  146. package/dist/traversal/traversal.test.js +224 -0
  147. package/dist/traversal/traversal.test.js.map +1 -0
  148. package/opencode.json +24 -0
  149. package/package.json +29 -0
  150. package/src/analysis/analysis.test.ts +405 -0
  151. package/src/analysis/complexity.ts +107 -0
  152. package/src/analysis/coupling.ts +106 -0
  153. package/src/analysis/hotspot.ts +52 -0
  154. package/src/analysis/index.ts +17 -0
  155. package/src/boundaries/index.test.ts +335 -0
  156. package/src/boundaries/index.ts +137 -0
  157. package/src/changes/index.test.ts +114 -0
  158. package/src/changes/index.ts +95 -0
  159. package/src/cli/index.ts +736 -0
  160. package/src/git/index.test.ts +92 -0
  161. package/src/git/index.ts +86 -0
  162. package/src/graph/types.test.ts +383 -0
  163. package/src/graph/types.ts +353 -0
  164. package/src/mcp/mcp.test.ts +176 -0
  165. package/src/mcp/server.ts +217 -0
  166. package/src/nextjs/index.ts +23 -0
  167. package/src/nextjs/nextjs.test.ts +233 -0
  168. package/src/nextjs/pages.ts +43 -0
  169. package/src/nextjs/react.ts +100 -0
  170. package/src/nextjs/router.ts +102 -0
  171. package/src/nextjs/routes.ts +69 -0
  172. package/src/opencode/index.test.ts +90 -0
  173. package/src/opencode/index.ts +83 -0
  174. package/src/parser/index.ts +339 -0
  175. package/src/parser/parser.test.ts +282 -0
  176. package/src/plan/index.test.ts +162 -0
  177. package/src/plan/index.ts +161 -0
  178. package/src/report/index.ts +128 -0
  179. package/src/scanner/index.ts +97 -0
  180. package/src/scanner/scanner.test.ts +135 -0
  181. package/src/search/index.ts +163 -0
  182. package/src/search/search.test.ts +512 -0
  183. package/src/traversal/index.ts +5 -0
  184. package/src/traversal/traversal.test.ts +266 -0
  185. package/src/traversal/traversal.ts +185 -0
  186. package/tsconfig.json +20 -0
  187. package/vitest.config.ts +7 -0
@@ -0,0 +1,209 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
4
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
5
+ import { z } from "zod/v4";
6
+ import { deserialize } from "../graph/types.js";
7
+ import { findCallers, findCallees, findNode, querySymbols, findImports, findPublic, context } from "../search/index.js";
8
+ import { impact, findPath, findOrphans, trace } from "../traversal/index.js";
9
+ import { analyzeComplexity, findHotspots, analyzeCoupling } from "../analysis/index.js";
10
+ function loadGraph(rootDir) {
11
+ const graphPath = path.join(rootDir, ".tsgraph", "graph.json");
12
+ const raw = fs.readFileSync(graphPath, "utf-8");
13
+ return deserialize(raw);
14
+ }
15
+ function text(text) {
16
+ return { content: [{ type: "text", text }] };
17
+ }
18
+ function error(msg) {
19
+ return { content: [{ type: "text", text: msg }], isError: true };
20
+ }
21
+ function withGraph(rootDir, fn) {
22
+ try {
23
+ const graph = loadGraph(rootDir);
24
+ return fn(graph);
25
+ }
26
+ catch (err) {
27
+ throw new Error(`Failed to load graph: ${err.message}`);
28
+ }
29
+ }
30
+ export async function startMcpServer(rootDir) {
31
+ const server = new McpServer({
32
+ name: "tsgraph",
33
+ version: "0.1.0",
34
+ });
35
+ server.resource("graph.json", `file://${path.join(rootDir, ".tsgraph", "graph.json")}`, { mimeType: "application/json" }, async (uri) => ({
36
+ contents: [{
37
+ uri: uri.href,
38
+ mimeType: "application/json",
39
+ text: fs.readFileSync(new URL(uri.href), "utf-8"),
40
+ }],
41
+ }));
42
+ server.registerTool("callers", {
43
+ description: "Find which functions call a given symbol",
44
+ inputSchema: z.object({ symbol: z.string() }),
45
+ }, async ({ symbol }) => {
46
+ try {
47
+ return text(JSON.stringify(withGraph(rootDir, (g) => findCallers(g, symbol)), null, 2));
48
+ }
49
+ catch (err) {
50
+ return error(err.message);
51
+ }
52
+ });
53
+ server.registerTool("callees", {
54
+ description: "Find which functions a given symbol calls",
55
+ inputSchema: z.object({ symbol: z.string() }),
56
+ }, async ({ symbol }) => {
57
+ try {
58
+ return text(JSON.stringify(withGraph(rootDir, (g) => findCallees(g, symbol)), null, 2));
59
+ }
60
+ catch (err) {
61
+ return error(err.message);
62
+ }
63
+ });
64
+ server.registerTool("node", {
65
+ description: "Get detailed information about a symbol",
66
+ inputSchema: z.object({ symbol: z.string() }),
67
+ }, async ({ symbol }) => {
68
+ try {
69
+ return text(JSON.stringify(withGraph(rootDir, (g) => findNode(g, symbol)), null, 2));
70
+ }
71
+ catch (err) {
72
+ return error(err.message);
73
+ }
74
+ });
75
+ server.registerTool("query", {
76
+ description: "Search for symbols matching a pattern",
77
+ inputSchema: z.object({ pattern: z.string() }),
78
+ }, async ({ pattern }) => {
79
+ try {
80
+ return text(JSON.stringify(withGraph(rootDir, (g) => querySymbols(g, pattern)), null, 2));
81
+ }
82
+ catch (err) {
83
+ return error(err.message);
84
+ }
85
+ });
86
+ server.registerTool("context", {
87
+ description: "Bundle node, source, callers, and callees for a symbol",
88
+ inputSchema: z.object({ symbol: z.string() }),
89
+ }, async ({ symbol }) => {
90
+ try {
91
+ return text(JSON.stringify(withGraph(rootDir, (g) => context(g, symbol)), null, 2));
92
+ }
93
+ catch (err) {
94
+ return error(err.message);
95
+ }
96
+ });
97
+ server.registerTool("imports", {
98
+ description: "Find all files importing a specific package path",
99
+ inputSchema: z.object({ path: z.string() }),
100
+ }, async ({ path: importPath }) => {
101
+ try {
102
+ return text(JSON.stringify(withGraph(rootDir, (g) => findImports(g, importPath)), null, 2));
103
+ }
104
+ catch (err) {
105
+ return error(err.message);
106
+ }
107
+ });
108
+ server.registerTool("public", {
109
+ description: "List exported symbols, optionally scoped to a package",
110
+ inputSchema: z.object({ package: z.string().optional() }),
111
+ }, async ({ package: pkg }) => {
112
+ try {
113
+ return text(JSON.stringify(withGraph(rootDir, (g) => findPublic(g, pkg)), null, 2));
114
+ }
115
+ catch (err) {
116
+ return error(err.message);
117
+ }
118
+ });
119
+ server.registerTool("impact", {
120
+ description: "Show downstream blast radius (callers recursively)",
121
+ inputSchema: z.object({ symbol: z.string(), depth: z.number().optional() }),
122
+ }, async ({ symbol, depth }) => {
123
+ try {
124
+ return text(JSON.stringify(withGraph(rootDir, (g) => impact(g, symbol, depth)), null, 2));
125
+ }
126
+ catch (err) {
127
+ return error(err.message);
128
+ }
129
+ });
130
+ server.registerTool("path", {
131
+ description: "Find shortest call path between two symbols",
132
+ inputSchema: z.object({ from: z.string(), to: z.string(), depth: z.number().optional() }),
133
+ }, async ({ from, to, depth }) => {
134
+ try {
135
+ return text(JSON.stringify(withGraph(rootDir, (g) => findPath(g, from, to, depth)), null, 2));
136
+ }
137
+ catch (err) {
138
+ return error(err.message);
139
+ }
140
+ });
141
+ server.registerTool("orphans", {
142
+ description: "Find dead code — symbols with no callers or tests",
143
+ inputSchema: z.object({}),
144
+ }, async () => {
145
+ try {
146
+ return text(JSON.stringify(withGraph(rootDir, (g) => findOrphans(g)), null, 2));
147
+ }
148
+ catch (err) {
149
+ return error(err.message);
150
+ }
151
+ });
152
+ server.registerTool("trace", {
153
+ description: "Find a string literal across symbols and trace callers upstream",
154
+ inputSchema: z.object({ string: z.string(), depth: z.number().optional() }),
155
+ }, async ({ string: searchStr, depth }) => {
156
+ try {
157
+ return text(JSON.stringify(withGraph(rootDir, (g) => trace(g, searchStr, depth)), null, 2));
158
+ }
159
+ catch (err) {
160
+ return error(err.message);
161
+ }
162
+ });
163
+ server.registerTool("complexity", {
164
+ description: "Show cyclomatic complexity for functions and methods",
165
+ inputSchema: z.object({ file: z.string().optional(), sort: z.boolean().optional(), min: z.number().optional() }),
166
+ }, async ({ file, sort, min }) => {
167
+ try {
168
+ const graph = loadGraph(rootDir);
169
+ let results = analyzeComplexity(graph, file);
170
+ if (min)
171
+ results = results.filter((r) => r.complexity >= min);
172
+ if (sort)
173
+ results.sort((a, b) => b.complexity - a.complexity);
174
+ return text(JSON.stringify(results, null, 2));
175
+ }
176
+ catch (err) {
177
+ return error(err.message);
178
+ }
179
+ });
180
+ server.registerTool("hotspot", {
181
+ description: "Rank files by complexity × size (hotness score)",
182
+ inputSchema: z.object({ top: z.number().optional() }),
183
+ }, async ({ top }) => {
184
+ try {
185
+ return text(JSON.stringify(withGraph(rootDir, (g) => findHotspots(g, top)), null, 2));
186
+ }
187
+ catch (err) {
188
+ return error(err.message);
189
+ }
190
+ });
191
+ server.registerTool("coupling", {
192
+ description: "Show package coupling based on import edges",
193
+ inputSchema: z.object({ package: z.string().optional() }),
194
+ }, async ({ package: pkg }) => {
195
+ try {
196
+ const graph = loadGraph(rootDir);
197
+ let results = analyzeCoupling(graph);
198
+ if (pkg)
199
+ results = results.filter((r) => r.packageName === pkg);
200
+ return text(JSON.stringify(results, null, 2));
201
+ }
202
+ catch (err) {
203
+ return error(err.message);
204
+ }
205
+ });
206
+ const transport = new StdioServerTransport();
207
+ await server.connect(transport);
208
+ }
209
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/mcp/server.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ import type { Graph } from "../graph/types.js";
2
+ import type { ScannedFile } from "../scanner/index.js";
3
+ export { extractAppRouter } from "./router.js";
4
+ export { extractPagesRouter } from "./pages.js";
5
+ export { classifyReactComponents } from "./react.js";
6
+ export { extractAPIRoutes } from "./routes.js";
7
+ export declare function extractNextJs(graph: Graph, rootDir: string, scanned: ScannedFile[]): Graph;
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/nextjs/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAMvD,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAChD,OAAO,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAC;AACrD,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAE/C,wBAAgB,aAAa,CAC3B,KAAK,EAAE,KAAK,EACZ,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,WAAW,EAAE,GACrB,KAAK,CAMP"}
@@ -0,0 +1,16 @@
1
+ import { extractAppRouter } from "./router.js";
2
+ import { extractPagesRouter } from "./pages.js";
3
+ import { classifyReactComponents } from "./react.js";
4
+ import { extractAPIRoutes } from "./routes.js";
5
+ export { extractAppRouter } from "./router.js";
6
+ export { extractPagesRouter } from "./pages.js";
7
+ export { classifyReactComponents } from "./react.js";
8
+ export { extractAPIRoutes } from "./routes.js";
9
+ export function extractNextJs(graph, rootDir, scanned) {
10
+ let g = extractAppRouter(graph, scanned);
11
+ g = extractPagesRouter(g, scanned);
12
+ g = classifyReactComponents(g, rootDir);
13
+ g = extractAPIRoutes(g, scanned, rootDir);
14
+ return g;
15
+ }
16
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/nextjs/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAChD,OAAO,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAC;AACrD,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAE/C,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAChD,OAAO,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAC;AACrD,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAE/C,MAAM,UAAU,aAAa,CAC3B,KAAY,EACZ,OAAe,EACf,OAAsB;IAEtB,IAAI,CAAC,GAAG,gBAAgB,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;IACzC,CAAC,GAAG,kBAAkB,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;IACnC,CAAC,GAAG,uBAAuB,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;IACxC,CAAC,GAAG,gBAAgB,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;IAC1C,OAAO,CAAC,CAAC;AACX,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=nextjs.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nextjs.test.d.ts","sourceRoot":"","sources":["../../src/nextjs/nextjs.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,190 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import path from "node:path";
3
+ import fs from "node:fs";
4
+ import os from "node:os";
5
+ import { scanFiles } from "../scanner/index.js";
6
+ import { parseProject } from "../parser/index.js";
7
+ import { extractAppRouter } from "./router.js";
8
+ import { extractPagesRouter } from "./pages.js";
9
+ import { extractAPIRoutes } from "./routes.js";
10
+ import { makeGraph, makeFileNode } from "../graph/types.js";
11
+ function createTempDir() {
12
+ return fs.mkdtempSync(path.join(os.tmpdir(), "tsgraph-test-"));
13
+ }
14
+ function writeFile(dir, relativePath, content) {
15
+ const fullPath = path.join(dir, relativePath);
16
+ fs.mkdirSync(path.dirname(fullPath), { recursive: true });
17
+ fs.writeFileSync(fullPath, content, "utf-8");
18
+ }
19
+ describe("extractAppRouter", () => {
20
+ it("builds tree with page, layout, loading", () => {
21
+ const dir = createTempDir();
22
+ writeFile(dir, "app/page.tsx", "export default function Home() {}");
23
+ writeFile(dir, "app/layout.tsx", "export default function RootLayout() {}");
24
+ writeFile(dir, "app/loading.tsx", "export default function Loading() {}");
25
+ const { files } = scanFiles(dir);
26
+ const graph = makeGraph({ root: dir, files: files.map((f) => makeFileNode({ path: f.relativePath })) });
27
+ const result = extractAppRouter(graph, files);
28
+ expect(result.appRouter).toBeTruthy();
29
+ expect(result.appRouter.path).toBe("/");
30
+ expect(result.appRouter.files.page).toMatch(/app\/page\.tsx$/);
31
+ expect(result.appRouter.files.layout).toMatch(/app\/layout\.tsx$/);
32
+ expect(result.appRouter.files.loading).toMatch(/app\/loading\.tsx$/);
33
+ expect(result.appRouter.files.error).toBeUndefined();
34
+ fs.rmSync(dir, { recursive: true });
35
+ });
36
+ it("detects nested route segments", () => {
37
+ const dir = createTempDir();
38
+ writeFile(dir, "app/layout.tsx", "export default function Root() {}");
39
+ writeFile(dir, "app/dashboard/page.tsx", "export default function Dashboard() {}");
40
+ writeFile(dir, "app/dashboard/layout.tsx", "export default function DashLayout() {}");
41
+ writeFile(dir, "app/dashboard/settings/page.tsx", "export default function Settings() {}");
42
+ const { files } = scanFiles(dir);
43
+ const graph = makeGraph({ root: dir, files: files.map((f) => makeFileNode({ path: f.relativePath })) });
44
+ const result = extractAppRouter(graph, files);
45
+ expect(result.appRouter).toBeTruthy();
46
+ expect(result.appRouter.children).toHaveLength(1);
47
+ expect(result.appRouter.children[0].path).toBe("/dashboard");
48
+ expect(result.appRouter.children[0].files.page).toMatch(/dashboard\/page\.tsx$/);
49
+ expect(result.appRouter.children[0].children).toHaveLength(1);
50
+ expect(result.appRouter.children[0].children[0].path).toBe("/dashboard/settings");
51
+ fs.rmSync(dir, { recursive: true });
52
+ });
53
+ it("returns undefined when no app directory", () => {
54
+ const dir = createTempDir();
55
+ writeFile(dir, "src/index.ts", "const x = 1;");
56
+ const { files } = scanFiles(dir);
57
+ const graph = makeGraph({ root: dir });
58
+ const result = extractAppRouter(graph, files);
59
+ expect(result.appRouter).toBeUndefined();
60
+ fs.rmSync(dir, { recursive: true });
61
+ });
62
+ });
63
+ describe("extractPagesRouter", () => {
64
+ it("detects index and nested pages", () => {
65
+ const dir = createTempDir();
66
+ writeFile(dir, "pages/index.tsx", "export default function Home() {}");
67
+ writeFile(dir, "pages/about.tsx", "export default function About() {}");
68
+ writeFile(dir, "pages/blog/[slug].tsx", "export default function Post() {}");
69
+ const { files } = scanFiles(dir);
70
+ const graph = makeGraph({ root: dir });
71
+ const result = extractPagesRouter(graph, files);
72
+ expect(result.routes).toHaveLength(3);
73
+ const paths = result.routes.map((r) => r.path).sort();
74
+ expect(paths).toEqual(["/", "/about", "/blog/:slug"]);
75
+ expect(result.routes[0].method).toBe("GET");
76
+ fs.rmSync(dir, { recursive: true });
77
+ });
78
+ it("ignores non-page files", () => {
79
+ const dir = createTempDir();
80
+ writeFile(dir, "pages/api/users.ts", "export default function handler() {}");
81
+ writeFile(dir, "pages/_app.tsx", "export default function App() {}");
82
+ writeFile(dir, "pages/_document.tsx", "");
83
+ const { files } = scanFiles(dir);
84
+ const graph = makeGraph({ root: dir });
85
+ const result = extractPagesRouter(graph, files);
86
+ const paths = result.routes.map((r) => r.path);
87
+ expect(paths).toContain("/api/users");
88
+ fs.rmSync(dir, { recursive: true });
89
+ });
90
+ });
91
+ describe("classifyReactComponents", () => {
92
+ it("sets isClientComponent from 'use client' directive", () => {
93
+ const dir = createTempDir();
94
+ writeFile(dir, "component.tsx", `"use client";
95
+ export function Button() { return null; }`);
96
+ const { files } = scanFiles(dir);
97
+ const graph = parseProject(dir, files);
98
+ const button = graph.symbols.find((s) => s.name === "Button");
99
+ expect(button).toBeTruthy();
100
+ expect(button.isClientComponent).toBe(true);
101
+ fs.rmSync(dir, { recursive: true });
102
+ });
103
+ it("sets isServerComponent from 'use server' directive", () => {
104
+ const dir = createTempDir();
105
+ writeFile(dir, "action.ts", `"use server";
106
+ export async function submit() { return null; }`);
107
+ const { files } = scanFiles(dir);
108
+ const graph = parseProject(dir, files);
109
+ const submit = graph.symbols.find((s) => s.name === "submit");
110
+ expect(submit).toBeTruthy();
111
+ expect(submit.isServerComponent).toBe(true);
112
+ fs.rmSync(dir, { recursive: true });
113
+ });
114
+ it("detects hooks in tsx functions", () => {
115
+ const dir = createTempDir();
116
+ writeFile(dir, "counter.tsx", `
117
+ import { useState } from "react";
118
+ export function Counter() {
119
+ const [count, setCount] = useState(0);
120
+ return null;
121
+ }`);
122
+ const { files } = scanFiles(dir);
123
+ const graph = parseProject(dir, files);
124
+ const counter = graph.symbols.find((s) => s.name === "Counter");
125
+ expect(counter).toBeTruthy();
126
+ expect(counter.isClientComponent).toBe(true);
127
+ fs.rmSync(dir, { recursive: true });
128
+ });
129
+ });
130
+ describe("extractAPIRoutes", () => {
131
+ it("extracts HTTP methods from route.ts", () => {
132
+ const dir = createTempDir();
133
+ writeFile(dir, "app/api/users/route.ts", `
134
+ export async function GET() { return Response.json({}); }
135
+ export async function POST(req: Request) { return Response.json({}); }
136
+ `);
137
+ const { files } = scanFiles(dir);
138
+ const graph = makeGraph({ root: dir });
139
+ const result = extractAPIRoutes(graph, files, dir);
140
+ const routes = result.routes.sort((a, b) => a.method.localeCompare(b.method));
141
+ expect(routes).toHaveLength(2);
142
+ expect(routes[0].method).toBe("GET");
143
+ expect(routes[0].path).toBe("/api/users");
144
+ expect(routes[1].method).toBe("POST");
145
+ expect(routes[1].path).toBe("/api/users");
146
+ fs.rmSync(dir, { recursive: true });
147
+ });
148
+ it("handles dynamic route params", () => {
149
+ const dir = createTempDir();
150
+ writeFile(dir, "app/api/items/[id]/route.ts", `
151
+ export async function GET(req: Request, { params }: { params: { id: string } }) {
152
+ return Response.json({});
153
+ }
154
+ `);
155
+ const { files } = scanFiles(dir);
156
+ const graph = makeGraph({ root: dir });
157
+ const result = extractAPIRoutes(graph, files, dir);
158
+ expect(result.routes).toHaveLength(1);
159
+ expect(result.routes[0].path).toBe("/api/items/:id");
160
+ fs.rmSync(dir, { recursive: true });
161
+ });
162
+ });
163
+ describe("end-to-end: parseProject with Next.js", () => {
164
+ it("produces appRouter and routes from a Next.js project", () => {
165
+ const dir = createTempDir();
166
+ writeFile(dir, "package.json", JSON.stringify({ name: "my-app", dependencies: { "next": "^14" } }));
167
+ writeFile(dir, "app/layout.tsx", "export default function Root({ children }: { children: React.ReactNode }) { return null; }");
168
+ writeFile(dir, "app/page.tsx", `"use client";
169
+ import { useState } from "react";
170
+ export default function Home() {
171
+ const [count, setCount] = useState(0);
172
+ return null;
173
+ }`);
174
+ writeFile(dir, "app/api/hello/route.ts", "export async function GET() { return Response.json({}); }");
175
+ writeFile(dir, "pages/about.tsx", "export default function About() { return null; }");
176
+ const { files } = scanFiles(dir);
177
+ const graph = parseProject(dir, files);
178
+ expect(graph.appRouter).toBeTruthy();
179
+ expect(graph.appRouter.files.layout).toBeTruthy();
180
+ expect(graph.appRouter.files.page).toBeTruthy();
181
+ const home = graph.symbols.find((s) => s.name === "Home");
182
+ expect(home).toBeTruthy();
183
+ expect(home.isClientComponent).toBe(true);
184
+ const routePaths = graph.routes.map((r) => r.path);
185
+ expect(routePaths).toContain("/api/hello");
186
+ expect(routePaths).toContain("/about");
187
+ fs.rmSync(dir, { recursive: true });
188
+ });
189
+ });
190
+ //# sourceMappingURL=nextjs.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nextjs.test.js","sourceRoot":"","sources":["../../src/nextjs/nextjs.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ import type { Graph } from "../graph/types.js";
2
+ import type { ScannedFile } from "../scanner/index.js";
3
+ export declare function extractPagesRouter(graph: Graph, scanned: ScannedFile[]): Graph;
4
+ //# sourceMappingURL=pages.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pages.d.ts","sourceRoot":"","sources":["../../src/nextjs/pages.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,KAAK,EAAa,MAAM,mBAAmB,CAAC;AAC1D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAcvD,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,KAAK,EACZ,OAAO,EAAE,WAAW,EAAE,GACrB,KAAK,CAuBP"}
@@ -0,0 +1,36 @@
1
+ import path from "node:path";
2
+ function pagePathToRoute(relativePath) {
3
+ let p = relativePath
4
+ .replace(/^pages[/\\]/, "")
5
+ .replace(/\.[jt]sx?$/, "")
6
+ .replace(/index$/, "")
7
+ .replace(/\[\.\.\.(.+?)\]/, "*$1")
8
+ .replace(/\[(.+?)\]/g, ":$1");
9
+ if (!p.startsWith("/"))
10
+ p = "/" + p;
11
+ return p === "/" ? "/" : p.replace(/\/$/, "");
12
+ }
13
+ export function extractPagesRouter(graph, scanned) {
14
+ const newRoutes = [...graph.routes];
15
+ for (const sf of scanned) {
16
+ const rel = sf.relativePath;
17
+ if (!rel.startsWith("pages/") && !rel.startsWith("pages\\"))
18
+ continue;
19
+ const ext = path.extname(rel);
20
+ if (ext !== ".ts" && ext !== ".tsx" && ext !== ".js" && ext !== ".jsx")
21
+ continue;
22
+ const route = pagePathToRoute(rel);
23
+ if (!route)
24
+ continue;
25
+ const isApi = route.startsWith("/api");
26
+ newRoutes.push({
27
+ method: "GET",
28
+ path: route,
29
+ handler: isApi ? `pages${route}` : rel,
30
+ file: rel,
31
+ line: 1,
32
+ });
33
+ }
34
+ return { ...graph, routes: newRoutes };
35
+ }
36
+ //# sourceMappingURL=pages.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pages.js","sourceRoot":"","sources":["../../src/nextjs/pages.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAI7B,SAAS,eAAe,CAAC,YAAoB;IAC3C,IAAI,CAAC,GAAG,YAAY;SACjB,OAAO,CAAC,aAAa,EAAE,EAAE,CAAC;SAC1B,OAAO,CAAC,YAAY,EAAE,EAAE,CAAC;SACzB,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC;SACrB,OAAO,CAAC,iBAAiB,EAAE,KAAK,CAAC;SACjC,OAAO,CAAC,YAAY,EAAE,KAAK,CAAC,CAAC;IAEhC,IAAI,CAAC,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,CAAC,GAAG,GAAG,GAAG,CAAC,CAAC;IACpC,OAAO,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;AAChD,CAAC;AAED,MAAM,UAAU,kBAAkB,CAChC,KAAY,EACZ,OAAsB;IAEtB,MAAM,SAAS,GAAgB,CAAC,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC;IAEjD,KAAK,MAAM,EAAE,IAAI,OAAO,EAAE,CAAC;QACzB,MAAM,GAAG,GAAG,EAAE,CAAC,YAAY,CAAC;QAC5B,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,SAAS,CAAC;YAAE,SAAS;QACtE,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAC9B,IAAI,GAAG,KAAK,KAAK,IAAI,GAAG,KAAK,MAAM,IAAI,GAAG,KAAK,KAAK,IAAI,GAAG,KAAK,MAAM;YAAE,SAAS;QAEjF,MAAM,KAAK,GAAG,eAAe,CAAC,GAAG,CAAC,CAAC;QACnC,IAAI,CAAC,KAAK;YAAE,SAAS;QAErB,MAAM,KAAK,GAAG,KAAK,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;QACvC,SAAS,CAAC,IAAI,CAAC;YACb,MAAM,EAAE,KAAK;YACb,IAAI,EAAE,KAAK;YACX,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC,QAAQ,KAAK,EAAE,CAAC,CAAC,CAAC,GAAG;YACtC,IAAI,EAAE,GAAG;YACT,IAAI,EAAE,CAAC;SACR,CAAC,CAAC;IACL,CAAC;IAED,OAAO,EAAE,GAAG,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC;AACzC,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { Graph } from "../graph/types.js";
2
+ export declare function classifyReactComponents(graph: Graph, rootDir: string): Graph;
3
+ //# sourceMappingURL=react.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"react.d.ts","sourceRoot":"","sources":["../../src/nextjs/react.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,KAAK,EAAc,MAAM,mBAAmB,CAAC;AA4D3D,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,KAAK,EACZ,OAAO,EAAE,MAAM,GACd,KAAK,CAkCP"}
@@ -0,0 +1,86 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ const REACT_HOOKS = new Set([
4
+ "useState", "useEffect", "useContext", "useReducer", "useCallback",
5
+ "useMemo", "useRef", "useImperativeHandle", "useLayoutEffect",
6
+ "useDebugValue", "useTransition", "useDeferredValue", "useId",
7
+ "useSyncExternalStore", "useInsertionEffect", "useActionState",
8
+ "useOptimistic",
9
+ ]);
10
+ function getDirectives(filePath) {
11
+ try {
12
+ const fd = fs.openSync(filePath, "r");
13
+ const buffer = Buffer.alloc(512);
14
+ const bytesRead = fs.readSync(fd, buffer, 0, 512, 0);
15
+ fs.closeSync(fd);
16
+ const head = buffer.toString("utf-8", 0, bytesRead);
17
+ const lines = head.split("\n").slice(0, 5);
18
+ const trimmed = lines.map((l) => l.trim().replace(/;$/, ""));
19
+ return {
20
+ isClient: trimmed.some((l) => l === `"use client"` || l === `'use client'`),
21
+ isServer: trimmed.some((l) => l === `"use server"` || l === `'use server'`),
22
+ };
23
+ }
24
+ catch {
25
+ return { isClient: false, isServer: false };
26
+ }
27
+ }
28
+ function checkHookUsage(filePath, symbols) {
29
+ const updated = [];
30
+ try {
31
+ const content = fs.readFileSync(filePath, "utf-8");
32
+ for (const sym of symbols) {
33
+ const lines = content.split("\n");
34
+ const snippet = lines.slice(sym.line - 1, sym.endLine).join("\n");
35
+ const usesHooks = [...REACT_HOOKS].some((hook) => {
36
+ const idx = snippet.indexOf(hook);
37
+ if (idx === -1)
38
+ return false;
39
+ const before = snippet[idx - 1];
40
+ if (before && (before === "." || /[a-zA-Z0-9]/.test(before)))
41
+ return false;
42
+ return true;
43
+ });
44
+ if (usesHooks) {
45
+ updated.push({ ...sym, isClientComponent: true });
46
+ }
47
+ else {
48
+ updated.push(sym);
49
+ }
50
+ }
51
+ }
52
+ catch {
53
+ return symbols;
54
+ }
55
+ return updated;
56
+ }
57
+ export function classifyReactComponents(graph, rootDir) {
58
+ const symByFile = new Map();
59
+ for (const sym of graph.symbols) {
60
+ const list = symByFile.get(sym.file) ?? [];
61
+ list.push(sym);
62
+ symByFile.set(sym.file, list);
63
+ }
64
+ const updatedSymbols = [];
65
+ for (const [fileRel, symbols] of symByFile) {
66
+ const filePath = path.join(rootDir, fileRel);
67
+ const { isClient, isServer } = getDirectives(filePath);
68
+ if (isClient || isServer) {
69
+ updatedSymbols.push(...symbols.map((s) => ({
70
+ ...s,
71
+ isClientComponent: isClient || s.isClientComponent,
72
+ isServerComponent: isServer || s.isServerComponent,
73
+ })));
74
+ continue;
75
+ }
76
+ if (fileRel.endsWith(".tsx")) {
77
+ const checked = checkHookUsage(filePath, symbols);
78
+ updatedSymbols.push(...checked);
79
+ }
80
+ else {
81
+ updatedSymbols.push(...symbols);
82
+ }
83
+ }
84
+ return { ...graph, symbols: updatedSymbols };
85
+ }
86
+ //# sourceMappingURL=react.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"react.js","sourceRoot":"","sources":["../../src/nextjs/react.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAG7B,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC;IAC1B,UAAU,EAAE,WAAW,EAAE,YAAY,EAAE,YAAY,EAAE,aAAa;IAClE,SAAS,EAAE,QAAQ,EAAE,qBAAqB,EAAE,iBAAiB;IAC7D,eAAe,EAAE,eAAe,EAAE,kBAAkB,EAAE,OAAO;IAC7D,sBAAsB,EAAE,oBAAoB,EAAE,gBAAgB;IAC9D,eAAe;CAChB,CAAC,CAAC;AAEH,SAAS,aAAa,CAAC,QAAgB;IAIrC,IAAI,CAAC;QACH,MAAM,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;QACtC,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QACjC,MAAM,SAAS,GAAG,EAAE,CAAC,QAAQ,CAAC,EAAE,EAAE,MAAM,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC;QACrD,EAAE,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;QACjB,MAAM,IAAI,GAAG,MAAM,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAC,EAAE,SAAS,CAAC,CAAC;QACpD,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QAC3C,MAAM,OAAO,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC;QAC7D,OAAO;YACL,QAAQ,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,cAAc,IAAI,CAAC,KAAK,cAAc,CAAC;YAC3E,QAAQ,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,cAAc,IAAI,CAAC,KAAK,cAAc,CAAC;SAC5E,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;IAC9C,CAAC;AACH,CAAC;AAED,SAAS,cAAc,CACrB,QAAgB,EAChB,OAAqB;IAErB,MAAM,OAAO,GAAiB,EAAE,CAAC;IACjC,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,EAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QACnD,KAAK,MAAM,GAAG,IAAI,OAAO,EAAE,CAAC;YAC1B,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YAClC,MAAM,OAAO,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAClE,MAAM,SAAS,GAAG,CAAC,GAAG,WAAW,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE;gBAC/C,MAAM,GAAG,GAAG,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;gBAClC,IAAI,GAAG,KAAK,CAAC,CAAC;oBAAE,OAAO,KAAK,CAAC;gBAC7B,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC;gBAChC,IAAI,MAAM,IAAI,CAAC,MAAM,KAAK,GAAG,IAAI,aAAa,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;oBAAE,OAAO,KAAK,CAAC;gBAC3E,OAAO,IAAI,CAAC;YACd,CAAC,CAAC,CAAC;YACH,IAAI,SAAS,EAAE,CAAC;gBACd,OAAO,CAAC,IAAI,CAAC,EAAE,GAAG,GAAG,EAAE,iBAAiB,EAAE,IAAI,EAAE,CAAC,CAAC;YACpD,CAAC;iBAAM,CAAC;gBACN,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YACpB,CAAC;QACH,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,OAAO,CAAC;IACjB,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,MAAM,UAAU,uBAAuB,CACrC,KAAY,EACZ,OAAe;IAEf,MAAM,SAAS,GAAG,IAAI,GAAG,EAAwB,CAAC;IAClD,KAAK,MAAM,GAAG,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC;QAChC,MAAM,IAAI,GAAG,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;QAC3C,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACf,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IAChC,CAAC;IAED,MAAM,cAAc,GAAiB,EAAE,CAAC;IAExC,KAAK,MAAM,CAAC,OAAO,EAAE,OAAO,CAAC,IAAI,SAAS,EAAE,CAAC;QAC3C,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QAC7C,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,GAAG,aAAa,CAAC,QAAQ,CAAC,CAAC;QAEvD,IAAI,QAAQ,IAAI,QAAQ,EAAE,CAAC;YACzB,cAAc,CAAC,IAAI,CACjB,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;gBACrB,GAAG,CAAC;gBACJ,iBAAiB,EAAE,QAAQ,IAAI,CAAC,CAAC,iBAAiB;gBAClD,iBAAiB,EAAE,QAAQ,IAAI,CAAC,CAAC,iBAAiB;aACnD,CAAC,CAAC,CACJ,CAAC;YACF,SAAS;QACX,CAAC;QAED,IAAI,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;YAC7B,MAAM,OAAO,GAAG,cAAc,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;YAClD,cAAc,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,CAAC;QAClC,CAAC;aAAM,CAAC;YACN,cAAc,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,CAAC;QAClC,CAAC;IACH,CAAC;IAED,OAAO,EAAE,GAAG,KAAK,EAAE,OAAO,EAAE,cAAc,EAAE,CAAC;AAC/C,CAAC"}
@@ -0,0 +1,4 @@
1
+ import type { Graph } from "../graph/types.js";
2
+ import type { ScannedFile } from "../scanner/index.js";
3
+ export declare function extractAppRouter(graph: Graph, scanned: ScannedFile[]): Graph;
4
+ //# sourceMappingURL=router.d.ts.map