@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,135 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { existsSync } from "node:fs";
|
|
4
|
+
class DjangoDetector {
|
|
5
|
+
name = "django";
|
|
6
|
+
language = "python";
|
|
7
|
+
filePattern = /urls\.py$/;
|
|
8
|
+
projectFiles = [];
|
|
9
|
+
async detect(projectRoot, files) {
|
|
10
|
+
this.projectFiles = files;
|
|
11
|
+
const hasManagePy = files.some((f) => f.endsWith("manage.py"));
|
|
12
|
+
if (hasManagePy) return true;
|
|
13
|
+
for (const file of ["requirements.txt", "Pipfile", "pyproject.toml"]) {
|
|
14
|
+
const filePath = join(projectRoot, file);
|
|
15
|
+
if (existsSync(filePath)) {
|
|
16
|
+
try {
|
|
17
|
+
const content = await readFile(filePath, "utf-8");
|
|
18
|
+
if (content.toLowerCase().includes("django")) {
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
} catch {
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
async extractRoutes(filePath, content, ctx) {
|
|
28
|
+
const routes = [];
|
|
29
|
+
const visitedFiles = /* @__PURE__ */ new Set();
|
|
30
|
+
await this.parseUrlsFile(filePath, content, "", ctx, routes, visitedFiles);
|
|
31
|
+
return routes;
|
|
32
|
+
}
|
|
33
|
+
async parseUrlsFile(filePath, content, prefix, ctx, routes, visitedFiles) {
|
|
34
|
+
if (visitedFiles.has(filePath)) return;
|
|
35
|
+
visitedFiles.add(filePath);
|
|
36
|
+
const pathRegex = /\b(path|re_path|url)\s*\(\s*['"]([^'"]*)['"]\s*,\s*([^)]+)\)/g;
|
|
37
|
+
let match;
|
|
38
|
+
while ((match = pathRegex.exec(content)) !== null) {
|
|
39
|
+
const routePath = match[2];
|
|
40
|
+
const handlerStr = match[3].trim();
|
|
41
|
+
const cleanPrefix = prefix.replace(/^\/|\/$/g, "");
|
|
42
|
+
const cleanRoute = routePath.replace(/^\/|\/$/g, "");
|
|
43
|
+
const fullPath = "/" + [cleanPrefix, cleanRoute].filter(Boolean).join("/");
|
|
44
|
+
const includeMatch = handlerStr.match(/include\s*\(\s*['"]([^'"]+)['"]/);
|
|
45
|
+
if (includeMatch) {
|
|
46
|
+
const includedModule = includeMatch[1];
|
|
47
|
+
const resolvedPath = this.resolveModulePath(includedModule);
|
|
48
|
+
if (resolvedPath) {
|
|
49
|
+
const absResolvedPath = join(ctx.workspaceRoot, resolvedPath);
|
|
50
|
+
if (existsSync(absResolvedPath)) {
|
|
51
|
+
try {
|
|
52
|
+
const subContent = await readFile(absResolvedPath, "utf-8");
|
|
53
|
+
await this.parseUrlsFile(resolvedPath, subContent, fullPath, ctx, routes, visitedFiles);
|
|
54
|
+
} catch {
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
} else {
|
|
59
|
+
let handlerSymbol = handlerStr.split(",")[0].trim();
|
|
60
|
+
handlerSymbol = handlerSymbol.replace(/\.as_view\s*\([^)]*\)/, "");
|
|
61
|
+
let resolvedFile = filePath;
|
|
62
|
+
const resolvedPath = ctx.resolveSymbolToFile(handlerSymbol);
|
|
63
|
+
if (resolvedPath) {
|
|
64
|
+
resolvedFile = resolvedPath;
|
|
65
|
+
}
|
|
66
|
+
routes.push({
|
|
67
|
+
framework: this.name,
|
|
68
|
+
method: "GET",
|
|
69
|
+
// Django defaults to GET/any unless method-check decorators are used
|
|
70
|
+
path: fullPath,
|
|
71
|
+
handlerFile: resolvedFile,
|
|
72
|
+
handlerSymbol,
|
|
73
|
+
metadata: {
|
|
74
|
+
confidence: "inferred"
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
const routerRegex = /router\.register\s*\(\s*r?['"]([^'"]+)['"]\s*,\s*([a-zA-Z0-9_]+)/g;
|
|
80
|
+
while ((match = routerRegex.exec(content)) !== null) {
|
|
81
|
+
const routerPath = match[1];
|
|
82
|
+
const viewSetSymbol = match[2];
|
|
83
|
+
const cleanPrefix = prefix.replace(/^\/|\/$/g, "");
|
|
84
|
+
const cleanRoute = routerPath.replace(/^\/|\/$/g, "");
|
|
85
|
+
const fullPath = "/" + [cleanPrefix, cleanRoute].filter(Boolean).join("/");
|
|
86
|
+
let resolvedFile = filePath;
|
|
87
|
+
const resolvedPath = ctx.resolveSymbolToFile(viewSetSymbol);
|
|
88
|
+
if (resolvedPath) {
|
|
89
|
+
resolvedFile = resolvedPath;
|
|
90
|
+
}
|
|
91
|
+
const verbs = [
|
|
92
|
+
{ method: "GET", suffix: "/", symbolSuffix: ".list" },
|
|
93
|
+
{ method: "POST", suffix: "/", symbolSuffix: ".create" },
|
|
94
|
+
{ method: "GET", suffix: "/{id}/", symbolSuffix: ".retrieve" },
|
|
95
|
+
{ method: "PUT", suffix: "/{id}/", symbolSuffix: ".update" },
|
|
96
|
+
{ method: "PATCH", suffix: "/{id}/", symbolSuffix: ".partial_update" },
|
|
97
|
+
{ method: "DELETE", suffix: "/{id}/", symbolSuffix: ".destroy" }
|
|
98
|
+
];
|
|
99
|
+
for (const v of verbs) {
|
|
100
|
+
const cleanSuffix = v.suffix.replace(/^\/|\/$/g, "");
|
|
101
|
+
const finalPath = "/" + [fullPath.replace(/^\/|\/$/g, ""), cleanSuffix].filter(Boolean).join("/");
|
|
102
|
+
routes.push({
|
|
103
|
+
framework: this.name,
|
|
104
|
+
method: v.method,
|
|
105
|
+
path: finalPath,
|
|
106
|
+
handlerFile: resolvedFile,
|
|
107
|
+
handlerSymbol: `${viewSetSymbol}${v.symbolSuffix}`,
|
|
108
|
+
metadata: {
|
|
109
|
+
confidence: "inferred",
|
|
110
|
+
resourceType: "drf_viewset"
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
resolveModulePath(moduleStr) {
|
|
117
|
+
const targetRelPath = moduleStr.replace(/\./g, "/") + ".py";
|
|
118
|
+
for (const f of this.projectFiles) {
|
|
119
|
+
if (f.endsWith(targetRelPath)) {
|
|
120
|
+
return f;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
const parts = moduleStr.split(".");
|
|
124
|
+
const lastPart = parts[parts.length - 1] + ".py";
|
|
125
|
+
for (const f of this.projectFiles) {
|
|
126
|
+
if (f.endsWith(lastPart)) {
|
|
127
|
+
return f;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
export {
|
|
134
|
+
DjangoDetector
|
|
135
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { FrameworkDetector, ScanContext, RouteBinding, HookBinding } from '../../types.js';
|
|
2
|
+
|
|
3
|
+
declare class DrupalDetector implements FrameworkDetector {
|
|
4
|
+
readonly name = "drupal";
|
|
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 createRouteBinding;
|
|
10
|
+
extractHooks(filePath: string, content: string, ctx: ScanContext): Promise<HookBinding[]>;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export { DrupalDetector };
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { join, basename } from "node:path";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
class DrupalDetector {
|
|
4
|
+
name = "drupal";
|
|
5
|
+
language = "php";
|
|
6
|
+
filePattern = /\.(yml|module|php)$/;
|
|
7
|
+
async detect(projectRoot, files) {
|
|
8
|
+
const corePath = join(projectRoot, "core/lib/Drupal.php");
|
|
9
|
+
if (existsSync(corePath)) return true;
|
|
10
|
+
const hasRoutingYml = files.some((f) => f.endsWith(".routing.yml"));
|
|
11
|
+
if (hasRoutingYml) return true;
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
async extractRoutes(filePath, content, ctx) {
|
|
15
|
+
const routes = [];
|
|
16
|
+
if (!filePath.endsWith(".routing.yml")) {
|
|
17
|
+
return [];
|
|
18
|
+
}
|
|
19
|
+
const lines = content.split("\n");
|
|
20
|
+
let currentRouteId = "";
|
|
21
|
+
let currentPath = "";
|
|
22
|
+
let currentController = "";
|
|
23
|
+
for (const line of lines) {
|
|
24
|
+
const routeMatch = line.match(/^([a-z0-9_.-]+):/);
|
|
25
|
+
if (routeMatch) {
|
|
26
|
+
if (currentPath && currentController) {
|
|
27
|
+
routes.push(this.createRouteBinding(currentPath, currentController, filePath, ctx));
|
|
28
|
+
}
|
|
29
|
+
currentRouteId = routeMatch[1];
|
|
30
|
+
currentPath = "";
|
|
31
|
+
currentController = "";
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
const pathMatch = line.match(/^\s+path:\s*['"]?([^'"]+)['"]?/);
|
|
35
|
+
if (pathMatch) {
|
|
36
|
+
currentPath = pathMatch[1];
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
const controllerMatch = line.match(/^\s+_(?:controller|form):\s*['"]?([^'"]+)['"]?/);
|
|
40
|
+
if (controllerMatch) {
|
|
41
|
+
currentController = controllerMatch[1];
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (currentPath && currentController) {
|
|
45
|
+
routes.push(this.createRouteBinding(currentPath, currentController, filePath, ctx));
|
|
46
|
+
}
|
|
47
|
+
return routes;
|
|
48
|
+
}
|
|
49
|
+
createRouteBinding(path, controller, filePath, ctx) {
|
|
50
|
+
const parts = controller.split("::");
|
|
51
|
+
const controllerClass = parts[0];
|
|
52
|
+
const controllerMethod = parts[1] || "default";
|
|
53
|
+
const symbol = controllerClass.split("\\").pop() || "Controller";
|
|
54
|
+
let resolvedFile = filePath;
|
|
55
|
+
const resolvedPath = ctx.resolveSymbolToFile(symbol);
|
|
56
|
+
if (resolvedPath) {
|
|
57
|
+
resolvedFile = resolvedPath;
|
|
58
|
+
}
|
|
59
|
+
return {
|
|
60
|
+
framework: this.name,
|
|
61
|
+
method: "ALL",
|
|
62
|
+
path,
|
|
63
|
+
handlerFile: resolvedFile,
|
|
64
|
+
handlerSymbol: `${symbol}::${controllerMethod}`,
|
|
65
|
+
metadata: {
|
|
66
|
+
confidence: "inferred",
|
|
67
|
+
routeType: "server"
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
async extractHooks(filePath, content, ctx) {
|
|
72
|
+
if (!filePath.endsWith(".module")) {
|
|
73
|
+
return [];
|
|
74
|
+
}
|
|
75
|
+
const hooks = [];
|
|
76
|
+
const moduleName = basename(filePath, ".module");
|
|
77
|
+
const functionRegex = new RegExp(`function\\s+(${moduleName})_([a-zA-Z0-9_]+)\\s*\\(`, "g");
|
|
78
|
+
let match;
|
|
79
|
+
while ((match = functionRegex.exec(content)) !== null) {
|
|
80
|
+
const hookName = match[2];
|
|
81
|
+
hooks.push({
|
|
82
|
+
framework: this.name,
|
|
83
|
+
hookName,
|
|
84
|
+
handlerFile: filePath,
|
|
85
|
+
handlerSymbol: `${moduleName}_${hookName}`,
|
|
86
|
+
hookType: "hook"
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
return hooks;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
export {
|
|
93
|
+
DrupalDetector
|
|
94
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { FrameworkDetector, ScanContext, RouteBinding } from '../../types.js';
|
|
2
|
+
|
|
3
|
+
declare class ExpressDetector implements FrameworkDetector {
|
|
4
|
+
readonly name = "express";
|
|
5
|
+
readonly language = "typescript";
|
|
6
|
+
readonly filePattern: RegExp;
|
|
7
|
+
private routerPrefixes;
|
|
8
|
+
detect(projectRoot: string, files: string[]): Promise<boolean>;
|
|
9
|
+
extractRoutes(filePath: string, content: string, ctx: ScanContext): Promise<RouteBinding[]>;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export { ExpressDetector };
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { existsSync } from "node:fs";
|
|
4
|
+
class ExpressDetector {
|
|
5
|
+
name = "express";
|
|
6
|
+
language = "typescript";
|
|
7
|
+
filePattern = /\.(js|ts)$/;
|
|
8
|
+
routerPrefixes = /* @__PURE__ */ new Map();
|
|
9
|
+
// router variable -> prefix
|
|
10
|
+
async detect(projectRoot, files) {
|
|
11
|
+
let isExpress = false;
|
|
12
|
+
const packageJsonPath = join(projectRoot, "package.json");
|
|
13
|
+
if (existsSync(packageJsonPath)) {
|
|
14
|
+
try {
|
|
15
|
+
const pkg = JSON.parse(await readFile(packageJsonPath, "utf-8"));
|
|
16
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
17
|
+
if (deps && deps.express) {
|
|
18
|
+
isExpress = true;
|
|
19
|
+
}
|
|
20
|
+
} catch {
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
if (isExpress) {
|
|
24
|
+
for (const file of files) {
|
|
25
|
+
if (!file.match(this.filePattern) || file.includes("node_modules")) continue;
|
|
26
|
+
try {
|
|
27
|
+
const content = await readFile(join(projectRoot, file), "utf-8");
|
|
28
|
+
const useRegex = /([a-zA-Z0-9_]+)\.use\s*\(\s*['"]([^'"]+)['"]\s*,\s*([a-zA-Z0-9_]+)\)/g;
|
|
29
|
+
let match;
|
|
30
|
+
while ((match = useRegex.exec(content)) !== null) {
|
|
31
|
+
const prefix = match[2];
|
|
32
|
+
const routerVar = match[3];
|
|
33
|
+
this.routerPrefixes.set(routerVar, prefix);
|
|
34
|
+
}
|
|
35
|
+
} catch {
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return isExpress;
|
|
40
|
+
}
|
|
41
|
+
async extractRoutes(filePath, content, ctx) {
|
|
42
|
+
const routes = [];
|
|
43
|
+
if (!content.includes("express") && !content.includes("Router") && !content.includes(".get") && !content.includes(".post")) {
|
|
44
|
+
return [];
|
|
45
|
+
}
|
|
46
|
+
let index = 0;
|
|
47
|
+
while (true) {
|
|
48
|
+
const match = content.substring(index).match(/([a-zA-Z0-9_]+)\.(get|post|put|delete|patch|options|use)\s*\(/);
|
|
49
|
+
if (!match) break;
|
|
50
|
+
const targetVar = match[1];
|
|
51
|
+
const method = match[2].toUpperCase();
|
|
52
|
+
const startOfParen = index + match.index + match[0].length - 1;
|
|
53
|
+
const paren = getParenthesizedContent(content, startOfParen);
|
|
54
|
+
if (!paren) {
|
|
55
|
+
index = startOfParen + 1;
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
const args = parseArgs(paren.content);
|
|
59
|
+
const routePath = args[0] || "";
|
|
60
|
+
if (args.length >= 2 && method !== "USE") {
|
|
61
|
+
const handlerSymbol = args[args.length - 1];
|
|
62
|
+
const middlewares = args.slice(1, args.length - 1);
|
|
63
|
+
const routerPrefix = this.routerPrefixes.get(targetVar) || "";
|
|
64
|
+
const cleanPrefix = routerPrefix.replace(/^\/|\/$/g, "");
|
|
65
|
+
const cleanRoute = routePath.replace(/^\/|\/$/g, "");
|
|
66
|
+
const fullPath = "/" + [cleanPrefix, cleanRoute].filter(Boolean).join("/");
|
|
67
|
+
let resolvedFile = filePath;
|
|
68
|
+
const resolvedPath = ctx.resolveSymbolToFile(handlerSymbol);
|
|
69
|
+
if (resolvedPath) {
|
|
70
|
+
resolvedFile = resolvedPath;
|
|
71
|
+
}
|
|
72
|
+
routes.push({
|
|
73
|
+
framework: this.name,
|
|
74
|
+
method,
|
|
75
|
+
path: fullPath,
|
|
76
|
+
handlerFile: resolvedFile,
|
|
77
|
+
handlerSymbol,
|
|
78
|
+
metadata: {
|
|
79
|
+
confidence: "inferred",
|
|
80
|
+
middlewares: middlewares.filter((m) => !m.includes("function") && !m.includes("=>"))
|
|
81
|
+
// filter inline callbacks
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
index = paren.endIndex;
|
|
86
|
+
}
|
|
87
|
+
let routeIndex = 0;
|
|
88
|
+
while (true) {
|
|
89
|
+
const routeMatch = content.substring(routeIndex).match(/([a-zA-Z0-9_]+)\.route\s*\(\s*['"]([^'"]+)['"]\s*\)/);
|
|
90
|
+
if (!routeMatch) break;
|
|
91
|
+
const routePath = routeMatch[2];
|
|
92
|
+
const startOfChain = routeIndex + routeMatch.index + routeMatch[0].length;
|
|
93
|
+
const trailingSlice = content.substring(startOfChain);
|
|
94
|
+
const chainRegex = /\s*\.(get|post|put|delete|patch|options)\s*\(\s*([^)]+)\)/g;
|
|
95
|
+
let chainMatch;
|
|
96
|
+
let lastMatchEnd = 0;
|
|
97
|
+
while ((chainMatch = chainRegex.exec(trailingSlice)) !== null) {
|
|
98
|
+
const matchIndex = chainMatch.index;
|
|
99
|
+
const skipped = trailingSlice.substring(lastMatchEnd, matchIndex);
|
|
100
|
+
if (skipped.trim() !== "") {
|
|
101
|
+
break;
|
|
102
|
+
}
|
|
103
|
+
const method = chainMatch[1].toUpperCase();
|
|
104
|
+
const handlerSymbol = chainMatch[2].trim();
|
|
105
|
+
let resolvedFile = filePath;
|
|
106
|
+
const resolvedPath = ctx.resolveSymbolToFile(handlerSymbol);
|
|
107
|
+
if (resolvedPath) {
|
|
108
|
+
resolvedFile = resolvedPath;
|
|
109
|
+
}
|
|
110
|
+
routes.push({
|
|
111
|
+
framework: this.name,
|
|
112
|
+
method,
|
|
113
|
+
path: routePath,
|
|
114
|
+
handlerFile: resolvedFile,
|
|
115
|
+
handlerSymbol,
|
|
116
|
+
metadata: {
|
|
117
|
+
confidence: "inferred"
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
lastMatchEnd = chainRegex.lastIndex;
|
|
121
|
+
}
|
|
122
|
+
routeIndex = startOfChain;
|
|
123
|
+
}
|
|
124
|
+
return routes;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
function getParenthesizedContent(str, startIndex) {
|
|
128
|
+
let depth = 0;
|
|
129
|
+
let inString = false;
|
|
130
|
+
let stringChar = "";
|
|
131
|
+
let escape = false;
|
|
132
|
+
for (let i = startIndex; i < str.length; i++) {
|
|
133
|
+
const char = str[i];
|
|
134
|
+
if (escape) {
|
|
135
|
+
escape = false;
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
if (char === "\\") {
|
|
139
|
+
escape = true;
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
if (inString) {
|
|
143
|
+
if (char === stringChar) {
|
|
144
|
+
inString = false;
|
|
145
|
+
}
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
if (char === "'" || char === '"' || char === "`") {
|
|
149
|
+
inString = true;
|
|
150
|
+
stringChar = char;
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
if (char === "(") {
|
|
154
|
+
depth++;
|
|
155
|
+
if (depth === 1) {
|
|
156
|
+
startIndex = i + 1;
|
|
157
|
+
}
|
|
158
|
+
} else if (char === ")") {
|
|
159
|
+
depth--;
|
|
160
|
+
if (depth === 0) {
|
|
161
|
+
return {
|
|
162
|
+
content: str.substring(startIndex, i),
|
|
163
|
+
endIndex: i + 1
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
function parseArgs(argsStr) {
|
|
171
|
+
const args = [];
|
|
172
|
+
let current = "";
|
|
173
|
+
let depth = 0;
|
|
174
|
+
let bracketDepth = 0;
|
|
175
|
+
let braceDepth = 0;
|
|
176
|
+
let inString = false;
|
|
177
|
+
let stringChar = "";
|
|
178
|
+
let escape = false;
|
|
179
|
+
for (let i = 0; i < argsStr.length; i++) {
|
|
180
|
+
const char = argsStr[i];
|
|
181
|
+
if (escape) {
|
|
182
|
+
escape = false;
|
|
183
|
+
current += char;
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
if (char === "\\") {
|
|
187
|
+
escape = true;
|
|
188
|
+
current += char;
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
if (inString) {
|
|
192
|
+
if (char === stringChar) {
|
|
193
|
+
inString = false;
|
|
194
|
+
}
|
|
195
|
+
current += char;
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
if (char === "'" || char === '"' || char === "`") {
|
|
199
|
+
inString = true;
|
|
200
|
+
stringChar = char;
|
|
201
|
+
current += char;
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
if (char === "(") {
|
|
205
|
+
depth++;
|
|
206
|
+
} else if (char === ")") {
|
|
207
|
+
depth--;
|
|
208
|
+
} else if (char === "[") {
|
|
209
|
+
bracketDepth++;
|
|
210
|
+
} else if (char === "]") {
|
|
211
|
+
bracketDepth--;
|
|
212
|
+
} else if (char === "{") {
|
|
213
|
+
braceDepth++;
|
|
214
|
+
} else if (char === "}") {
|
|
215
|
+
braceDepth--;
|
|
216
|
+
}
|
|
217
|
+
if (char === "," && depth === 0 && bracketDepth === 0 && braceDepth === 0) {
|
|
218
|
+
args.push(cleanQuotes(current.trim()));
|
|
219
|
+
current = "";
|
|
220
|
+
} else {
|
|
221
|
+
current += char;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
if (current.trim()) {
|
|
225
|
+
args.push(cleanQuotes(current.trim()));
|
|
226
|
+
}
|
|
227
|
+
return args;
|
|
228
|
+
}
|
|
229
|
+
function cleanQuotes(str) {
|
|
230
|
+
return str.replace(/^['"`]|['"`]$/g, "");
|
|
231
|
+
}
|
|
232
|
+
export {
|
|
233
|
+
ExpressDetector
|
|
234
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { FrameworkDetector, ScanContext, RouteBinding } from '../../types.js';
|
|
2
|
+
|
|
3
|
+
declare class FastAPIDetector implements FrameworkDetector {
|
|
4
|
+
readonly name = "fastapi";
|
|
5
|
+
readonly language = "python";
|
|
6
|
+
readonly filePattern: RegExp;
|
|
7
|
+
private routerPrefixes;
|
|
8
|
+
detect(projectRoot: string, files: string[]): Promise<boolean>;
|
|
9
|
+
extractRoutes(filePath: string, content: string, ctx: ScanContext): Promise<RouteBinding[]>;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export { FastAPIDetector };
|