@mgamil/mapx 0.2.4

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 (203) hide show
  1. package/LICENSE +194 -0
  2. package/README.md +488 -0
  3. package/VERSION +1 -0
  4. package/dist/agents/generator.d.ts +74 -0
  5. package/dist/agents/generator.js +375 -0
  6. package/dist/agents/templates.d.ts +29 -0
  7. package/dist/agents/templates.js +459 -0
  8. package/dist/cli.d.ts +16 -0
  9. package/dist/cli.js +1835 -0
  10. package/dist/core/cluster-engine.d.ts +32 -0
  11. package/dist/core/cluster-engine.js +314 -0
  12. package/dist/core/config.d.ts +29 -0
  13. package/dist/core/config.js +178 -0
  14. package/dist/core/context-builder.d.ts +61 -0
  15. package/dist/core/context-builder.js +252 -0
  16. package/dist/core/flow-tracer.d.ts +63 -0
  17. package/dist/core/flow-tracer.js +366 -0
  18. package/dist/core/git-tracker.d.ts +20 -0
  19. package/dist/core/git-tracker.js +159 -0
  20. package/dist/core/graph.d.ts +42 -0
  21. package/dist/core/graph.js +186 -0
  22. package/dist/core/metrics.d.ts +24 -0
  23. package/dist/core/metrics.js +87 -0
  24. package/dist/core/scanner.d.ts +53 -0
  25. package/dist/core/scanner.js +949 -0
  26. package/dist/core/store-bun.d.ts +13 -0
  27. package/dist/core/store-bun.js +34 -0
  28. package/dist/core/store-interface.d.ts +15 -0
  29. package/dist/core/store-interface.js +7 -0
  30. package/dist/core/store-node.d.ts +13 -0
  31. package/dist/core/store-node.js +35 -0
  32. package/dist/core/store.d.ts +132 -0
  33. package/dist/core/store.js +614 -0
  34. package/dist/core/workspace-manager.d.ts +9 -0
  35. package/dist/core/workspace-manager.js +64 -0
  36. package/dist/exporters/dot-exporter.d.ts +16 -0
  37. package/dist/exporters/dot-exporter.js +179 -0
  38. package/dist/exporters/graph-exporter.d.ts +14 -0
  39. package/dist/exporters/graph-exporter.js +85 -0
  40. package/dist/exporters/index.d.ts +9 -0
  41. package/dist/exporters/index.js +12 -0
  42. package/dist/exporters/llm-exporter.d.ts +18 -0
  43. package/dist/exporters/llm-exporter.js +224 -0
  44. package/dist/exporters/svg-exporter.d.ts +19 -0
  45. package/dist/exporters/svg-exporter.js +319 -0
  46. package/dist/exporters/toon-exporter.d.ts +16 -0
  47. package/dist/exporters/toon-exporter.js +246 -0
  48. package/dist/frameworks/detectors/aspnet.d.ts +11 -0
  49. package/dist/frameworks/detectors/aspnet.js +52 -0
  50. package/dist/frameworks/detectors/django.d.ts +14 -0
  51. package/dist/frameworks/detectors/django.js +135 -0
  52. package/dist/frameworks/detectors/drupal.d.ts +13 -0
  53. package/dist/frameworks/detectors/drupal.js +94 -0
  54. package/dist/frameworks/detectors/express.d.ts +12 -0
  55. package/dist/frameworks/detectors/express.js +234 -0
  56. package/dist/frameworks/detectors/fastapi.d.ts +12 -0
  57. package/dist/frameworks/detectors/fastapi.js +203 -0
  58. package/dist/frameworks/detectors/flask.d.ts +12 -0
  59. package/dist/frameworks/detectors/flask.js +244 -0
  60. package/dist/frameworks/detectors/go.d.ts +11 -0
  61. package/dist/frameworks/detectors/go.js +75 -0
  62. package/dist/frameworks/detectors/laravel.d.ts +11 -0
  63. package/dist/frameworks/detectors/laravel.js +462 -0
  64. package/dist/frameworks/detectors/nestjs.d.ts +12 -0
  65. package/dist/frameworks/detectors/nestjs.js +155 -0
  66. package/dist/frameworks/detectors/nextjs.d.ts +11 -0
  67. package/dist/frameworks/detectors/nextjs.js +118 -0
  68. package/dist/frameworks/detectors/rails.d.ts +12 -0
  69. package/dist/frameworks/detectors/rails.js +76 -0
  70. package/dist/frameworks/detectors/react-router.d.ts +11 -0
  71. package/dist/frameworks/detectors/react-router.js +115 -0
  72. package/dist/frameworks/detectors/rust.d.ts +11 -0
  73. package/dist/frameworks/detectors/rust.js +59 -0
  74. package/dist/frameworks/detectors/spring.d.ts +11 -0
  75. package/dist/frameworks/detectors/spring.js +56 -0
  76. package/dist/frameworks/detectors/sveltekit.d.ts +11 -0
  77. package/dist/frameworks/detectors/sveltekit.js +154 -0
  78. package/dist/frameworks/detectors/symfony.d.ts +13 -0
  79. package/dist/frameworks/detectors/symfony.js +175 -0
  80. package/dist/frameworks/detectors/tanstack-router.d.ts +12 -0
  81. package/dist/frameworks/detectors/tanstack-router.js +80 -0
  82. package/dist/frameworks/detectors/vapor.d.ts +11 -0
  83. package/dist/frameworks/detectors/vapor.js +52 -0
  84. package/dist/frameworks/detectors/vue-router.d.ts +12 -0
  85. package/dist/frameworks/detectors/vue-router.js +237 -0
  86. package/dist/frameworks/detectors/wordpress.d.ts +13 -0
  87. package/dist/frameworks/detectors/wordpress.js +141 -0
  88. package/dist/frameworks/detectors/yii.d.ts +11 -0
  89. package/dist/frameworks/detectors/yii.js +131 -0
  90. package/dist/frameworks/framework-registry.d.ts +13 -0
  91. package/dist/frameworks/framework-registry.js +77 -0
  92. package/dist/frameworks/route-registry.d.ts +26 -0
  93. package/dist/frameworks/route-registry.js +102 -0
  94. package/dist/index.d.ts +19 -0
  95. package/dist/index.js +30 -0
  96. package/dist/languages/index.d.ts +2 -0
  97. package/dist/languages/index.js +7 -0
  98. package/dist/languages/installer.d.ts +13 -0
  99. package/dist/languages/installer.js +103 -0
  100. package/dist/languages/registry.d.ts +19 -0
  101. package/dist/languages/registry.js +427 -0
  102. package/dist/main.d.ts +2 -0
  103. package/dist/main.js +20 -0
  104. package/dist/mcp.d.ts +11 -0
  105. package/dist/mcp.js +1699 -0
  106. package/dist/parsers/common-methods.d.ts +3 -0
  107. package/dist/parsers/common-methods.js +33 -0
  108. package/dist/parsers/fallback-parser.d.ts +10 -0
  109. package/dist/parsers/fallback-parser.js +18 -0
  110. package/dist/parsers/generic-wasm-parser.d.ts +23 -0
  111. package/dist/parsers/generic-wasm-parser.js +168 -0
  112. package/dist/parsers/ignored-symbols.d.ts +26 -0
  113. package/dist/parsers/ignored-symbols.js +77 -0
  114. package/dist/parsers/index.d.ts +9 -0
  115. package/dist/parsers/index.js +13 -0
  116. package/dist/parsers/languages/javascript.d.ts +11 -0
  117. package/dist/parsers/languages/javascript.js +28 -0
  118. package/dist/parsers/languages/php.d.ts +15 -0
  119. package/dist/parsers/languages/php.js +648 -0
  120. package/dist/parsers/languages/typescript.d.ts +10 -0
  121. package/dist/parsers/languages/typescript.js +9 -0
  122. package/dist/parsers/languages/vue.d.ts +13 -0
  123. package/dist/parsers/languages/vue.js +63 -0
  124. package/dist/parsers/parse-worker.d.ts +2 -0
  125. package/dist/parsers/parse-worker.js +185 -0
  126. package/dist/parsers/parser-interface.d.ts +9 -0
  127. package/dist/parsers/parser-interface.js +0 -0
  128. package/dist/parsers/parser-registry.d.ts +8 -0
  129. package/dist/parsers/parser-registry.js +52 -0
  130. package/dist/parsers/wasm-parser.d.ts +16 -0
  131. package/dist/parsers/wasm-parser.js +110 -0
  132. package/dist/types.d.ts +172 -0
  133. package/dist/types.js +0 -0
  134. package/dist/ui/index.html +270 -0
  135. package/dist/ui/main.js +581 -0
  136. package/dist/ui/main.js.map +7 -0
  137. package/dist/ui/styles.css +573 -0
  138. package/dist/ui-events.d.ts +36 -0
  139. package/dist/ui-events.js +61 -0
  140. package/dist/ui-server.d.ts +12 -0
  141. package/dist/ui-server.js +504 -0
  142. package/package.json +179 -0
  143. package/queries/bash/references.scm +22 -0
  144. package/queries/bash/symbols.scm +15 -0
  145. package/queries/c/references.scm +14 -0
  146. package/queries/c/symbols.scm +30 -0
  147. package/queries/c-sharp/references.scm +26 -0
  148. package/queries/c-sharp/symbols.scm +57 -0
  149. package/queries/cpp/references.scm +21 -0
  150. package/queries/cpp/symbols.scm +44 -0
  151. package/queries/dart/references.scm +33 -0
  152. package/queries/dart/symbols.scm +38 -0
  153. package/queries/elixir/references.scm +45 -0
  154. package/queries/elixir/symbols.scm +41 -0
  155. package/queries/go/references.scm +22 -0
  156. package/queries/go/symbols.scm +53 -0
  157. package/queries/java/references.scm +32 -0
  158. package/queries/java/symbols.scm +41 -0
  159. package/queries/javascript/references.scm +14 -0
  160. package/queries/javascript/symbols.scm +23 -0
  161. package/queries/kotlin/references.scm +31 -0
  162. package/queries/kotlin/symbols.scm +24 -0
  163. package/queries/lua/references.scm +19 -0
  164. package/queries/lua/symbols.scm +29 -0
  165. package/queries/pascal/references.scm +29 -0
  166. package/queries/pascal/symbols.scm +45 -0
  167. package/queries/php/references.scm +109 -0
  168. package/queries/php/symbols.scm +33 -0
  169. package/queries/python/references.scm +50 -0
  170. package/queries/python/symbols.scm +21 -0
  171. package/queries/ruby/references.scm +48 -0
  172. package/queries/ruby/symbols.scm +24 -0
  173. package/queries/rust/references.scm +31 -0
  174. package/queries/rust/symbols.scm +35 -0
  175. package/queries/scala/references.scm +30 -0
  176. package/queries/scala/symbols.scm +35 -0
  177. package/queries/svelte/references.scm +20 -0
  178. package/queries/svelte/symbols.scm +30 -0
  179. package/queries/swift/references.scm +22 -0
  180. package/queries/swift/symbols.scm +37 -0
  181. package/queries/typescript/references.scm +25 -0
  182. package/queries/typescript/symbols.scm +35 -0
  183. package/queries/vue/references.scm +20 -0
  184. package/queries/vue/symbols.scm +28 -0
  185. package/queries/zig/references.scm +20 -0
  186. package/queries/zig/symbols.scm +22 -0
  187. package/wasm/tree-sitter-c.wasm +0 -0
  188. package/wasm/tree-sitter-c_sharp.wasm +0 -0
  189. package/wasm/tree-sitter-cpp.wasm +0 -0
  190. package/wasm/tree-sitter-dart.wasm +0 -0
  191. package/wasm/tree-sitter-go.wasm +0 -0
  192. package/wasm/tree-sitter-java.wasm +0 -0
  193. package/wasm/tree-sitter-javascript.wasm +0 -0
  194. package/wasm/tree-sitter-kotlin.wasm +0 -0
  195. package/wasm/tree-sitter-php.wasm +0 -0
  196. package/wasm/tree-sitter-python.wasm +0 -0
  197. package/wasm/tree-sitter-ruby.wasm +0 -0
  198. package/wasm/tree-sitter-rust.wasm +0 -0
  199. package/wasm/tree-sitter-scala.wasm +0 -0
  200. package/wasm/tree-sitter-swift.wasm +0 -0
  201. package/wasm/tree-sitter-tsx.wasm +0 -0
  202. package/wasm/tree-sitter-typescript.wasm +0 -0
  203. package/wasm/tree-sitter-vue.wasm +0 -0
@@ -0,0 +1,462 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ import { existsSync } from "node:fs";
4
+ class LaravelDetector {
5
+ name = "laravel";
6
+ language = "php";
7
+ filePattern = /\.php$/;
8
+ async detect(projectRoot, files) {
9
+ const hasArtisan = files.some((f) => f.endsWith("artisan"));
10
+ if (hasArtisan) return true;
11
+ const composerPath = join(projectRoot, "composer.json");
12
+ if (existsSync(composerPath)) {
13
+ try {
14
+ const composerContent = JSON.parse(await readFile(composerPath, "utf-8"));
15
+ const deps = { ...composerContent.require, ...composerContent["require-dev"] };
16
+ if (deps && deps["laravel/framework"]) {
17
+ return true;
18
+ }
19
+ } catch {
20
+ }
21
+ }
22
+ return false;
23
+ }
24
+ async extractRoutes(filePath, content, ctx) {
25
+ const normalizedPath = filePath.replace(/\\/g, "/");
26
+ const isRouteFile = normalizedPath.includes("routes/");
27
+ const hasRouteAttributes = content.includes("RouteAttributes") || content.includes("#[Route") || content.includes("#[Get") || content.includes("#[Post") || content.includes("#[Put") || content.includes("#[Patch") || content.includes("#[Delete") || content.includes("#[Options") || content.includes("#[Any") || content.includes("#[Head") || content.includes("#[Prefix") || content.includes("#[Middleware");
28
+ if (!isRouteFile && !hasRouteAttributes) {
29
+ return [];
30
+ }
31
+ if (isRouteFile) {
32
+ const routes2 = [];
33
+ let braceDepth = 0;
34
+ const groupStack = [];
35
+ for (let i = 0; i < content.length; i++) {
36
+ const char = content[i];
37
+ if (char === "{") {
38
+ braceDepth++;
39
+ } else if (char === "}") {
40
+ braceDepth--;
41
+ while (groupStack.length > 0 && groupStack[groupStack.length - 1].startDepth > braceDepth) {
42
+ groupStack.pop();
43
+ }
44
+ }
45
+ if (content.substring(i, i + 7) === "Route::") {
46
+ const parsed = parseRouteChain(content, i);
47
+ if (parsed) {
48
+ const skippedText = content.substring(i, parsed.endIndex);
49
+ for (const c of skippedText) {
50
+ if (c === "{") braceDepth++;
51
+ else if (c === "}") braceDepth--;
52
+ }
53
+ i = parsed.endIndex - 1;
54
+ processRouteChain(parsed.calls, groupStack, braceDepth, filePath, ctx, routes2, this.name);
55
+ }
56
+ }
57
+ }
58
+ return routes2;
59
+ }
60
+ const routes = [];
61
+ const namespaceMatch = content.match(/namespace\s+([^;]+);/);
62
+ const namespace = namespaceMatch ? namespaceMatch[1].trim() : "";
63
+ const classMatch = content.match(/class\s+(\w+)/);
64
+ if (!classMatch) return [];
65
+ const className = classMatch[1];
66
+ const classFqn = namespace ? `${namespace}\\${className}` : className;
67
+ const classIndex = content.indexOf(`class ${className}`);
68
+ const beforeClass = content.substring(0, classIndex);
69
+ let classPrefix = "";
70
+ const classMiddlewares = [];
71
+ const classAttrs = extractAttributes(beforeClass);
72
+ for (const attr of classAttrs) {
73
+ if (attr.startsWith("Prefix")) {
74
+ const match = attr.match(/Prefix\s*\(\s*['"]([^'"]+)['"]/);
75
+ if (match) classPrefix = match[1];
76
+ } else if (attr.startsWith("Route")) {
77
+ const prefixMatch = attr.match(/prefix\s*:\s*['"]([^'"]+)['"]/);
78
+ if (prefixMatch) {
79
+ classPrefix = prefixMatch[1];
80
+ } else {
81
+ const argsMatch = attr.match(/Route\s*\(\s*['"]([^'"]+)['"]/);
82
+ if (argsMatch && !attr.includes("method")) {
83
+ classPrefix = argsMatch[1];
84
+ }
85
+ }
86
+ const mwMatch = attr.match(/middleware\s*:\s*['"]([^'"]+)['"]/);
87
+ if (mwMatch) {
88
+ classMiddlewares.push(mwMatch[1]);
89
+ } else {
90
+ const mwsMatch = attr.match(/middleware\s*:\s*\[([^\]]+)\]/);
91
+ if (mwsMatch) {
92
+ const matches = mwsMatch[1].match(/['"]([^'"]+)['"]/g) || [];
93
+ classMiddlewares.push(...matches.map((m) => m.replace(/['"]/g, "")));
94
+ }
95
+ }
96
+ } else if (attr.startsWith("Middleware")) {
97
+ if (attr.includes("[")) {
98
+ const matches = attr.match(/['"]([^'"]+)['"]/g) || [];
99
+ classMiddlewares.push(...matches.map((m) => m.replace(/['"]/g, "")));
100
+ } else {
101
+ const match = attr.match(/Middleware\s*\(\s*['"]([^'"]+)['"]/);
102
+ if (match) classMiddlewares.push(match[1]);
103
+ }
104
+ }
105
+ }
106
+ const parts = content.substring(classIndex).split(/\bfunction\s+/);
107
+ let previousText = parts[0];
108
+ for (let idx = 1; idx < parts.length; idx++) {
109
+ const part = parts[idx];
110
+ const nameMatch = part.match(/^(\w+)\s*\(/);
111
+ if (!nameMatch) {
112
+ previousText += " function " + part;
113
+ continue;
114
+ }
115
+ const methodName = nameMatch[1];
116
+ const methodAttrs = extractAttributes(previousText);
117
+ for (const attr of methodAttrs) {
118
+ const verbMatch = attr.match(/^(Get|Post|Put|Patch|Delete|Options|Any|Head|Route)\b/);
119
+ if (!verbMatch) continue;
120
+ const attrName = verbMatch[1];
121
+ let verb = attrName === "Route" ? "GET" : attrName.toUpperCase();
122
+ let uri = "";
123
+ const methodMiddlewares = [];
124
+ if (attrName === "Route") {
125
+ const verbParamMatch = attr.match(/Route\s*\(\s*['"]([^'"]+)['"]/);
126
+ if (verbParamMatch) {
127
+ verb = verbParamMatch[1].toUpperCase();
128
+ }
129
+ const uriParamMatch = attr.match(/Route\s*\(\s*['"][^'"]+['"]\s*,\s*['"]([^'"]+)['"]/);
130
+ if (uriParamMatch) {
131
+ uri = uriParamMatch[1];
132
+ } else {
133
+ const uriNamedMatch = attr.match(/uri\s*:\s*['"]([^'"]+)['"]/);
134
+ if (uriNamedMatch) uri = uriNamedMatch[1];
135
+ }
136
+ } else {
137
+ const uriParamMatch = attr.match(/^[a-zA-Z]+\s*\(\s*['"]([^'"]+)['"]/);
138
+ if (uriParamMatch) uri = uriParamMatch[1];
139
+ }
140
+ const mwAttrs = methodAttrs.filter((a) => a.startsWith("Middleware"));
141
+ for (const mwAttr of mwAttrs) {
142
+ if (mwAttr.includes("[")) {
143
+ const matches = mwAttr.match(/['"]([^'"]+)['"]/g) || [];
144
+ methodMiddlewares.push(...matches.map((m) => m.replace(/['"]/g, "")));
145
+ } else {
146
+ const match = mwAttr.match(/Middleware\s*\(\s*['"]([^'"]+)['"]/);
147
+ if (match) methodMiddlewares.push(match[1]);
148
+ }
149
+ }
150
+ const cleanClassPrefix = classPrefix.replace(/^\/|\/$/g, "").trim();
151
+ const cleanUri = uri.replace(/^\/|\/$/g, "").trim();
152
+ const fullPath = "/" + [cleanClassPrefix, cleanUri].filter(Boolean).join("/");
153
+ const allMiddlewares = Array.from(/* @__PURE__ */ new Set([...classMiddlewares, ...methodMiddlewares]));
154
+ routes.push({
155
+ framework: this.name,
156
+ method: verb,
157
+ path: fullPath,
158
+ handlerFile: filePath,
159
+ handlerSymbol: `${classFqn}@${methodName}`,
160
+ metadata: {
161
+ confidence: "inferred",
162
+ middlewares: allMiddlewares
163
+ }
164
+ });
165
+ }
166
+ previousText = part;
167
+ }
168
+ return routes;
169
+ }
170
+ }
171
+ function getParenthesizedContent(str, startIndex) {
172
+ let depth = 0;
173
+ let inString = false;
174
+ let stringChar = "";
175
+ let escape = false;
176
+ for (let i = startIndex; i < str.length; i++) {
177
+ const char = str[i];
178
+ if (escape) {
179
+ escape = false;
180
+ continue;
181
+ }
182
+ if (char === "\\") {
183
+ escape = true;
184
+ continue;
185
+ }
186
+ if (inString) {
187
+ if (char === stringChar) {
188
+ inString = false;
189
+ }
190
+ continue;
191
+ }
192
+ if (char === "'" || char === '"') {
193
+ inString = true;
194
+ stringChar = char;
195
+ continue;
196
+ }
197
+ if (char === "(") {
198
+ depth++;
199
+ if (depth === 1) {
200
+ startIndex = i + 1;
201
+ }
202
+ } else if (char === ")") {
203
+ depth--;
204
+ if (depth === 0) {
205
+ return {
206
+ content: str.substring(startIndex, i),
207
+ endIndex: i + 1
208
+ };
209
+ }
210
+ }
211
+ }
212
+ return null;
213
+ }
214
+ function parseRouteChain(content, startIndex) {
215
+ let index = startIndex;
216
+ const calls = [];
217
+ const initMatch = content.substring(index).match(/^Route::([a-zA-Z0-9_]+)\s*\(/);
218
+ if (!initMatch) return null;
219
+ const name = initMatch[1];
220
+ index += initMatch[0].length - 1;
221
+ if (name === "group") {
222
+ calls.push({ name: "group", args: "" });
223
+ return { calls, endIndex: index + 1 };
224
+ }
225
+ const paren = getParenthesizedContent(content, index);
226
+ if (!paren) return null;
227
+ calls.push({ name, args: paren.content });
228
+ index = paren.endIndex;
229
+ while (true) {
230
+ const wsMatch = content.substring(index).match(/^\s+/);
231
+ if (wsMatch) {
232
+ index += wsMatch[0].length;
233
+ }
234
+ if (content.substring(index, index + 2) !== "->") {
235
+ break;
236
+ }
237
+ const methodMatch = content.substring(index + 2).match(/^([a-zA-Z0-9_]+)\s*\(/);
238
+ if (!methodMatch) {
239
+ break;
240
+ }
241
+ const methodName = methodMatch[1];
242
+ index += 2 + methodMatch[0].length - 1;
243
+ if (methodName === "group") {
244
+ calls.push({ name: "group", args: "" });
245
+ return { calls, endIndex: index + 1 };
246
+ }
247
+ const methodParen = getParenthesizedContent(content, index);
248
+ if (!methodParen) {
249
+ break;
250
+ }
251
+ calls.push({ name: methodName, args: methodParen.content });
252
+ index = methodParen.endIndex;
253
+ }
254
+ return { calls, endIndex: index };
255
+ }
256
+ function parseArgs(argsStr) {
257
+ const args = [];
258
+ let current = "";
259
+ let depth = 0;
260
+ let bracketDepth = 0;
261
+ let inString = false;
262
+ let stringChar = "";
263
+ let escape = false;
264
+ for (let i = 0; i < argsStr.length; i++) {
265
+ const char = argsStr[i];
266
+ if (escape) {
267
+ escape = false;
268
+ current += char;
269
+ continue;
270
+ }
271
+ if (char === "\\") {
272
+ escape = true;
273
+ current += char;
274
+ continue;
275
+ }
276
+ if (inString) {
277
+ if (char === stringChar) {
278
+ inString = false;
279
+ }
280
+ current += char;
281
+ continue;
282
+ }
283
+ if (char === "'" || char === '"') {
284
+ inString = true;
285
+ stringChar = char;
286
+ current += char;
287
+ continue;
288
+ }
289
+ if (char === "(") {
290
+ depth++;
291
+ } else if (char === ")") {
292
+ depth--;
293
+ } else if (char === "[") {
294
+ bracketDepth++;
295
+ } else if (char === "]") {
296
+ bracketDepth--;
297
+ }
298
+ if (char === "," && depth === 0 && bracketDepth === 0) {
299
+ args.push(cleanQuotes(current.trim()));
300
+ current = "";
301
+ } else {
302
+ current += char;
303
+ }
304
+ }
305
+ if (current.trim()) {
306
+ args.push(cleanQuotes(current.trim()));
307
+ }
308
+ return args;
309
+ }
310
+ function cleanQuotes(str) {
311
+ return str.replace(/^['"]|['"]$/g, "");
312
+ }
313
+ function getBracketedContent(str, startIndex) {
314
+ let depth = 0;
315
+ let inString = false;
316
+ let stringChar = "";
317
+ let escape = false;
318
+ for (let i = startIndex; i < str.length; i++) {
319
+ const char = str[i];
320
+ if (escape) {
321
+ escape = false;
322
+ continue;
323
+ }
324
+ if (char === "\\") {
325
+ escape = true;
326
+ continue;
327
+ }
328
+ if (inString) {
329
+ if (char === stringChar) {
330
+ inString = false;
331
+ }
332
+ continue;
333
+ }
334
+ if (char === "'" || char === '"') {
335
+ inString = true;
336
+ stringChar = char;
337
+ continue;
338
+ }
339
+ if (char === "[") {
340
+ depth++;
341
+ if (depth === 1) {
342
+ startIndex = i + 1;
343
+ }
344
+ } else if (char === "]") {
345
+ depth--;
346
+ if (depth === 0) {
347
+ return {
348
+ content: str.substring(startIndex, i),
349
+ endIndex: i + 1
350
+ };
351
+ }
352
+ }
353
+ }
354
+ return null;
355
+ }
356
+ function extractAttributes(text) {
357
+ const attrs = [];
358
+ let index = 0;
359
+ while (true) {
360
+ const start = text.indexOf("#[", index);
361
+ if (start === -1) break;
362
+ const bracket = getBracketedContent(text, start + 1);
363
+ if (!bracket) {
364
+ index = start + 2;
365
+ continue;
366
+ }
367
+ attrs.push(bracket.content);
368
+ index = bracket.endIndex;
369
+ }
370
+ return attrs;
371
+ }
372
+ function processRouteChain(calls, groupStack, currentBraceDepth, filePath, ctx, routes, frameworkName) {
373
+ let verb = null;
374
+ let uri = null;
375
+ let handlerStr = null;
376
+ const chainPrefixes = [];
377
+ const chainMiddlewares = [];
378
+ let isGroup = false;
379
+ for (const call of calls) {
380
+ if (call.name === "group") {
381
+ isGroup = true;
382
+ } else if (["get", "post", "put", "patch", "delete", "options", "any", "match", "resource", "apiResource"].includes(call.name)) {
383
+ verb = call.name.toUpperCase();
384
+ const args = parseArgs(call.args);
385
+ uri = args[0] || null;
386
+ handlerStr = args[1] || null;
387
+ } else if (call.name === "prefix") {
388
+ const args = parseArgs(call.args);
389
+ if (args[0]) chainPrefixes.push(args[0]);
390
+ } else if (call.name === "middleware") {
391
+ if (call.args.trim().startsWith("[")) {
392
+ const matches = call.args.match(/['"]([^'"]+)['"]/g) || [];
393
+ chainMiddlewares.push(...matches.map((m) => m.replace(/['"]/g, "")));
394
+ } else {
395
+ const args = parseArgs(call.args);
396
+ if (args[0]) chainMiddlewares.push(args[0]);
397
+ }
398
+ }
399
+ }
400
+ const groupPrefixes = groupStack.flatMap((g) => g.prefixes);
401
+ const groupMiddlewares = groupStack.flatMap((g) => g.middlewares);
402
+ const fullPrefixes = [...groupPrefixes, ...chainPrefixes];
403
+ const fullMiddlewares = [...groupMiddlewares, ...chainMiddlewares];
404
+ if (isGroup) {
405
+ groupStack.push({
406
+ prefixes: chainPrefixes,
407
+ middlewares: chainMiddlewares,
408
+ startDepth: currentBraceDepth + 1
409
+ });
410
+ } else if (verb && uri && handlerStr) {
411
+ let controllerClass = null;
412
+ let controllerMethod = null;
413
+ let resourceType = null;
414
+ if (verb === "RESOURCE" || verb === "APIRESOURCE") {
415
+ resourceType = verb.toLowerCase();
416
+ const classMatch = handlerStr.match(/([a-zA-Z0-9_\\]+)(?:::class)?/);
417
+ if (classMatch) {
418
+ controllerClass = classMatch[1];
419
+ }
420
+ } else {
421
+ if (handlerStr.includes("::class")) {
422
+ const classAndMethod = handlerStr.match(/\[\s*([a-zA-Z0-9_\\]+)::class\s*,\s*['"]([^'"]+)['"]\s*\]/);
423
+ if (classAndMethod) {
424
+ controllerClass = classAndMethod[1];
425
+ controllerMethod = classAndMethod[2];
426
+ }
427
+ } else if (handlerStr.includes("@")) {
428
+ const cleanHandler = handlerStr.replace(/['"]/g, "");
429
+ const strMatch = cleanHandler.match(/^([a-zA-Z0-9_\\]+)@([a-zA-Z0-9_]+)$/);
430
+ if (strMatch) {
431
+ controllerClass = strMatch[1];
432
+ controllerMethod = strMatch[2];
433
+ }
434
+ }
435
+ }
436
+ if (controllerClass) {
437
+ let resolvedFile = filePath;
438
+ const resolvedPath = ctx.resolveSymbolToFile(controllerClass);
439
+ if (resolvedPath) {
440
+ resolvedFile = resolvedPath;
441
+ }
442
+ const cleanPrefixes = fullPrefixes.map((p) => p.replace(/^\/|\/$/g, "")).filter(Boolean);
443
+ const cleanUri = uri.replace(/^\/|\/$/g, "");
444
+ const fullPath = "/" + [...cleanPrefixes, cleanUri].join("/");
445
+ routes.push({
446
+ framework: frameworkName,
447
+ method: verb,
448
+ path: fullPath,
449
+ handlerFile: resolvedFile,
450
+ handlerSymbol: controllerMethod ? `${controllerClass}@${controllerMethod}` : controllerClass,
451
+ metadata: {
452
+ confidence: "inferred",
453
+ resourceType,
454
+ middlewares: fullMiddlewares
455
+ }
456
+ });
457
+ }
458
+ }
459
+ }
460
+ export {
461
+ LaravelDetector
462
+ };
@@ -0,0 +1,12 @@
1
+ import { FrameworkDetector, ScanContext, RouteBinding, HookBinding } from '../../types.js';
2
+
3
+ declare class NestJSDetector implements FrameworkDetector {
4
+ readonly name = "nestjs";
5
+ readonly language = "typescript";
6
+ readonly filePattern: RegExp;
7
+ detect(projectRoot: string, files: string[]): Promise<boolean>;
8
+ extractRoutes(filePath: string, content: string, ctx: ScanContext): Promise<RouteBinding[]>;
9
+ extractHooks(filePath: string, content: string, ctx: ScanContext): Promise<HookBinding[]>;
10
+ }
11
+
12
+ export { NestJSDetector };
@@ -0,0 +1,155 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ import { existsSync } from "node:fs";
4
+ class NestJSDetector {
5
+ name = "nestjs";
6
+ language = "typescript";
7
+ filePattern = /\.(ts|js)$/;
8
+ async detect(projectRoot, files) {
9
+ const packageJsonPath = join(projectRoot, "package.json");
10
+ if (existsSync(packageJsonPath)) {
11
+ try {
12
+ const pkg = JSON.parse(await readFile(packageJsonPath, "utf-8"));
13
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
14
+ if (deps && (deps["@nestjs/core"] || deps["@nestjs/common"])) {
15
+ return true;
16
+ }
17
+ } catch {
18
+ }
19
+ }
20
+ return false;
21
+ }
22
+ async extractRoutes(filePath, content, ctx) {
23
+ const routes = [];
24
+ if (!content.includes("@Controller") && !content.includes("@Get") && !content.includes("@Post")) {
25
+ return [];
26
+ }
27
+ const classes = content.split(/\bclass\s+/);
28
+ for (let i = 1; i < classes.length; i++) {
29
+ const classBlock = classes[i];
30
+ const classNameMatch = classBlock.match(/^([a-zA-Z0-9_]+)/);
31
+ if (!classNameMatch) continue;
32
+ const className = classNameMatch[1];
33
+ const classHeaderStart = content.indexOf(`class ${className}`);
34
+ const lookbackText = content.substring(Math.max(0, classHeaderStart - 300), classHeaderStart);
35
+ const controllerMatch = lookbackText.match(/@Controller\s*\(\s*(?:['"]([^'"]*)['"])?\s*\)/);
36
+ if (controllerMatch) {
37
+ const classPrefix = controllerMatch[1] || "";
38
+ const methodRegex = /@(Get|Post|Put|Delete|Patch|Options|All|Head)\s*\(\s*(?:['"]([^'"]*)['"])?\s*\)\s*(?:async\s+)?(\w+)\s*\(/g;
39
+ let match;
40
+ while ((match = methodRegex.exec(classBlock)) !== null) {
41
+ const verb = match[1].toUpperCase();
42
+ const routePath = match[2] || "";
43
+ const methodName = match[3];
44
+ const cleanPrefix = classPrefix.replace(/^\/|\/$/g, "");
45
+ const cleanRoute = routePath.replace(/^\/|\/$/g, "");
46
+ const fullPath = "/" + [cleanPrefix, cleanRoute].filter(Boolean).join("/");
47
+ let resolvedFile = filePath;
48
+ const resolvedPath = ctx.resolveSymbolToFile(className);
49
+ if (resolvedPath) {
50
+ resolvedFile = resolvedPath;
51
+ }
52
+ routes.push({
53
+ framework: this.name,
54
+ method: verb,
55
+ path: fullPath,
56
+ handlerFile: resolvedFile,
57
+ handlerSymbol: `${className}@${methodName}`,
58
+ metadata: {
59
+ confidence: "inferred"
60
+ }
61
+ });
62
+ }
63
+ }
64
+ }
65
+ return routes;
66
+ }
67
+ async extractHooks(filePath, content, ctx) {
68
+ const hooks = [];
69
+ const classes = content.split(/\bclass\s+/);
70
+ for (let i = 1; i < classes.length; i++) {
71
+ const classBlock = classes[i];
72
+ const classNameMatch = classBlock.match(/^([a-zA-Z0-9_]+)/);
73
+ if (!classNameMatch) continue;
74
+ const className = classNameMatch[1];
75
+ const classHeaderStart = content.indexOf(`class ${className}`);
76
+ const lookbackText = content.substring(Math.max(0, classHeaderStart - 300), classHeaderStart);
77
+ let resolvedFile = filePath;
78
+ const resolvedPath = ctx.resolveSymbolToFile(className);
79
+ if (resolvedPath) {
80
+ resolvedFile = resolvedPath;
81
+ }
82
+ const classGuardMatch = lookbackText.match(/@UseGuards\s*\(\s*([^)]+)\)/);
83
+ if (classGuardMatch) {
84
+ const guardSymbols = classGuardMatch[1].split(",").map((s) => s.trim());
85
+ for (const guardSymbol of guardSymbols) {
86
+ let guardFile = filePath;
87
+ const resolvedGuard = ctx.resolveSymbolToFile(guardSymbol);
88
+ if (resolvedGuard) {
89
+ guardFile = resolvedGuard;
90
+ }
91
+ hooks.push({
92
+ framework: this.name,
93
+ hookName: `guard:${guardSymbol}`,
94
+ hookType: "middleware",
95
+ handlerFile: guardFile,
96
+ handlerSymbol: guardSymbol
97
+ });
98
+ }
99
+ }
100
+ const isResolver = /@Resolver\s*\(/.test(lookbackText);
101
+ if (isResolver) {
102
+ const resolverRegex = /@(Query|Mutation|Subscription)\s*\(\s*(?:['"]([^'"]*)['"])?\s*\)\s*(?:async\s+)?(\w+)\s*\(/g;
103
+ let match2;
104
+ while ((match2 = resolverRegex.exec(classBlock)) !== null) {
105
+ const type = match2[1].toLowerCase();
106
+ const opName = match2[2] || match2[3];
107
+ const methodName = match2[3];
108
+ hooks.push({
109
+ framework: this.name,
110
+ hookName: opName,
111
+ hookType: "graphql_resolver",
112
+ handlerFile: resolvedFile,
113
+ handlerSymbol: `${className}@${methodName}`,
114
+ metadata: {
115
+ operationType: type
116
+ }
117
+ });
118
+ }
119
+ }
120
+ const messageRegex = /@(MessagePattern|EventPattern)\s*\(\s*([^)]+)\)\s*(?:async\s+)?(\w+)\s*\(/g;
121
+ let match;
122
+ while ((match = messageRegex.exec(classBlock)) !== null) {
123
+ const type = match[1] === "MessagePattern" ? "request-response" : "event-driven";
124
+ const pattern = match[2].replace(/['"{}]/g, "").trim();
125
+ const methodName = match[3];
126
+ hooks.push({
127
+ framework: this.name,
128
+ hookName: pattern,
129
+ hookType: "message_handler",
130
+ handlerFile: resolvedFile,
131
+ handlerSymbol: `${className}@${methodName}`,
132
+ metadata: {
133
+ patternType: type
134
+ }
135
+ });
136
+ }
137
+ const wsRegex = /@SubscribeMessage\s*\(\s*['"]([^'"]+)['"]\s*\)\s*(?:async\s+)?(\w+)\s*\(/g;
138
+ while ((match = wsRegex.exec(classBlock)) !== null) {
139
+ const event = match[1];
140
+ const methodName = match[2];
141
+ hooks.push({
142
+ framework: this.name,
143
+ hookName: event,
144
+ hookType: "websocket_handler",
145
+ handlerFile: resolvedFile,
146
+ handlerSymbol: `${className}@${methodName}`
147
+ });
148
+ }
149
+ }
150
+ return hooks;
151
+ }
152
+ }
153
+ export {
154
+ NestJSDetector
155
+ };
@@ -0,0 +1,11 @@
1
+ import { FrameworkDetector, ScanContext, RouteBinding } from '../../types.js';
2
+
3
+ declare class NextJSDetector implements FrameworkDetector {
4
+ readonly name = "nextjs";
5
+ readonly language = "typescript";
6
+ readonly filePattern: RegExp;
7
+ detect(projectRoot: string, files: string[]): Promise<boolean>;
8
+ extractRoutes(filePath: string, content: string, ctx: ScanContext): Promise<RouteBinding[]>;
9
+ }
10
+
11
+ export { NextJSDetector };