@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.
- package/LICENSE +194 -0
- package/README.md +488 -0
- package/VERSION +1 -0
- package/dist/agents/generator.d.ts +74 -0
- package/dist/agents/generator.js +375 -0
- package/dist/agents/templates.d.ts +29 -0
- package/dist/agents/templates.js +459 -0
- package/dist/cli.d.ts +16 -0
- package/dist/cli.js +1835 -0
- package/dist/core/cluster-engine.d.ts +32 -0
- package/dist/core/cluster-engine.js +314 -0
- package/dist/core/config.d.ts +29 -0
- package/dist/core/config.js +178 -0
- package/dist/core/context-builder.d.ts +61 -0
- package/dist/core/context-builder.js +252 -0
- package/dist/core/flow-tracer.d.ts +63 -0
- package/dist/core/flow-tracer.js +366 -0
- package/dist/core/git-tracker.d.ts +20 -0
- package/dist/core/git-tracker.js +159 -0
- package/dist/core/graph.d.ts +42 -0
- package/dist/core/graph.js +186 -0
- package/dist/core/metrics.d.ts +24 -0
- package/dist/core/metrics.js +87 -0
- package/dist/core/scanner.d.ts +53 -0
- package/dist/core/scanner.js +949 -0
- package/dist/core/store-bun.d.ts +13 -0
- package/dist/core/store-bun.js +34 -0
- package/dist/core/store-interface.d.ts +15 -0
- package/dist/core/store-interface.js +7 -0
- package/dist/core/store-node.d.ts +13 -0
- package/dist/core/store-node.js +35 -0
- package/dist/core/store.d.ts +132 -0
- package/dist/core/store.js +614 -0
- package/dist/core/workspace-manager.d.ts +9 -0
- package/dist/core/workspace-manager.js +64 -0
- package/dist/exporters/dot-exporter.d.ts +16 -0
- package/dist/exporters/dot-exporter.js +179 -0
- package/dist/exporters/graph-exporter.d.ts +14 -0
- package/dist/exporters/graph-exporter.js +85 -0
- package/dist/exporters/index.d.ts +9 -0
- package/dist/exporters/index.js +12 -0
- package/dist/exporters/llm-exporter.d.ts +18 -0
- package/dist/exporters/llm-exporter.js +224 -0
- package/dist/exporters/svg-exporter.d.ts +19 -0
- package/dist/exporters/svg-exporter.js +319 -0
- package/dist/exporters/toon-exporter.d.ts +16 -0
- package/dist/exporters/toon-exporter.js +246 -0
- package/dist/frameworks/detectors/aspnet.d.ts +11 -0
- package/dist/frameworks/detectors/aspnet.js +52 -0
- package/dist/frameworks/detectors/django.d.ts +14 -0
- package/dist/frameworks/detectors/django.js +135 -0
- package/dist/frameworks/detectors/drupal.d.ts +13 -0
- package/dist/frameworks/detectors/drupal.js +94 -0
- package/dist/frameworks/detectors/express.d.ts +12 -0
- package/dist/frameworks/detectors/express.js +234 -0
- package/dist/frameworks/detectors/fastapi.d.ts +12 -0
- package/dist/frameworks/detectors/fastapi.js +203 -0
- package/dist/frameworks/detectors/flask.d.ts +12 -0
- package/dist/frameworks/detectors/flask.js +244 -0
- package/dist/frameworks/detectors/go.d.ts +11 -0
- package/dist/frameworks/detectors/go.js +75 -0
- package/dist/frameworks/detectors/laravel.d.ts +11 -0
- package/dist/frameworks/detectors/laravel.js +462 -0
- package/dist/frameworks/detectors/nestjs.d.ts +12 -0
- package/dist/frameworks/detectors/nestjs.js +155 -0
- package/dist/frameworks/detectors/nextjs.d.ts +11 -0
- package/dist/frameworks/detectors/nextjs.js +118 -0
- package/dist/frameworks/detectors/rails.d.ts +12 -0
- package/dist/frameworks/detectors/rails.js +76 -0
- package/dist/frameworks/detectors/react-router.d.ts +11 -0
- package/dist/frameworks/detectors/react-router.js +115 -0
- package/dist/frameworks/detectors/rust.d.ts +11 -0
- package/dist/frameworks/detectors/rust.js +59 -0
- package/dist/frameworks/detectors/spring.d.ts +11 -0
- package/dist/frameworks/detectors/spring.js +56 -0
- package/dist/frameworks/detectors/sveltekit.d.ts +11 -0
- package/dist/frameworks/detectors/sveltekit.js +154 -0
- package/dist/frameworks/detectors/symfony.d.ts +13 -0
- package/dist/frameworks/detectors/symfony.js +175 -0
- package/dist/frameworks/detectors/tanstack-router.d.ts +12 -0
- package/dist/frameworks/detectors/tanstack-router.js +80 -0
- package/dist/frameworks/detectors/vapor.d.ts +11 -0
- package/dist/frameworks/detectors/vapor.js +52 -0
- package/dist/frameworks/detectors/vue-router.d.ts +12 -0
- package/dist/frameworks/detectors/vue-router.js +237 -0
- package/dist/frameworks/detectors/wordpress.d.ts +13 -0
- package/dist/frameworks/detectors/wordpress.js +141 -0
- package/dist/frameworks/detectors/yii.d.ts +11 -0
- package/dist/frameworks/detectors/yii.js +131 -0
- package/dist/frameworks/framework-registry.d.ts +13 -0
- package/dist/frameworks/framework-registry.js +77 -0
- package/dist/frameworks/route-registry.d.ts +26 -0
- package/dist/frameworks/route-registry.js +102 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +30 -0
- package/dist/languages/index.d.ts +2 -0
- package/dist/languages/index.js +7 -0
- package/dist/languages/installer.d.ts +13 -0
- package/dist/languages/installer.js +103 -0
- package/dist/languages/registry.d.ts +19 -0
- package/dist/languages/registry.js +427 -0
- package/dist/main.d.ts +2 -0
- package/dist/main.js +20 -0
- package/dist/mcp.d.ts +11 -0
- package/dist/mcp.js +1699 -0
- package/dist/parsers/common-methods.d.ts +3 -0
- package/dist/parsers/common-methods.js +33 -0
- package/dist/parsers/fallback-parser.d.ts +10 -0
- package/dist/parsers/fallback-parser.js +18 -0
- package/dist/parsers/generic-wasm-parser.d.ts +23 -0
- package/dist/parsers/generic-wasm-parser.js +168 -0
- package/dist/parsers/ignored-symbols.d.ts +26 -0
- package/dist/parsers/ignored-symbols.js +77 -0
- package/dist/parsers/index.d.ts +9 -0
- package/dist/parsers/index.js +13 -0
- package/dist/parsers/languages/javascript.d.ts +11 -0
- package/dist/parsers/languages/javascript.js +28 -0
- package/dist/parsers/languages/php.d.ts +15 -0
- package/dist/parsers/languages/php.js +648 -0
- package/dist/parsers/languages/typescript.d.ts +10 -0
- package/dist/parsers/languages/typescript.js +9 -0
- package/dist/parsers/languages/vue.d.ts +13 -0
- package/dist/parsers/languages/vue.js +63 -0
- package/dist/parsers/parse-worker.d.ts +2 -0
- package/dist/parsers/parse-worker.js +185 -0
- package/dist/parsers/parser-interface.d.ts +9 -0
- package/dist/parsers/parser-interface.js +0 -0
- package/dist/parsers/parser-registry.d.ts +8 -0
- package/dist/parsers/parser-registry.js +52 -0
- package/dist/parsers/wasm-parser.d.ts +16 -0
- package/dist/parsers/wasm-parser.js +110 -0
- package/dist/types.d.ts +172 -0
- package/dist/types.js +0 -0
- package/dist/ui/index.html +270 -0
- package/dist/ui/main.js +581 -0
- package/dist/ui/main.js.map +7 -0
- package/dist/ui/styles.css +573 -0
- package/dist/ui-events.d.ts +36 -0
- package/dist/ui-events.js +61 -0
- package/dist/ui-server.d.ts +12 -0
- package/dist/ui-server.js +504 -0
- package/package.json +179 -0
- package/queries/bash/references.scm +22 -0
- package/queries/bash/symbols.scm +15 -0
- package/queries/c/references.scm +14 -0
- package/queries/c/symbols.scm +30 -0
- package/queries/c-sharp/references.scm +26 -0
- package/queries/c-sharp/symbols.scm +57 -0
- package/queries/cpp/references.scm +21 -0
- package/queries/cpp/symbols.scm +44 -0
- package/queries/dart/references.scm +33 -0
- package/queries/dart/symbols.scm +38 -0
- package/queries/elixir/references.scm +45 -0
- package/queries/elixir/symbols.scm +41 -0
- package/queries/go/references.scm +22 -0
- package/queries/go/symbols.scm +53 -0
- package/queries/java/references.scm +32 -0
- package/queries/java/symbols.scm +41 -0
- package/queries/javascript/references.scm +14 -0
- package/queries/javascript/symbols.scm +23 -0
- package/queries/kotlin/references.scm +31 -0
- package/queries/kotlin/symbols.scm +24 -0
- package/queries/lua/references.scm +19 -0
- package/queries/lua/symbols.scm +29 -0
- package/queries/pascal/references.scm +29 -0
- package/queries/pascal/symbols.scm +45 -0
- package/queries/php/references.scm +109 -0
- package/queries/php/symbols.scm +33 -0
- package/queries/python/references.scm +50 -0
- package/queries/python/symbols.scm +21 -0
- package/queries/ruby/references.scm +48 -0
- package/queries/ruby/symbols.scm +24 -0
- package/queries/rust/references.scm +31 -0
- package/queries/rust/symbols.scm +35 -0
- package/queries/scala/references.scm +30 -0
- package/queries/scala/symbols.scm +35 -0
- package/queries/svelte/references.scm +20 -0
- package/queries/svelte/symbols.scm +30 -0
- package/queries/swift/references.scm +22 -0
- package/queries/swift/symbols.scm +37 -0
- package/queries/typescript/references.scm +25 -0
- package/queries/typescript/symbols.scm +35 -0
- package/queries/vue/references.scm +20 -0
- package/queries/vue/symbols.scm +28 -0
- package/queries/zig/references.scm +20 -0
- package/queries/zig/symbols.scm +22 -0
- package/wasm/tree-sitter-c.wasm +0 -0
- package/wasm/tree-sitter-c_sharp.wasm +0 -0
- package/wasm/tree-sitter-cpp.wasm +0 -0
- package/wasm/tree-sitter-dart.wasm +0 -0
- package/wasm/tree-sitter-go.wasm +0 -0
- package/wasm/tree-sitter-java.wasm +0 -0
- package/wasm/tree-sitter-javascript.wasm +0 -0
- package/wasm/tree-sitter-kotlin.wasm +0 -0
- package/wasm/tree-sitter-php.wasm +0 -0
- package/wasm/tree-sitter-python.wasm +0 -0
- package/wasm/tree-sitter-ruby.wasm +0 -0
- package/wasm/tree-sitter-rust.wasm +0 -0
- package/wasm/tree-sitter-scala.wasm +0 -0
- package/wasm/tree-sitter-swift.wasm +0 -0
- package/wasm/tree-sitter-tsx.wasm +0 -0
- package/wasm/tree-sitter-typescript.wasm +0 -0
- package/wasm/tree-sitter-vue.wasm +0 -0
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { existsSync } from "node:fs";
|
|
4
|
+
class NextJSDetector {
|
|
5
|
+
name = "nextjs";
|
|
6
|
+
language = "typescript";
|
|
7
|
+
filePattern = /\.(js|jsx|ts|tsx)$/;
|
|
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.next) {
|
|
15
|
+
return true;
|
|
16
|
+
}
|
|
17
|
+
} catch {
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
async extractRoutes(filePath, content, ctx) {
|
|
23
|
+
const routes = [];
|
|
24
|
+
const pathNormalized = filePath.replace(/\\/g, "/");
|
|
25
|
+
if (pathNormalized.includes("pages/")) {
|
|
26
|
+
const pagesIndex = pathNormalized.indexOf("pages/");
|
|
27
|
+
let routePart = pathNormalized.substring(pagesIndex + 6);
|
|
28
|
+
routePart = routePart.replace(/\.[a-zA-Z0-9]+$/, "");
|
|
29
|
+
let routePath = "/" + routePart;
|
|
30
|
+
routePath = routePath.replace(/\/index$/, "");
|
|
31
|
+
if (routePath === "") routePath = "/";
|
|
32
|
+
routePath = routePath.replace(/\[([a-zA-Z0-9_]+)\]/g, "{$1}");
|
|
33
|
+
const isApi = routePath.startsWith("/api/");
|
|
34
|
+
routes.push({
|
|
35
|
+
framework: this.name,
|
|
36
|
+
method: isApi ? "ALL" : "GET",
|
|
37
|
+
path: routePath,
|
|
38
|
+
handlerFile: filePath,
|
|
39
|
+
handlerSymbol: "default",
|
|
40
|
+
metadata: {
|
|
41
|
+
confidence: "inferred",
|
|
42
|
+
routeType: isApi ? "server" : "client"
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
if (pathNormalized.endsWith("/page.tsx") || pathNormalized.endsWith("/page.jsx")) {
|
|
47
|
+
const appIndex = pathNormalized.indexOf("app/");
|
|
48
|
+
if (appIndex !== -1) {
|
|
49
|
+
let routePart = pathNormalized.substring(appIndex + 4);
|
|
50
|
+
routePart = routePart.replace(/\/page\.[a-zA-Z0-9]+$/, "");
|
|
51
|
+
const segments = routePart.split("/").filter((seg) => !seg.startsWith("(") || !seg.endsWith(")"));
|
|
52
|
+
let routePath = "/" + segments.join("/");
|
|
53
|
+
if (routePath === "") routePath = "/";
|
|
54
|
+
routePath = routePath.replace(/\[([a-zA-Z0-9_]+)\]/g, "{$1}");
|
|
55
|
+
routes.push({
|
|
56
|
+
framework: this.name,
|
|
57
|
+
method: "GET",
|
|
58
|
+
path: routePath,
|
|
59
|
+
handlerFile: filePath,
|
|
60
|
+
handlerSymbol: "default",
|
|
61
|
+
metadata: {
|
|
62
|
+
confidence: "inferred",
|
|
63
|
+
routeType: "client"
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
if (pathNormalized.endsWith("/route.ts") || pathNormalized.endsWith("/route.js")) {
|
|
69
|
+
const appIndex = pathNormalized.indexOf("app/");
|
|
70
|
+
if (appIndex !== -1) {
|
|
71
|
+
let routePart = pathNormalized.substring(appIndex + 4);
|
|
72
|
+
routePart = routePart.replace(/\/route\.[a-zA-Z0-9]+$/, "");
|
|
73
|
+
const segments = routePart.split("/").filter((seg) => !seg.startsWith("(") || !seg.endsWith(")"));
|
|
74
|
+
let routePath = "/" + segments.join("/");
|
|
75
|
+
if (routePath === "") routePath = "/";
|
|
76
|
+
routePath = routePath.replace(/\[([a-zA-Z0-9_]+)\]/g, "{$1}");
|
|
77
|
+
const verbRegex = /export\s+(?:async\s+)?function\s+(GET|POST|PUT|DELETE|PATCH|OPTIONS|HEAD)\b/g;
|
|
78
|
+
let match;
|
|
79
|
+
while ((match = verbRegex.exec(content)) !== null) {
|
|
80
|
+
const verb = match[1];
|
|
81
|
+
routes.push({
|
|
82
|
+
framework: this.name,
|
|
83
|
+
method: verb,
|
|
84
|
+
path: routePath,
|
|
85
|
+
handlerFile: filePath,
|
|
86
|
+
handlerSymbol: verb,
|
|
87
|
+
metadata: {
|
|
88
|
+
confidence: "inferred",
|
|
89
|
+
routeType: "server"
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
if (content.includes("'use server'") || content.includes('"use server"')) {
|
|
96
|
+
const actionRegex = /export\s+(?:async\s+)?function\s+([a-zA-Z0-9_]+)\b/g;
|
|
97
|
+
let match;
|
|
98
|
+
while ((match = actionRegex.exec(content)) !== null) {
|
|
99
|
+
const actionName = match[1];
|
|
100
|
+
routes.push({
|
|
101
|
+
framework: this.name,
|
|
102
|
+
method: "POST",
|
|
103
|
+
path: `/action/${actionName}`,
|
|
104
|
+
handlerFile: filePath,
|
|
105
|
+
handlerSymbol: actionName,
|
|
106
|
+
metadata: {
|
|
107
|
+
confidence: "inferred",
|
|
108
|
+
routeType: "server"
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return routes;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
export {
|
|
117
|
+
NextJSDetector
|
|
118
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { FrameworkDetector, ScanContext, RouteBinding } from '../../types.js';
|
|
2
|
+
|
|
3
|
+
declare class RailsDetector implements FrameworkDetector {
|
|
4
|
+
readonly name = "rails";
|
|
5
|
+
readonly language = "ruby";
|
|
6
|
+
readonly filePattern: RegExp;
|
|
7
|
+
detect(projectRoot: string, files: string[]): Promise<boolean>;
|
|
8
|
+
extractRoutes(filePath: string, content: string, ctx: ScanContext): Promise<RouteBinding[]>;
|
|
9
|
+
private createRouteBinding;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export { RailsDetector };
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { existsSync } from "node:fs";
|
|
4
|
+
class RailsDetector {
|
|
5
|
+
name = "rails";
|
|
6
|
+
language = "ruby";
|
|
7
|
+
filePattern = /(routes\.rb|Gemfile)$/;
|
|
8
|
+
async detect(projectRoot, files) {
|
|
9
|
+
const gemfilePath = join(projectRoot, "Gemfile");
|
|
10
|
+
if (existsSync(gemfilePath)) {
|
|
11
|
+
const content = await readFile(gemfilePath, "utf-8");
|
|
12
|
+
if (content.includes("rails")) return true;
|
|
13
|
+
}
|
|
14
|
+
return files.some((f) => f.endsWith("routes.rb"));
|
|
15
|
+
}
|
|
16
|
+
async extractRoutes(filePath, content, ctx) {
|
|
17
|
+
const routes = [];
|
|
18
|
+
if (!filePath.endsWith("routes.rb")) {
|
|
19
|
+
return [];
|
|
20
|
+
}
|
|
21
|
+
const routeRegex = /\b(get|post|put|patch|delete)\s+['"]([^'"]+)['"]\s*,\s*to:\s*['"]([^'"]+)['"]/g;
|
|
22
|
+
let match;
|
|
23
|
+
while ((match = routeRegex.exec(content)) !== null) {
|
|
24
|
+
const verb = match[1].toUpperCase();
|
|
25
|
+
let pathVal = match[2];
|
|
26
|
+
const controllerAction = match[3];
|
|
27
|
+
pathVal = pathVal.replace(/:([a-zA-Z0-9_]+)/g, "{$1}");
|
|
28
|
+
routes.push(this.createRouteBinding(verb, pathVal, controllerAction, filePath, ctx));
|
|
29
|
+
}
|
|
30
|
+
const resourcesRegex = /\bresources\s+:([a-zA-Z0-9_]+)/g;
|
|
31
|
+
while ((match = resourcesRegex.exec(content)) !== null) {
|
|
32
|
+
const resourceName = match[1];
|
|
33
|
+
const controllerPrefix = resourceName.charAt(0).toUpperCase() + resourceName.slice(1);
|
|
34
|
+
const controllerClass = `${controllerPrefix}Controller`;
|
|
35
|
+
const crudEndpoints = [
|
|
36
|
+
{ verb: "GET", path: `/${resourceName}`, action: "index" },
|
|
37
|
+
{ verb: "GET", path: `/${resourceName}/new`, action: "new" },
|
|
38
|
+
{ verb: "POST", path: `/${resourceName}`, action: "create" },
|
|
39
|
+
{ verb: "GET", path: `/${resourceName}/{id}`, action: "show" },
|
|
40
|
+
{ verb: "GET", path: `/${resourceName}/{id}/edit`, action: "edit" },
|
|
41
|
+
{ verb: "PUT", path: `/${resourceName}/{id}`, action: "update" },
|
|
42
|
+
{ verb: "PATCH", path: `/${resourceName}/{id}`, action: "update" },
|
|
43
|
+
{ verb: "DELETE", path: `/${resourceName}/{id}`, action: "destroy" }
|
|
44
|
+
];
|
|
45
|
+
for (const endpoint of crudEndpoints) {
|
|
46
|
+
routes.push(this.createRouteBinding(endpoint.verb, endpoint.path, `${resourceName}#${endpoint.action}`, filePath, ctx));
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return routes;
|
|
50
|
+
}
|
|
51
|
+
createRouteBinding(verb, path, controllerAction, filePath, ctx) {
|
|
52
|
+
const parts = controllerAction.split("#");
|
|
53
|
+
const controller = parts[0];
|
|
54
|
+
const action = parts[1] || "index";
|
|
55
|
+
const controllerClassName = controller.charAt(0).toUpperCase() + controller.slice(1) + "Controller";
|
|
56
|
+
let resolvedFile = filePath;
|
|
57
|
+
const resolvedPath = ctx.resolveSymbolToFile(controllerClassName);
|
|
58
|
+
if (resolvedPath) {
|
|
59
|
+
resolvedFile = resolvedPath;
|
|
60
|
+
}
|
|
61
|
+
return {
|
|
62
|
+
framework: this.name,
|
|
63
|
+
method: verb,
|
|
64
|
+
path,
|
|
65
|
+
handlerFile: resolvedFile,
|
|
66
|
+
handlerSymbol: `${controllerClassName}#${action}`,
|
|
67
|
+
metadata: {
|
|
68
|
+
confidence: "inferred",
|
|
69
|
+
routeType: "server"
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
export {
|
|
75
|
+
RailsDetector
|
|
76
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { FrameworkDetector, ScanContext, RouteBinding } from '../../types.js';
|
|
2
|
+
|
|
3
|
+
declare class ReactRouterDetector implements FrameworkDetector {
|
|
4
|
+
readonly name = "react-router";
|
|
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 { ReactRouterDetector };
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { existsSync } from "node:fs";
|
|
4
|
+
class ReactRouterDetector {
|
|
5
|
+
name = "react-router";
|
|
6
|
+
language = "typescript";
|
|
7
|
+
filePattern = /\.(js|jsx|ts|tsx)$/;
|
|
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["react-router"] || deps["react-router-dom"])) {
|
|
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("Route") && !content.includes("createBrowserRouter")) {
|
|
25
|
+
return [];
|
|
26
|
+
}
|
|
27
|
+
const tagRegex = /<\/?Route\b|path=['"]([^'"]*)['"]|element=\{\s*<\s*([a-zA-Z0-9_]+)/g;
|
|
28
|
+
let match;
|
|
29
|
+
const pathStack = [];
|
|
30
|
+
let currentPath = null;
|
|
31
|
+
let currentElement = null;
|
|
32
|
+
let inOpeningTag = false;
|
|
33
|
+
const lines = content.split("\n");
|
|
34
|
+
for (const line of lines) {
|
|
35
|
+
if (line.includes("<Route")) {
|
|
36
|
+
const pathMatch = line.match(/\bpath=['"]([^'"]*)['"]/);
|
|
37
|
+
const elementMatch = line.match(/\belement=\{\s*<\s*([a-zA-Z0-9_]+)/);
|
|
38
|
+
const pathSeg = pathMatch ? pathMatch[1] : "";
|
|
39
|
+
const element = elementMatch ? elementMatch[1] : null;
|
|
40
|
+
if (line.trim().endsWith("/>")) {
|
|
41
|
+
const fullPath = "/" + [...pathStack, pathSeg].map((p) => p.replace(/^\/|\/$/g, "")).filter(Boolean).join("/");
|
|
42
|
+
if (element) {
|
|
43
|
+
let resolvedFile = filePath;
|
|
44
|
+
const resolvedPath = ctx.resolveSymbolToFile(element);
|
|
45
|
+
if (resolvedPath) {
|
|
46
|
+
resolvedFile = resolvedPath;
|
|
47
|
+
}
|
|
48
|
+
routes.push({
|
|
49
|
+
framework: this.name,
|
|
50
|
+
method: "GET",
|
|
51
|
+
path: fullPath,
|
|
52
|
+
handlerFile: resolvedFile,
|
|
53
|
+
handlerSymbol: element,
|
|
54
|
+
metadata: {
|
|
55
|
+
confidence: "inferred",
|
|
56
|
+
routeType: "client"
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
} else {
|
|
61
|
+
pathStack.push(pathSeg);
|
|
62
|
+
if (element) {
|
|
63
|
+
const fullPath = "/" + pathStack.map((p) => p.replace(/^\/|\/$/g, "")).filter(Boolean).join("/");
|
|
64
|
+
let resolvedFile = filePath;
|
|
65
|
+
const resolvedPath = ctx.resolveSymbolToFile(element);
|
|
66
|
+
if (resolvedPath) {
|
|
67
|
+
resolvedFile = resolvedPath;
|
|
68
|
+
}
|
|
69
|
+
routes.push({
|
|
70
|
+
framework: this.name,
|
|
71
|
+
method: "GET",
|
|
72
|
+
path: fullPath,
|
|
73
|
+
handlerFile: resolvedFile,
|
|
74
|
+
handlerSymbol: element,
|
|
75
|
+
metadata: {
|
|
76
|
+
confidence: "inferred",
|
|
77
|
+
routeType: "client"
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
} else if (line.includes("</Route>")) {
|
|
83
|
+
pathStack.pop();
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
const createRouterRegex = /createBrowserRouter\s*\(/;
|
|
87
|
+
if (createRouterRegex.test(content)) {
|
|
88
|
+
const configRegex = /path\s*:\s*['"]([^'"]+)['"]\s*,\s*element\s*:\s*<\s*([a-zA-Z0-9_]+)/g;
|
|
89
|
+
while ((match = configRegex.exec(content)) !== null) {
|
|
90
|
+
const routePath = match[1];
|
|
91
|
+
const element = match[2];
|
|
92
|
+
let resolvedFile = filePath;
|
|
93
|
+
const resolvedPath = ctx.resolveSymbolToFile(element);
|
|
94
|
+
if (resolvedPath) {
|
|
95
|
+
resolvedFile = resolvedPath;
|
|
96
|
+
}
|
|
97
|
+
routes.push({
|
|
98
|
+
framework: this.name,
|
|
99
|
+
method: "GET",
|
|
100
|
+
path: routePath,
|
|
101
|
+
handlerFile: resolvedFile,
|
|
102
|
+
handlerSymbol: element,
|
|
103
|
+
metadata: {
|
|
104
|
+
confidence: "inferred",
|
|
105
|
+
routeType: "client"
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return routes;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
export {
|
|
114
|
+
ReactRouterDetector
|
|
115
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { FrameworkDetector, ScanContext, RouteBinding } from '../../types.js';
|
|
2
|
+
|
|
3
|
+
declare class RustDetector implements FrameworkDetector {
|
|
4
|
+
readonly name = "rust-web";
|
|
5
|
+
readonly language = "rust";
|
|
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 { RustDetector };
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
class RustDetector {
|
|
4
|
+
name = "rust-web";
|
|
5
|
+
language = "rust";
|
|
6
|
+
filePattern = /\.rs$/;
|
|
7
|
+
async detect(projectRoot, files) {
|
|
8
|
+
const cargoPath = join(projectRoot, "Cargo.toml");
|
|
9
|
+
return existsSync(cargoPath) || files.some((f) => f.endsWith(".rs"));
|
|
10
|
+
}
|
|
11
|
+
async extractRoutes(filePath, content, ctx) {
|
|
12
|
+
const routes = [];
|
|
13
|
+
const axumRegex = /\.route\s*\(\s*['"]([^'"]+)['"]\s*,\s*([a-z0-9_]+)\s*\(\s*([a-zA-Z0-9_:]+)\s*\)\s*\)/g;
|
|
14
|
+
let match;
|
|
15
|
+
while ((match = axumRegex.exec(content)) !== null) {
|
|
16
|
+
const pathVal = match[1];
|
|
17
|
+
const verb = match[2].toUpperCase();
|
|
18
|
+
const handlerSymbol = match[3];
|
|
19
|
+
let resolvedFile = filePath;
|
|
20
|
+
const resolvedPath = ctx.resolveSymbolToFile(handlerSymbol);
|
|
21
|
+
if (resolvedPath) {
|
|
22
|
+
resolvedFile = resolvedPath;
|
|
23
|
+
}
|
|
24
|
+
routes.push({
|
|
25
|
+
framework: this.name,
|
|
26
|
+
method: verb,
|
|
27
|
+
path: pathVal,
|
|
28
|
+
handlerFile: resolvedFile,
|
|
29
|
+
handlerSymbol,
|
|
30
|
+
metadata: {
|
|
31
|
+
confidence: "inferred",
|
|
32
|
+
routeType: "server"
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
const attrRegex = /#\[(get|post|put|delete|patch)\s*\(\s*['"]([^'"]+)['"]\s*\)\]\s*(?:pub\s+)?(?:async\s+)?fn\s+([a-zA-Z0-9_]+)\b/g;
|
|
37
|
+
while ((match = attrRegex.exec(content)) !== null) {
|
|
38
|
+
const verb = match[1].toUpperCase();
|
|
39
|
+
let pathVal = match[2];
|
|
40
|
+
const handlerSymbol = match[3];
|
|
41
|
+
pathVal = pathVal.replace(/<([a-zA-Z0-9_]+)>/g, "{$1}");
|
|
42
|
+
routes.push({
|
|
43
|
+
framework: this.name,
|
|
44
|
+
method: verb,
|
|
45
|
+
path: pathVal,
|
|
46
|
+
handlerFile: filePath,
|
|
47
|
+
handlerSymbol,
|
|
48
|
+
metadata: {
|
|
49
|
+
confidence: "inferred",
|
|
50
|
+
routeType: "server"
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
return routes;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
export {
|
|
58
|
+
RustDetector
|
|
59
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { FrameworkDetector, ScanContext, RouteBinding } from '../../types.js';
|
|
2
|
+
|
|
3
|
+
declare class SpringDetector implements FrameworkDetector {
|
|
4
|
+
readonly name = "spring";
|
|
5
|
+
readonly language = "java";
|
|
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 { SpringDetector };
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
class SpringDetector {
|
|
4
|
+
name = "spring";
|
|
5
|
+
language = "java";
|
|
6
|
+
filePattern = /\.(java|kt)$/;
|
|
7
|
+
async detect(projectRoot, files) {
|
|
8
|
+
const hasPom = existsSync(join(projectRoot, "pom.xml"));
|
|
9
|
+
const hasGradle = existsSync(join(projectRoot, "build.gradle"));
|
|
10
|
+
if (hasPom || hasGradle) return true;
|
|
11
|
+
return files.some((f) => f.endsWith(".java") || f.endsWith(".kt"));
|
|
12
|
+
}
|
|
13
|
+
async extractRoutes(filePath, content, ctx) {
|
|
14
|
+
const routes = [];
|
|
15
|
+
if (!content.includes("Controller") && !content.includes("RequestMapping")) {
|
|
16
|
+
return [];
|
|
17
|
+
}
|
|
18
|
+
let classPrefix = "";
|
|
19
|
+
const classMappingMatch = content.match(/@RequestMapping\s*\(\s*['"]([^'"]+)['"]\s*\)/);
|
|
20
|
+
if (classMappingMatch) {
|
|
21
|
+
classPrefix = classMappingMatch[1];
|
|
22
|
+
} else {
|
|
23
|
+
const restControllerMatch = content.match(/@RestController\s*\(\s*['"]([^'"]+)['"]\s*\)/);
|
|
24
|
+
if (restControllerMatch) {
|
|
25
|
+
classPrefix = restControllerMatch[1];
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
const classDeclMatch = content.match(/\bclass\s+([a-zA-Z0-9_]+)\b/);
|
|
29
|
+
const className = classDeclMatch ? classDeclMatch[1] : "Controller";
|
|
30
|
+
const mappingRegex = /@(Get|Post|Put|Delete|Patch)Mapping(?:\s*\(\s*['"]([^'"]+)['"]\s*\))?[^({]*?\b([a-zA-Z0-9_]+)\s*\(/g;
|
|
31
|
+
let match;
|
|
32
|
+
while ((match = mappingRegex.exec(content)) !== null) {
|
|
33
|
+
const mappingType = match[1].toUpperCase();
|
|
34
|
+
const mappingPath = match[2] || "";
|
|
35
|
+
const methodName = match[3];
|
|
36
|
+
const cleanClassPrefix = classPrefix.replace(/^\/|\/$/g, "");
|
|
37
|
+
const cleanMethodPath = mappingPath.replace(/^\/|\/$/g, "");
|
|
38
|
+
const combinedPath = "/" + [cleanClassPrefix, cleanMethodPath].filter(Boolean).join("/");
|
|
39
|
+
routes.push({
|
|
40
|
+
framework: this.name,
|
|
41
|
+
method: mappingType,
|
|
42
|
+
path: combinedPath,
|
|
43
|
+
handlerFile: filePath,
|
|
44
|
+
handlerSymbol: `${className}.${methodName}`,
|
|
45
|
+
metadata: {
|
|
46
|
+
confidence: "inferred",
|
|
47
|
+
routeType: "server"
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
return routes;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
export {
|
|
55
|
+
SpringDetector
|
|
56
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { FrameworkDetector, ScanContext, RouteBinding } from '../../types.js';
|
|
2
|
+
|
|
3
|
+
declare class SvelteKitDetector implements FrameworkDetector {
|
|
4
|
+
readonly name = "sveltekit";
|
|
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 { SvelteKitDetector };
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { existsSync } from "node:fs";
|
|
4
|
+
class SvelteKitDetector {
|
|
5
|
+
name = "sveltekit";
|
|
6
|
+
language = "typescript";
|
|
7
|
+
filePattern = /\.(svelte|ts|js)$/;
|
|
8
|
+
async detect(projectRoot, files) {
|
|
9
|
+
const svelteConfigPath = join(projectRoot, "svelte.config.js");
|
|
10
|
+
if (existsSync(svelteConfigPath)) {
|
|
11
|
+
return true;
|
|
12
|
+
}
|
|
13
|
+
const packageJsonPath = join(projectRoot, "package.json");
|
|
14
|
+
if (existsSync(packageJsonPath)) {
|
|
15
|
+
try {
|
|
16
|
+
const pkg = JSON.parse(await readFile(packageJsonPath, "utf-8"));
|
|
17
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
18
|
+
if (deps && (deps["@sveltejs/kit"] || deps["svelte"])) {
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
} catch {
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
async extractRoutes(filePath, content, ctx) {
|
|
27
|
+
const routes = [];
|
|
28
|
+
const pathNormalized = filePath.replace(/\\/g, "/");
|
|
29
|
+
if (pathNormalized.includes("src/routes/")) {
|
|
30
|
+
const routesIndex = pathNormalized.indexOf("src/routes/");
|
|
31
|
+
let routePart = pathNormalized.substring(routesIndex + 11);
|
|
32
|
+
if (routePart.endsWith("+page.svelte")) {
|
|
33
|
+
routePart = routePart.replace(/\/\+page\.svelte$/, "");
|
|
34
|
+
let routePath = "/" + routePart;
|
|
35
|
+
if (routePath === "/+") routePath = "/";
|
|
36
|
+
if (routePath === "") routePath = "/";
|
|
37
|
+
routePath = routePath.replace(/\[([a-zA-Z0-9_]+)\]/g, "{$1}");
|
|
38
|
+
routes.push({
|
|
39
|
+
framework: this.name,
|
|
40
|
+
method: "GET",
|
|
41
|
+
path: routePath,
|
|
42
|
+
handlerFile: filePath,
|
|
43
|
+
handlerSymbol: "default",
|
|
44
|
+
metadata: {
|
|
45
|
+
confidence: "inferred",
|
|
46
|
+
routeType: "client"
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
if (routePart.endsWith("+server.ts") || routePart.endsWith("+server.js")) {
|
|
51
|
+
routePart = routePart.replace(/\/\+server\.[a-zA-Z0-9]+$/, "");
|
|
52
|
+
let routePath = "/" + routePart;
|
|
53
|
+
if (routePath === "/+") routePath = "/";
|
|
54
|
+
if (routePath === "") routePath = "/";
|
|
55
|
+
routePath = routePath.replace(/\[([a-zA-Z0-9_]+)\]/g, "{$1}");
|
|
56
|
+
const verbRegex = /export\s+(?:const|async\s+function)\s+(GET|POST|PUT|DELETE|PATCH)\b/g;
|
|
57
|
+
let match;
|
|
58
|
+
while ((match = verbRegex.exec(content)) !== null) {
|
|
59
|
+
const verb = match[1];
|
|
60
|
+
routes.push({
|
|
61
|
+
framework: this.name,
|
|
62
|
+
method: verb,
|
|
63
|
+
path: routePath,
|
|
64
|
+
handlerFile: filePath,
|
|
65
|
+
handlerSymbol: verb,
|
|
66
|
+
metadata: {
|
|
67
|
+
confidence: "inferred",
|
|
68
|
+
routeType: "server"
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
if (routePart.endsWith("+page.server.ts") || routePart.endsWith("+page.server.js")) {
|
|
74
|
+
routePart = routePart.replace(/\/\+page\.server\.[a-zA-Z0-9]+$/, "");
|
|
75
|
+
let routePath = "/" + routePart;
|
|
76
|
+
if (routePath === "/+") routePath = "/";
|
|
77
|
+
if (routePath === "") routePath = "/";
|
|
78
|
+
routePath = routePath.replace(/\[([a-zA-Z0-9_]+)\]/g, "{$1}");
|
|
79
|
+
const actionsIndex = content.indexOf("export const actions");
|
|
80
|
+
if (actionsIndex !== -1) {
|
|
81
|
+
const braceStart = content.indexOf("{", actionsIndex);
|
|
82
|
+
if (braceStart !== -1) {
|
|
83
|
+
const bodyObj = getBracedContent(content, braceStart);
|
|
84
|
+
if (bodyObj) {
|
|
85
|
+
const actionKeysRegex = /\b([a-zA-Z0-9_]+)\s*:/g;
|
|
86
|
+
let keyMatch;
|
|
87
|
+
while ((keyMatch = actionKeysRegex.exec(bodyObj.content)) !== null) {
|
|
88
|
+
const actionName = keyMatch[1];
|
|
89
|
+
routes.push({
|
|
90
|
+
framework: this.name,
|
|
91
|
+
method: "POST",
|
|
92
|
+
path: actionName === "default" ? routePath : `${routePath}?/${actionName}`,
|
|
93
|
+
handlerFile: filePath,
|
|
94
|
+
handlerSymbol: `actions.${actionName}`,
|
|
95
|
+
metadata: {
|
|
96
|
+
confidence: "inferred",
|
|
97
|
+
routeType: "server"
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return routes;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
function getBracedContent(str, startIndex) {
|
|
110
|
+
let depth = 0;
|
|
111
|
+
let inString = false;
|
|
112
|
+
let stringChar = "";
|
|
113
|
+
let escape = false;
|
|
114
|
+
for (let i = startIndex; i < str.length; i++) {
|
|
115
|
+
const char = str[i];
|
|
116
|
+
if (escape) {
|
|
117
|
+
escape = false;
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
if (char === "\\") {
|
|
121
|
+
escape = true;
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
if (inString) {
|
|
125
|
+
if (char === stringChar) {
|
|
126
|
+
inString = false;
|
|
127
|
+
}
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
if (char === "'" || char === '"' || char === "`") {
|
|
131
|
+
inString = true;
|
|
132
|
+
stringChar = char;
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
if (char === "{") {
|
|
136
|
+
depth++;
|
|
137
|
+
if (depth === 1) {
|
|
138
|
+
startIndex = i + 1;
|
|
139
|
+
}
|
|
140
|
+
} else if (char === "}") {
|
|
141
|
+
depth--;
|
|
142
|
+
if (depth === 0) {
|
|
143
|
+
return {
|
|
144
|
+
content: str.substring(startIndex, i),
|
|
145
|
+
endIndex: i + 1
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
export {
|
|
153
|
+
SvelteKitDetector
|
|
154
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { FrameworkDetector, ScanContext, RouteBinding, HookBinding } from '../../types.js';
|
|
2
|
+
|
|
3
|
+
declare class SymfonyDetector implements FrameworkDetector {
|
|
4
|
+
readonly name = "symfony";
|
|
5
|
+
readonly language = "php";
|
|
6
|
+
readonly filePattern: RegExp;
|
|
7
|
+
detect(projectRoot: string, files: string[]): Promise<boolean>;
|
|
8
|
+
extractRoutes(filePath: string, content: string, ctx: ScanContext): Promise<RouteBinding[]>;
|
|
9
|
+
private createYamlRouteBindings;
|
|
10
|
+
extractHooks(filePath: string, content: string, ctx: ScanContext): Promise<HookBinding[]>;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export { SymfonyDetector };
|