codesight 1.0.0 → 1.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.
@@ -42,6 +42,27 @@ export async function detectRoutes(files, project) {
42
42
  case "koa":
43
43
  routes.push(...(await detectKoaRoutes(files, project)));
44
44
  break;
45
+ case "nestjs":
46
+ routes.push(...(await detectNestJSRoutes(files, project)));
47
+ break;
48
+ case "elysia":
49
+ routes.push(...(await detectElysiaRoutes(files, project)));
50
+ break;
51
+ case "adonis":
52
+ routes.push(...(await detectAdonisRoutes(files, project)));
53
+ break;
54
+ case "trpc":
55
+ routes.push(...(await detectTRPCRoutes(files, project)));
56
+ break;
57
+ case "sveltekit":
58
+ routes.push(...(await detectSvelteKitRoutes(files, project)));
59
+ break;
60
+ case "remix":
61
+ routes.push(...(await detectRemixRoutes(files, project)));
62
+ break;
63
+ case "nuxt":
64
+ routes.push(...(await detectNuxtRoutes(files, project)));
65
+ break;
45
66
  case "fastapi":
46
67
  routes.push(...(await detectFastAPIRoutes(files, project)));
47
68
  break;
@@ -54,8 +75,26 @@ export async function detectRoutes(files, project) {
54
75
  case "gin":
55
76
  case "go-net-http":
56
77
  case "fiber":
78
+ case "echo":
79
+ case "chi":
57
80
  routes.push(...(await detectGoRoutes(files, project, fw)));
58
81
  break;
82
+ case "rails":
83
+ routes.push(...(await detectRailsRoutes(files, project)));
84
+ break;
85
+ case "phoenix":
86
+ routes.push(...(await detectPhoenixRoutes(files, project)));
87
+ break;
88
+ case "spring":
89
+ routes.push(...(await detectSpringRoutes(files, project)));
90
+ break;
91
+ case "actix":
92
+ case "axum":
93
+ routes.push(...(await detectRustRoutes(files, project, fw)));
94
+ break;
95
+ case "raw-http":
96
+ routes.push(...(await detectRawHttpRoutes(files, project)));
97
+ break;
59
98
  }
60
99
  }
61
100
  return routes;
@@ -67,7 +106,6 @@ async function detectNextAppRoutes(files, project) {
67
106
  for (const file of routeFiles) {
68
107
  const content = await readFileSafe(file);
69
108
  const rel = relative(project.root, file);
70
- // Extract API path from file path
71
109
  const pathMatch = rel.match(/(?:src\/)?app(.*)\/route\./);
72
110
  const apiPath = pathMatch ? pathMatch[1] || "/" : "/";
73
111
  for (const method of HTTP_METHODS) {
@@ -95,7 +133,6 @@ async function detectNextPagesApi(files, project) {
95
133
  const pathMatch = rel.match(/(?:src\/)?pages(\/api\/.*)\.(?:ts|js|tsx|jsx)$/);
96
134
  let apiPath = pathMatch ? pathMatch[1] : "/api";
97
135
  apiPath = apiPath.replace(/\/index$/, "").replace(/\[([^\]]+)\]/g, ":$1");
98
- // Detect methods from handler
99
136
  const methods = [];
100
137
  for (const method of HTTP_METHODS) {
101
138
  if (content.includes(`req.method === '${method}'`) || content.includes(`req.method === "${method}"`)) {
@@ -125,12 +162,10 @@ async function detectHonoRoutes(files, project) {
125
162
  if (!content.includes("hono") && !content.includes("Hono"))
126
163
  continue;
127
164
  const rel = relative(project.root, file);
128
- // Match: app.get("/path", ...), router.post("/path", ...), .route("/base", ...)
129
165
  const routePattern = /\.\s*(get|post|put|patch|delete|options|all)\s*\(\s*['"`]([^'"`]+)['"`]/gi;
130
166
  let match;
131
167
  while ((match = routePattern.exec(content)) !== null) {
132
168
  const path = match[2];
133
- // Skip non-path strings (middleware keys like "user", "userId", etc.)
134
169
  if (!path.startsWith("/") && !path.startsWith(":"))
135
170
  continue;
136
171
  routes.push({
@@ -153,7 +188,6 @@ async function detectExpressRoutes(files, project) {
153
188
  if (!content.includes("express") && !content.includes("Router"))
154
189
  continue;
155
190
  const rel = relative(project.root, file);
156
- // Match: app.get("/path", ...), router.post("/path", ...)
157
191
  const routePattern = /(?:app|router|server)\s*\.\s*(get|post|put|patch|delete|options|all)\s*\(\s*['"`]([^'"`]+)['"`]/gi;
158
192
  let match;
159
193
  while ((match = routePattern.exec(content)) !== null) {
@@ -177,7 +211,6 @@ async function detectFastifyRoutes(files, project) {
177
211
  if (!content.includes("fastify"))
178
212
  continue;
179
213
  const rel = relative(project.root, file);
180
- // Match: fastify.get("/path", ...) or server.route({ method: 'GET', url: '/path' })
181
214
  const routePattern = /(?:fastify|server|app)\s*\.\s*(get|post|put|patch|delete|options|all)\s*\(\s*['"`]([^'"`]+)['"`]/gi;
182
215
  let match;
183
216
  while ((match = routePattern.exec(content)) !== null) {
@@ -226,6 +259,233 @@ async function detectKoaRoutes(files, project) {
226
259
  }
227
260
  return routes;
228
261
  }
262
+ // --- NestJS ---
263
+ async function detectNestJSRoutes(files, project) {
264
+ const tsFiles = files.filter((f) => f.match(/\.(ts|js)$/));
265
+ const routes = [];
266
+ for (const file of tsFiles) {
267
+ const content = await readFileSafe(file);
268
+ if (!content.includes("@Controller") && !content.includes("@Get") && !content.includes("@Post"))
269
+ continue;
270
+ const rel = relative(project.root, file);
271
+ // Extract controller base path: @Controller('users') or @Controller('/users')
272
+ const controllerMatch = content.match(/@Controller\s*\(\s*['"`]([^'"`]*)['"`]\s*\)/);
273
+ const basePath = controllerMatch ? "/" + controllerMatch[1].replace(/^\//, "") : "";
274
+ // Match method decorators: @Get(), @Post('/create'), @Put(':id'), etc.
275
+ const decoratorPattern = /@(Get|Post|Put|Patch|Delete|Options|Head|All)\s*\(\s*(?:['"`]([^'"`]*)['"`])?\s*\)/gi;
276
+ let match;
277
+ while ((match = decoratorPattern.exec(content)) !== null) {
278
+ const method = match[1].toUpperCase();
279
+ const subPath = match[2] || "";
280
+ const fullPath = basePath + (subPath ? "/" + subPath.replace(/^\//, "") : "") || "/";
281
+ routes.push({
282
+ method,
283
+ path: fullPath,
284
+ file: rel,
285
+ tags: detectTags(content),
286
+ framework: "nestjs",
287
+ });
288
+ }
289
+ }
290
+ return routes;
291
+ }
292
+ // --- Elysia (Bun) ---
293
+ async function detectElysiaRoutes(files, project) {
294
+ const tsFiles = files.filter((f) => f.match(/\.(ts|js|mjs)$/));
295
+ const routes = [];
296
+ for (const file of tsFiles) {
297
+ const content = await readFileSafe(file);
298
+ if (!content.includes("elysia") && !content.includes("Elysia"))
299
+ continue;
300
+ const rel = relative(project.root, file);
301
+ const routePattern = /\.\s*(get|post|put|patch|delete|options|all)\s*\(\s*['"`]([^'"`]+)['"`]/gi;
302
+ let match;
303
+ while ((match = routePattern.exec(content)) !== null) {
304
+ const path = match[2];
305
+ if (!path.startsWith("/") && !path.startsWith(":"))
306
+ continue;
307
+ routes.push({
308
+ method: match[1].toUpperCase(),
309
+ path,
310
+ file: rel,
311
+ tags: detectTags(content),
312
+ framework: "elysia",
313
+ });
314
+ }
315
+ }
316
+ return routes;
317
+ }
318
+ // --- AdonisJS ---
319
+ async function detectAdonisRoutes(files, project) {
320
+ // AdonisJS uses start/routes.ts with Route.get(), Route.post(), router.get(), etc.
321
+ const routeFiles = files.filter((f) => f.match(/routes\.(ts|js)$/) || f.match(/\/routes\/.*\.(ts|js)$/));
322
+ const routes = [];
323
+ for (const file of routeFiles) {
324
+ const content = await readFileSafe(file);
325
+ const rel = relative(project.root, file);
326
+ const routePattern = /(?:Route|router)\s*\.\s*(get|post|put|patch|delete|any)\s*\(\s*['"`]([^'"`]+)['"`]/gi;
327
+ let match;
328
+ while ((match = routePattern.exec(content)) !== null) {
329
+ routes.push({
330
+ method: match[1].toUpperCase() === "ANY" ? "ALL" : match[1].toUpperCase(),
331
+ path: match[2],
332
+ file: rel,
333
+ tags: detectTags(content),
334
+ framework: "adonis",
335
+ });
336
+ }
337
+ }
338
+ return routes;
339
+ }
340
+ // --- tRPC ---
341
+ async function detectTRPCRoutes(files, project) {
342
+ const tsFiles = files.filter((f) => f.match(/\.(ts|js)$/));
343
+ const routes = [];
344
+ for (const file of tsFiles) {
345
+ const content = await readFileSafe(file);
346
+ if (!content.includes("Procedure") && !content.includes("procedure") && !content.includes("router"))
347
+ continue;
348
+ if (!content.includes("trpc") && !content.includes("TRPC") && !content.includes("createTRPCRouter") && !content.includes("publicProcedure") && !content.includes("protectedProcedure"))
349
+ continue;
350
+ const rel = relative(project.root, file);
351
+ // Match tRPC procedure definitions like:
352
+ // list: publicProcedure.query(...)
353
+ // create: publicProcedure.input(schema).mutation(...)
354
+ // getById: t.procedure.input(z.object({...})).query(...)
355
+ // Strategy: find lines with "query(" or "mutation(" and extract the procedure name
356
+ const lines = content.split("\n");
357
+ for (const line of lines) {
358
+ const queryMatch = line.match(/^\s*(\w+)\s*:\s*.*\.(query)\s*\(/);
359
+ const mutationMatch = line.match(/^\s*(\w+)\s*:\s*.*\.(mutation)\s*\(/);
360
+ const m = queryMatch || mutationMatch;
361
+ if (m) {
362
+ const procName = m[1];
363
+ const isQuery = m[2] === "query";
364
+ if (!routes.some((r) => r.path === procName && r.file === rel)) {
365
+ routes.push({
366
+ method: isQuery ? "QUERY" : "MUTATION",
367
+ path: procName,
368
+ file: rel,
369
+ tags: detectTags(content),
370
+ framework: "trpc",
371
+ });
372
+ }
373
+ }
374
+ }
375
+ }
376
+ return routes;
377
+ }
378
+ // --- SvelteKit ---
379
+ async function detectSvelteKitRoutes(files, project) {
380
+ // SvelteKit API routes: src/routes/**/+server.ts
381
+ const routeFiles = files.filter((f) => f.match(/\/routes\/.*\+server\.(ts|js)$/));
382
+ const routes = [];
383
+ for (const file of routeFiles) {
384
+ const content = await readFileSafe(file);
385
+ const rel = relative(project.root, file);
386
+ // Extract path from file structure: src/routes/api/users/+server.ts -> /api/users
387
+ const pathMatch = rel.match(/(?:src\/)?routes(.*)\/\+server\./);
388
+ let apiPath = pathMatch ? pathMatch[1] || "/" : "/";
389
+ // Convert [param] to :param
390
+ apiPath = apiPath.replace(/\[([^\]]+)\]/g, ":$1");
391
+ for (const method of HTTP_METHODS) {
392
+ const pattern = new RegExp(`export\\s+(?:async\\s+)?function\\s+${method}\\b`);
393
+ if (pattern.test(content)) {
394
+ routes.push({
395
+ method,
396
+ path: apiPath,
397
+ file: rel,
398
+ tags: detectTags(content),
399
+ framework: "sveltekit",
400
+ });
401
+ }
402
+ }
403
+ // Also detect: export const GET = ...
404
+ for (const method of HTTP_METHODS) {
405
+ const constPattern = new RegExp(`export\\s+const\\s+${method}\\s*[=:]`);
406
+ if (constPattern.test(content) && !routes.some((r) => r.method === method && r.path === apiPath)) {
407
+ routes.push({
408
+ method,
409
+ path: apiPath,
410
+ file: rel,
411
+ tags: detectTags(content),
412
+ framework: "sveltekit",
413
+ });
414
+ }
415
+ }
416
+ }
417
+ return routes;
418
+ }
419
+ // --- Remix ---
420
+ async function detectRemixRoutes(files, project) {
421
+ // Remix routes: app/routes/*.tsx with loader/action exports
422
+ const routeFiles = files.filter((f) => f.match(/\/routes\/.*\.(ts|tsx|js|jsx)$/));
423
+ const routes = [];
424
+ for (const file of routeFiles) {
425
+ const content = await readFileSafe(file);
426
+ const rel = relative(project.root, file);
427
+ // Convert filename to route path
428
+ const pathMatch = rel.match(/(?:app\/)?routes\/(.+)\.(ts|tsx|js|jsx)$/);
429
+ if (!pathMatch)
430
+ continue;
431
+ let routePath = "/" + pathMatch[1]
432
+ .replace(/\./g, "/") // dots become path segments
433
+ .replace(/_index$/, "") // _index -> root of parent
434
+ .replace(/\$/g, ":") // $param -> :param
435
+ .replace(/\[([^\]]+)\]/g, ":$1");
436
+ if (content.match(/export\s+(?:async\s+)?function\s+loader\b/) || content.match(/export\s+const\s+loader\b/)) {
437
+ routes.push({
438
+ method: "GET",
439
+ path: routePath,
440
+ file: rel,
441
+ tags: detectTags(content),
442
+ framework: "remix",
443
+ });
444
+ }
445
+ if (content.match(/export\s+(?:async\s+)?function\s+action\b/) || content.match(/export\s+const\s+action\b/)) {
446
+ routes.push({
447
+ method: "POST",
448
+ path: routePath,
449
+ file: rel,
450
+ tags: detectTags(content),
451
+ framework: "remix",
452
+ });
453
+ }
454
+ }
455
+ return routes;
456
+ }
457
+ // --- Nuxt ---
458
+ async function detectNuxtRoutes(files, project) {
459
+ // Nuxt server routes: server/api/**/*.ts
460
+ const routeFiles = files.filter((f) => f.match(/\/server\/(?:api|routes)\/.*\.(ts|js|mjs)$/));
461
+ const routes = [];
462
+ for (const file of routeFiles) {
463
+ const content = await readFileSafe(file);
464
+ const rel = relative(project.root, file);
465
+ // Extract path from file structure
466
+ const pathMatch = rel.match(/server\/((?:api|routes)\/.+)\.(ts|js|mjs)$/);
467
+ if (!pathMatch)
468
+ continue;
469
+ let routePath = "/" + pathMatch[1]
470
+ .replace(/\/index$/, "")
471
+ .replace(/\[([^\]]+)\]/g, ":$1");
472
+ // Detect method from filename (e.g., users.get.ts, users.post.ts)
473
+ const methodFromFile = basename(file).match(/\.(get|post|put|patch|delete)\.(ts|js|mjs)$/);
474
+ const method = methodFromFile ? methodFromFile[1].toUpperCase() : "ALL";
475
+ // Clean path: remove method suffix from path
476
+ if (methodFromFile) {
477
+ routePath = routePath.replace(new RegExp(`\\.${methodFromFile[1]}$`), "");
478
+ }
479
+ routes.push({
480
+ method,
481
+ path: routePath,
482
+ file: rel,
483
+ tags: detectTags(content),
484
+ framework: "nuxt",
485
+ });
486
+ }
487
+ return routes;
488
+ }
229
489
  // --- FastAPI ---
230
490
  async function detectFastAPIRoutes(files, project) {
231
491
  const pyFiles = files.filter((f) => f.endsWith(".py"));
@@ -235,7 +495,6 @@ async function detectFastAPIRoutes(files, project) {
235
495
  if (!content.includes("fastapi") && !content.includes("FastAPI") && !content.includes("APIRouter"))
236
496
  continue;
237
497
  const rel = relative(project.root, file);
238
- // Match: @app.get("/path") or @router.post("/path") or @api_router.get("/path")
239
498
  const routePattern = /@\w+\s*\.\s*(get|post|put|patch|delete|options)\s*\(\s*['"]([^'"]+)['"]/gi;
240
499
  let match;
241
500
  while ((match = routePattern.exec(content)) !== null) {
@@ -259,8 +518,7 @@ async function detectFlaskRoutes(files, project) {
259
518
  if (!content.includes("flask") && !content.includes("Flask") && !content.includes("Blueprint"))
260
519
  continue;
261
520
  const rel = relative(project.root, file);
262
- // Match: @app.route("/path", methods=["GET", "POST"])
263
- const routePattern = /@(?:app|bp|blueprint)\s*\.\s*route\s*\(\s*['"]([^'"]+)['"](?:\s*,\s*methods\s*=\s*\[([^\]]+)\])?\s*\)/gi;
521
+ const routePattern = /@(?:app|bp|blueprint|\w+)\s*\.\s*route\s*\(\s*['"]([^'"]+)['"](?:\s*,\s*methods\s*=\s*\[([^\]]+)\])?\s*\)/gi;
264
522
  let match;
265
523
  while ((match = routePattern.exec(content)) !== null) {
266
524
  const path = match[1];
@@ -287,7 +545,7 @@ async function detectDjangoRoutes(files, project) {
287
545
  for (const file of pyFiles) {
288
546
  const content = await readFileSafe(file);
289
547
  const rel = relative(project.root, file);
290
- // Match: path("api/v1/users/", views.UserView.as_view())
548
+ // path("api/v1/users/", views.UserView.as_view())
291
549
  const pathPattern = /path\s*\(\s*['"]([^'"]*)['"]\s*,/g;
292
550
  let match;
293
551
  while ((match = pathPattern.exec(content)) !== null) {
@@ -302,7 +560,7 @@ async function detectDjangoRoutes(files, project) {
302
560
  }
303
561
  return routes;
304
562
  }
305
- // --- Go ---
563
+ // --- Go (net/http, Gin, Fiber, Echo, Chi) ---
306
564
  async function detectGoRoutes(files, project, fw) {
307
565
  const goFiles = files.filter((f) => f.endsWith(".go"));
308
566
  const routes = [];
@@ -310,7 +568,6 @@ async function detectGoRoutes(files, project, fw) {
310
568
  const content = await readFileSafe(file);
311
569
  const rel = relative(project.root, file);
312
570
  if (fw === "gin") {
313
- // Match: r.GET("/path", handler)
314
571
  const pattern = /\.\s*(GET|POST|PUT|PATCH|DELETE|OPTIONS|HEAD)\s*\(\s*["']([^"']+)["']/g;
315
572
  let match;
316
573
  while ((match = pattern.exec(content)) !== null) {
@@ -324,7 +581,34 @@ async function detectGoRoutes(files, project, fw) {
324
581
  }
325
582
  }
326
583
  else if (fw === "fiber") {
327
- // Match: app.Get("/path", handler)
584
+ const pattern = /\.\s*(Get|Post|Put|Patch|Delete|Options|Head)\s*\(\s*["']([^"']+)["']/g;
585
+ let match;
586
+ while ((match = pattern.exec(content)) !== null) {
587
+ routes.push({
588
+ method: match[1].toUpperCase(),
589
+ path: match[2],
590
+ file: rel,
591
+ tags: detectTags(content),
592
+ framework: fw,
593
+ });
594
+ }
595
+ }
596
+ else if (fw === "echo") {
597
+ // e.GET("/path", handler) or g.POST("/path", handler)
598
+ const pattern = /\.\s*(GET|POST|PUT|PATCH|DELETE|OPTIONS|HEAD)\s*\(\s*["']([^"']+)["']/g;
599
+ let match;
600
+ while ((match = pattern.exec(content)) !== null) {
601
+ routes.push({
602
+ method: match[1],
603
+ path: match[2],
604
+ file: rel,
605
+ tags: detectTags(content),
606
+ framework: fw,
607
+ });
608
+ }
609
+ }
610
+ else if (fw === "chi") {
611
+ // r.Get("/path", handler), r.Post("/path", handler)
328
612
  const pattern = /\.\s*(Get|Post|Put|Patch|Delete|Options|Head)\s*\(\s*["']([^"']+)["']/g;
329
613
  let match;
330
614
  while ((match = pattern.exec(content)) !== null) {
@@ -354,3 +638,225 @@ async function detectGoRoutes(files, project, fw) {
354
638
  }
355
639
  return routes;
356
640
  }
641
+ // --- Rails ---
642
+ async function detectRailsRoutes(files, project) {
643
+ const routeFiles = files.filter((f) => f.match(/routes\.rb$/));
644
+ const routes = [];
645
+ for (const file of routeFiles) {
646
+ const content = await readFileSafe(file);
647
+ const rel = relative(project.root, file);
648
+ // get '/users', to: 'users#index'
649
+ const routePattern = /\b(get|post|put|patch|delete)\s+['"]([^'"]+)['"]/gi;
650
+ let match;
651
+ while ((match = routePattern.exec(content)) !== null) {
652
+ routes.push({
653
+ method: match[1].toUpperCase(),
654
+ path: match[2],
655
+ file: rel,
656
+ tags: detectTags(content),
657
+ framework: "rails",
658
+ });
659
+ }
660
+ // resources :users (generates RESTful routes)
661
+ const resourcePattern = /resources?\s+:(\w+)/g;
662
+ while ((match = resourcePattern.exec(content)) !== null) {
663
+ const name = match[1];
664
+ for (const [method, suffix] of [
665
+ ["GET", ""], ["GET", "/:id"], ["POST", ""],
666
+ ["PUT", "/:id"], ["PATCH", "/:id"], ["DELETE", "/:id"],
667
+ ]) {
668
+ routes.push({
669
+ method,
670
+ path: `/${name}${suffix}`,
671
+ file: rel,
672
+ tags: detectTags(content),
673
+ framework: "rails",
674
+ });
675
+ }
676
+ }
677
+ }
678
+ return routes;
679
+ }
680
+ // --- Phoenix (Elixir) ---
681
+ async function detectPhoenixRoutes(files, project) {
682
+ const routeFiles = files.filter((f) => f.match(/router\.ex$/));
683
+ const routes = [];
684
+ for (const file of routeFiles) {
685
+ const content = await readFileSafe(file);
686
+ const rel = relative(project.root, file);
687
+ // get "/users", UserController, :index
688
+ const routePattern = /\b(get|post|put|patch|delete)\s+["']([^"']+)["']/gi;
689
+ let match;
690
+ while ((match = routePattern.exec(content)) !== null) {
691
+ routes.push({
692
+ method: match[1].toUpperCase(),
693
+ path: match[2],
694
+ file: rel,
695
+ tags: detectTags(content),
696
+ framework: "phoenix",
697
+ });
698
+ }
699
+ // resources "/users", UserController
700
+ const resourcePattern = /resources\s+["']([^"']+)["']/g;
701
+ while ((match = resourcePattern.exec(content)) !== null) {
702
+ const basePath = match[1];
703
+ for (const [method, suffix] of [
704
+ ["GET", ""], ["GET", "/:id"], ["POST", ""],
705
+ ["PUT", "/:id"], ["PATCH", "/:id"], ["DELETE", "/:id"],
706
+ ]) {
707
+ routes.push({
708
+ method,
709
+ path: `${basePath}${suffix}`,
710
+ file: rel,
711
+ tags: detectTags(content),
712
+ framework: "phoenix",
713
+ });
714
+ }
715
+ }
716
+ }
717
+ return routes;
718
+ }
719
+ // --- Spring Boot (Java/Kotlin) ---
720
+ async function detectSpringRoutes(files, project) {
721
+ const javaFiles = files.filter((f) => f.match(/\.(java|kt)$/));
722
+ const routes = [];
723
+ for (const file of javaFiles) {
724
+ const content = await readFileSafe(file);
725
+ if (!content.includes("@RestController") && !content.includes("@Controller") && !content.includes("@RequestMapping"))
726
+ continue;
727
+ const rel = relative(project.root, file);
728
+ // Extract class-level @RequestMapping
729
+ const classMapping = content.match(/@RequestMapping\s*\(\s*(?:value\s*=\s*)?["']([^"']+)["']/);
730
+ const basePath = classMapping ? classMapping[1] : "";
731
+ // @GetMapping("/path"), @PostMapping("/path"), etc.
732
+ const mappingPattern = /@(Get|Post|Put|Patch|Delete)Mapping\s*\(\s*(?:value\s*=\s*)?(?:["']([^"']*)["'])?\s*\)/gi;
733
+ let match;
734
+ while ((match = mappingPattern.exec(content)) !== null) {
735
+ const method = match[1].toUpperCase();
736
+ const subPath = match[2] || "";
737
+ routes.push({
738
+ method,
739
+ path: basePath + subPath || "/",
740
+ file: rel,
741
+ tags: detectTags(content),
742
+ framework: "spring",
743
+ });
744
+ }
745
+ // @RequestMapping(method = RequestMethod.GET, value = "/path")
746
+ const reqMappingPattern = /@RequestMapping\s*\([^)]*method\s*=\s*RequestMethod\.(\w+)[^)]*value\s*=\s*["']([^"']+)["']/gi;
747
+ while ((match = reqMappingPattern.exec(content)) !== null) {
748
+ routes.push({
749
+ method: match[1].toUpperCase(),
750
+ path: basePath + match[2],
751
+ file: rel,
752
+ tags: detectTags(content),
753
+ framework: "spring",
754
+ });
755
+ }
756
+ }
757
+ return routes;
758
+ }
759
+ // --- Rust (Actix-web, Axum) ---
760
+ async function detectRustRoutes(files, project, fw) {
761
+ const rsFiles = files.filter((f) => f.endsWith(".rs"));
762
+ const routes = [];
763
+ for (const file of rsFiles) {
764
+ const content = await readFileSafe(file);
765
+ const rel = relative(project.root, file);
766
+ if (fw === "actix") {
767
+ // #[get("/path")], #[post("/path")], etc.
768
+ const attrPattern = /#\[(get|post|put|patch|delete)\s*\(\s*"([^"]+)"\s*\)\s*\]/gi;
769
+ let match;
770
+ while ((match = attrPattern.exec(content)) !== null) {
771
+ routes.push({
772
+ method: match[1].toUpperCase(),
773
+ path: match[2],
774
+ file: rel,
775
+ tags: detectTags(content),
776
+ framework: "actix",
777
+ });
778
+ }
779
+ // .route("/path", web::get().to(handler))
780
+ const routePattern = /\.route\s*\(\s*"([^"]+)"\s*,\s*web::(get|post|put|patch|delete)\s*\(\s*\)/gi;
781
+ while ((match = routePattern.exec(content)) !== null) {
782
+ routes.push({
783
+ method: match[2].toUpperCase(),
784
+ path: match[1],
785
+ file: rel,
786
+ tags: detectTags(content),
787
+ framework: "actix",
788
+ });
789
+ }
790
+ }
791
+ else if (fw === "axum") {
792
+ // .route("/path", get(handler)) or .route("/path", post(handler).get(handler))
793
+ const routePattern = /\.route\s*\(\s*"([^"]+)"\s*,\s*(get|post|put|patch|delete)\s*\(/gi;
794
+ let match;
795
+ while ((match = routePattern.exec(content)) !== null) {
796
+ routes.push({
797
+ method: match[2].toUpperCase(),
798
+ path: match[1],
799
+ file: rel,
800
+ tags: detectTags(content),
801
+ framework: "axum",
802
+ });
803
+ }
804
+ }
805
+ }
806
+ return routes;
807
+ }
808
+ // --- Raw HTTP (Node.js http.createServer, Deno, Bun.serve) ---
809
+ async function detectRawHttpRoutes(files, project) {
810
+ const tsFiles = files.filter((f) => f.match(/\.(ts|js|mjs|cjs)$/));
811
+ const routes = [];
812
+ const globalSeen = new Set();
813
+ for (const file of tsFiles) {
814
+ const content = await readFileSafe(file);
815
+ // Only scan files that handle HTTP requests
816
+ if (!content.match(/(?:createServer|http\.|req\.|request\.|url|pathname|Bun\.serve|Deno\.serve)/))
817
+ continue;
818
+ const rel = relative(project.root, file);
819
+ const patterns = [
820
+ // Direct comparison: url === "/path" or pathname === "/path"
821
+ /(?:url|pathname|parsedUrl\.pathname)\s*===?\s*['"`](\/[a-zA-Z0-9/_:.\-]+)['"`]/g,
822
+ // startsWith: url.startsWith("/api")
823
+ /(?:url|pathname)\s*\.startsWith\s*\(\s*['"`](\/[a-zA-Z0-9/_:.\-]+)['"`]\s*\)/g,
824
+ // Switch case: case "/path":
825
+ /case\s+['"`](\/[a-zA-Z0-9/_:.\-]+)['"`]\s*:/g,
826
+ ];
827
+ const fileTags = detectTags(content);
828
+ for (const pattern of patterns) {
829
+ let match;
830
+ while ((match = pattern.exec(content)) !== null) {
831
+ const path = match[1];
832
+ // Skip paths that are clearly not routes
833
+ if (path.includes("\\") || path.length > 100 || path.includes(".."))
834
+ continue;
835
+ // Skip file extensions
836
+ if (path.match(/\.\w{2,4}$/))
837
+ continue;
838
+ const key = `${rel}:${path}`;
839
+ if (globalSeen.has(key))
840
+ continue;
841
+ globalSeen.add(key);
842
+ // Try to detect method from surrounding context (within 300 chars)
843
+ const surroundingStart = Math.max(0, match.index - 300);
844
+ const surroundingEnd = Math.min(content.length, match.index + 300);
845
+ const surrounding = content.substring(surroundingStart, surroundingEnd);
846
+ let method = "ALL";
847
+ const methodMatch = surrounding.match(/method\s*===?\s*['"`](GET|POST|PUT|PATCH|DELETE)['"`]/i);
848
+ if (methodMatch) {
849
+ method = methodMatch[1].toUpperCase();
850
+ }
851
+ routes.push({
852
+ method,
853
+ path,
854
+ file: rel,
855
+ tags: fileTags,
856
+ framework: "raw-http",
857
+ });
858
+ }
859
+ }
860
+ }
861
+ return routes;
862
+ }
package/dist/formatter.js CHANGED
@@ -253,14 +253,27 @@ function formatCombined(result, sections) {
253
253
  }
254
254
  function filterNotableDeps(deps) {
255
255
  const notable = new Set([
256
+ // Frameworks
256
257
  "next", "react", "vue", "svelte", "hono", "express", "fastify", "koa",
257
- "drizzle-orm", "prisma", "@prisma/client", "typeorm", "tailwindcss",
258
- "stripe", "@polar-sh/sdk", "resend", "bullmq", "redis", "ioredis",
259
- "zod", "trpc", "@trpc/server", "better-auth", "@clerk/nextjs",
260
- "next-auth", "lucia", "passport", "@anthropic-ai/sdk", "openai",
261
- "ai", "langchain", "supabase", "@supabase/supabase-js", "mongoose",
262
- "pg", "mysql2", "better-sqlite3", "playwright", "puppeteer",
263
- "socket.io", "graphql", "@apollo/server",
258
+ "@nestjs/core", "@nestjs/common", "elysia", "@adonisjs/core",
259
+ "@sveltejs/kit", "@remix-run/node", "@remix-run/react", "nuxt",
260
+ // ORMs & DB
261
+ "drizzle-orm", "prisma", "@prisma/client", "typeorm", "mongoose", "sequelize",
262
+ "pg", "mysql2", "better-sqlite3", "knex",
263
+ // Auth
264
+ "better-auth", "@clerk/nextjs", "next-auth", "lucia", "passport", "@auth/core",
265
+ // Payments
266
+ "stripe", "@polar-sh/sdk", "resend", "@lemonsqueezy/lemonsqueezy.js",
267
+ // Infrastructure
268
+ "bullmq", "redis", "ioredis", "tailwindcss",
269
+ // API
270
+ "zod", "@trpc/server", "graphql", "@apollo/server",
271
+ // AI
272
+ "@anthropic-ai/sdk", "openai", "ai", "langchain", "@google/generative-ai",
273
+ // Services
274
+ "supabase", "@supabase/supabase-js", "firebase", "@firebase/app",
275
+ // Testing/tools
276
+ "playwright", "puppeteer", "socket.io",
264
277
  ]);
265
278
  return Object.entries(deps)
266
279
  .filter(([name]) => notable.has(name))