@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,203 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { existsSync } from "node:fs";
|
|
4
|
+
class FastAPIDetector {
|
|
5
|
+
name = "fastapi";
|
|
6
|
+
language = "python";
|
|
7
|
+
filePattern = /\.py$/;
|
|
8
|
+
routerPrefixes = /* @__PURE__ */ new Map();
|
|
9
|
+
// router variable -> prefix
|
|
10
|
+
async detect(projectRoot, files) {
|
|
11
|
+
let isFastAPI = false;
|
|
12
|
+
for (const file of ["requirements.txt", "Pipfile", "pyproject.toml"]) {
|
|
13
|
+
const filePath = join(projectRoot, file);
|
|
14
|
+
if (existsSync(filePath)) {
|
|
15
|
+
try {
|
|
16
|
+
const content = await readFile(filePath, "utf-8");
|
|
17
|
+
if (content.toLowerCase().includes("fastapi")) {
|
|
18
|
+
isFastAPI = true;
|
|
19
|
+
}
|
|
20
|
+
} catch {
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
if (!isFastAPI) {
|
|
25
|
+
isFastAPI = files.some((f) => f.endsWith("main.py"));
|
|
26
|
+
}
|
|
27
|
+
if (isFastAPI) {
|
|
28
|
+
for (const file of files) {
|
|
29
|
+
if (!file.endsWith(".py")) continue;
|
|
30
|
+
try {
|
|
31
|
+
const content = await readFile(join(projectRoot, file), "utf-8");
|
|
32
|
+
const routerDefRegex = /(\w+)\s*=\s*APIRouter\s*\(\s*(?:prefix\s*=\s*)?['"]([^'"]+)['"]/g;
|
|
33
|
+
let match;
|
|
34
|
+
while ((match = routerDefRegex.exec(content)) !== null) {
|
|
35
|
+
const varName = match[1];
|
|
36
|
+
const prefix = match[2];
|
|
37
|
+
this.routerPrefixes.set(varName, prefix);
|
|
38
|
+
}
|
|
39
|
+
const routerIncRegex = /include_router\s*\(\s*([^,\s)]+)(?:,\s*prefix\s*=\s*['"]([^'"]+)['"])?/g;
|
|
40
|
+
while ((match = routerIncRegex.exec(content)) !== null) {
|
|
41
|
+
const routerVar = match[1].split(".").pop() || "";
|
|
42
|
+
const prefix = match[2];
|
|
43
|
+
if (prefix) {
|
|
44
|
+
this.routerPrefixes.set(routerVar, prefix);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
} catch {
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return isFastAPI;
|
|
52
|
+
}
|
|
53
|
+
async extractRoutes(filePath, content, ctx) {
|
|
54
|
+
const routes = [];
|
|
55
|
+
if (!content.includes("@") || !content.includes(".")) {
|
|
56
|
+
return [];
|
|
57
|
+
}
|
|
58
|
+
let index = 0;
|
|
59
|
+
while (true) {
|
|
60
|
+
const match = content.substring(index).match(/@(\w+)\.(get|post|put|delete|patch|options|head|trace)\s*\(/);
|
|
61
|
+
if (!match) break;
|
|
62
|
+
const targetVar = match[1];
|
|
63
|
+
const method = match[2].toUpperCase();
|
|
64
|
+
const startOfParen = index + match.index + match[0].length - 1;
|
|
65
|
+
const paren = getParenthesizedContent(content, startOfParen);
|
|
66
|
+
if (!paren) {
|
|
67
|
+
index = startOfParen + 1;
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
const args = parseArgs(paren.content);
|
|
71
|
+
const routePath = args[0] || "";
|
|
72
|
+
const postDecoratorText = content.substring(paren.endIndex);
|
|
73
|
+
const funcMatch = postDecoratorText.match(/^\s*(?:def|async def)\s+(\w+)\s*\(/);
|
|
74
|
+
if (funcMatch) {
|
|
75
|
+
const funcName = funcMatch[1];
|
|
76
|
+
const routerPrefix = this.routerPrefixes.get(targetVar) || "";
|
|
77
|
+
const cleanPrefix = routerPrefix.replace(/^\/|\/$/g, "");
|
|
78
|
+
const cleanRoute = routePath.replace(/^\/|\/$/g, "");
|
|
79
|
+
const fullPath = "/" + [cleanPrefix, cleanRoute].filter(Boolean).join("/");
|
|
80
|
+
let resolvedFile = filePath;
|
|
81
|
+
const resolvedPath = ctx.resolveSymbolToFile(funcName);
|
|
82
|
+
if (resolvedPath) {
|
|
83
|
+
resolvedFile = resolvedPath;
|
|
84
|
+
}
|
|
85
|
+
routes.push({
|
|
86
|
+
framework: this.name,
|
|
87
|
+
method,
|
|
88
|
+
path: fullPath,
|
|
89
|
+
handlerFile: resolvedFile,
|
|
90
|
+
handlerSymbol: funcName,
|
|
91
|
+
metadata: {
|
|
92
|
+
confidence: "inferred"
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
index = paren.endIndex;
|
|
97
|
+
}
|
|
98
|
+
return routes;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
function getParenthesizedContent(str, startIndex) {
|
|
102
|
+
let depth = 0;
|
|
103
|
+
let inString = false;
|
|
104
|
+
let stringChar = "";
|
|
105
|
+
let escape = false;
|
|
106
|
+
for (let i = startIndex; i < str.length; i++) {
|
|
107
|
+
const char = str[i];
|
|
108
|
+
if (escape) {
|
|
109
|
+
escape = false;
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
if (char === "\\") {
|
|
113
|
+
escape = true;
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
if (inString) {
|
|
117
|
+
if (char === stringChar) {
|
|
118
|
+
inString = false;
|
|
119
|
+
}
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
if (char === "'" || char === '"') {
|
|
123
|
+
inString = true;
|
|
124
|
+
stringChar = char;
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
if (char === "(") {
|
|
128
|
+
depth++;
|
|
129
|
+
if (depth === 1) {
|
|
130
|
+
startIndex = i + 1;
|
|
131
|
+
}
|
|
132
|
+
} else if (char === ")") {
|
|
133
|
+
depth--;
|
|
134
|
+
if (depth === 0) {
|
|
135
|
+
return {
|
|
136
|
+
content: str.substring(startIndex, i),
|
|
137
|
+
endIndex: i + 1
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
function parseArgs(argsStr) {
|
|
145
|
+
const args = [];
|
|
146
|
+
let current = "";
|
|
147
|
+
let depth = 0;
|
|
148
|
+
let bracketDepth = 0;
|
|
149
|
+
let inString = false;
|
|
150
|
+
let stringChar = "";
|
|
151
|
+
let escape = false;
|
|
152
|
+
for (let i = 0; i < argsStr.length; i++) {
|
|
153
|
+
const char = argsStr[i];
|
|
154
|
+
if (escape) {
|
|
155
|
+
escape = false;
|
|
156
|
+
current += char;
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
if (char === "\\") {
|
|
160
|
+
escape = true;
|
|
161
|
+
current += char;
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
if (inString) {
|
|
165
|
+
if (char === stringChar) {
|
|
166
|
+
inString = false;
|
|
167
|
+
}
|
|
168
|
+
current += char;
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
if (char === "'" || char === '"') {
|
|
172
|
+
inString = true;
|
|
173
|
+
stringChar = char;
|
|
174
|
+
current += char;
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
if (char === "(") {
|
|
178
|
+
depth++;
|
|
179
|
+
} else if (char === ")") {
|
|
180
|
+
depth--;
|
|
181
|
+
} else if (char === "[") {
|
|
182
|
+
bracketDepth++;
|
|
183
|
+
} else if (char === "]") {
|
|
184
|
+
bracketDepth--;
|
|
185
|
+
}
|
|
186
|
+
if (char === "," && depth === 0 && bracketDepth === 0) {
|
|
187
|
+
args.push(cleanQuotes(current.trim()));
|
|
188
|
+
current = "";
|
|
189
|
+
} else {
|
|
190
|
+
current += char;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
if (current.trim()) {
|
|
194
|
+
args.push(cleanQuotes(current.trim()));
|
|
195
|
+
}
|
|
196
|
+
return args;
|
|
197
|
+
}
|
|
198
|
+
function cleanQuotes(str) {
|
|
199
|
+
return str.replace(/^['"]|['"]$/g, "");
|
|
200
|
+
}
|
|
201
|
+
export {
|
|
202
|
+
FastAPIDetector
|
|
203
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { FrameworkDetector, ScanContext, RouteBinding } from '../../types.js';
|
|
2
|
+
|
|
3
|
+
declare class FlaskDetector implements FrameworkDetector {
|
|
4
|
+
readonly name = "flask";
|
|
5
|
+
readonly language = "python";
|
|
6
|
+
readonly filePattern: RegExp;
|
|
7
|
+
private bpPrefixes;
|
|
8
|
+
detect(projectRoot: string, files: string[]): Promise<boolean>;
|
|
9
|
+
extractRoutes(filePath: string, content: string, ctx: ScanContext): Promise<RouteBinding[]>;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export { FlaskDetector };
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { existsSync } from "node:fs";
|
|
4
|
+
class FlaskDetector {
|
|
5
|
+
name = "flask";
|
|
6
|
+
language = "python";
|
|
7
|
+
filePattern = /\.py$/;
|
|
8
|
+
bpPrefixes = /* @__PURE__ */ new Map();
|
|
9
|
+
// blueprint variable/name -> prefix
|
|
10
|
+
async detect(projectRoot, files) {
|
|
11
|
+
let isFlask = false;
|
|
12
|
+
for (const file of ["requirements.txt", "Pipfile", "pyproject.toml"]) {
|
|
13
|
+
const filePath = join(projectRoot, file);
|
|
14
|
+
if (existsSync(filePath)) {
|
|
15
|
+
try {
|
|
16
|
+
const content = await readFile(filePath, "utf-8");
|
|
17
|
+
if (content.toLowerCase().includes("flask")) {
|
|
18
|
+
isFlask = true;
|
|
19
|
+
}
|
|
20
|
+
} catch {
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
if (!isFlask) {
|
|
25
|
+
isFlask = files.some((f) => f.endsWith("wsgi.py") || f.endsWith("app.py"));
|
|
26
|
+
}
|
|
27
|
+
if (isFlask) {
|
|
28
|
+
for (const file of files) {
|
|
29
|
+
if (!file.endsWith(".py")) continue;
|
|
30
|
+
try {
|
|
31
|
+
const content = await readFile(join(projectRoot, file), "utf-8");
|
|
32
|
+
const bpDefRegex = /(\w+)\s*=\s*Blueprint\s*\(\s*['"]([^'"]+)['"]\s*,\s*__name__(?:,\s*url_prefix\s*=\s*['"]([^'"]+)['"])?/g;
|
|
33
|
+
let match;
|
|
34
|
+
while ((match = bpDefRegex.exec(content)) !== null) {
|
|
35
|
+
const varName = match[1];
|
|
36
|
+
const prefix = match[3] || "";
|
|
37
|
+
this.bpPrefixes.set(varName, prefix);
|
|
38
|
+
}
|
|
39
|
+
const bpRegRegex = /register_blueprint\s*\(\s*([^,\s)]+)(?:,\s*url_prefix\s*=\s*['"]([^'"]+)['"])?/g;
|
|
40
|
+
while ((match = bpRegRegex.exec(content)) !== null) {
|
|
41
|
+
const bpVar = match[1].split(".").pop() || "";
|
|
42
|
+
const prefix = match[2];
|
|
43
|
+
if (prefix) {
|
|
44
|
+
this.bpPrefixes.set(bpVar, prefix);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
} catch {
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return isFlask;
|
|
52
|
+
}
|
|
53
|
+
async extractRoutes(filePath, content, ctx) {
|
|
54
|
+
const routes = [];
|
|
55
|
+
if (!content.includes(".route") && !content.includes("add_resource") && !content.includes("Route")) {
|
|
56
|
+
return [];
|
|
57
|
+
}
|
|
58
|
+
let index = 0;
|
|
59
|
+
while (true) {
|
|
60
|
+
const match = content.substring(index).match(/@(\w+)\.route\s*\(/);
|
|
61
|
+
if (!match) break;
|
|
62
|
+
const targetVar = match[1];
|
|
63
|
+
const startOfParen = index + match.index + match[0].length - 1;
|
|
64
|
+
const paren = getParenthesizedContent(content, startOfParen);
|
|
65
|
+
if (!paren) {
|
|
66
|
+
index = startOfParen + 1;
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
const args = parseArgs(paren.content);
|
|
70
|
+
const routePath = args[0] || "";
|
|
71
|
+
let methods = ["GET"];
|
|
72
|
+
const methodsMatch = paren.content.match(/methods\s*=\s*\[([^\]]+)\]/);
|
|
73
|
+
if (methodsMatch) {
|
|
74
|
+
methods = (methodsMatch[1].match(/['"]([^'"]+)['"]/g) || []).map((m) => m.replace(/['"]/g, "").toUpperCase());
|
|
75
|
+
}
|
|
76
|
+
const postDecoratorText = content.substring(paren.endIndex);
|
|
77
|
+
const funcMatch = postDecoratorText.match(/^\s*(?:def|async def)\s+(\w+)\s*\(/);
|
|
78
|
+
if (funcMatch) {
|
|
79
|
+
const funcName = funcMatch[1];
|
|
80
|
+
const bpPrefix = this.bpPrefixes.get(targetVar) || "";
|
|
81
|
+
const cleanPrefix = bpPrefix.replace(/^\/|\/$/g, "");
|
|
82
|
+
const cleanRoute = routePath.replace(/^\/|\/$/g, "");
|
|
83
|
+
const fullPath = "/" + [cleanPrefix, cleanRoute].filter(Boolean).join("/");
|
|
84
|
+
let resolvedFile = filePath;
|
|
85
|
+
const resolvedPath = ctx.resolveSymbolToFile(funcName);
|
|
86
|
+
if (resolvedPath) {
|
|
87
|
+
resolvedFile = resolvedPath;
|
|
88
|
+
}
|
|
89
|
+
for (const method of methods) {
|
|
90
|
+
routes.push({
|
|
91
|
+
framework: this.name,
|
|
92
|
+
method,
|
|
93
|
+
path: fullPath,
|
|
94
|
+
handlerFile: resolvedFile,
|
|
95
|
+
handlerSymbol: funcName,
|
|
96
|
+
metadata: {
|
|
97
|
+
confidence: "inferred"
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
index = paren.endIndex;
|
|
103
|
+
}
|
|
104
|
+
const restxRegex = /@(\w+)\.route\s*\(\s*['"]([^'"]+)['"]\s*\)\s*class\s+(\w+)/g;
|
|
105
|
+
let restxMatch;
|
|
106
|
+
while ((restxMatch = restxRegex.exec(content)) !== null) {
|
|
107
|
+
const routePath = restxMatch[2];
|
|
108
|
+
const className = restxMatch[3];
|
|
109
|
+
const classStart = content.indexOf(`class ${className}`);
|
|
110
|
+
let classEnd = content.length;
|
|
111
|
+
const classSlice = content.substring(classStart);
|
|
112
|
+
const nextClassMatch = classSlice.substring(className.length).match(/\bclass\s+\w+/);
|
|
113
|
+
if (nextClassMatch && nextClassMatch.index) {
|
|
114
|
+
classEnd = classStart + className.length + nextClassMatch.index;
|
|
115
|
+
}
|
|
116
|
+
const classBlock = content.substring(classStart, classEnd);
|
|
117
|
+
const methodRegex = /\bdef\s+(get|post|put|delete|patch|options|head)\s*\(\s*self\b/g;
|
|
118
|
+
let methodMatch;
|
|
119
|
+
let resolvedFile = filePath;
|
|
120
|
+
const resolvedPath = ctx.resolveSymbolToFile(className);
|
|
121
|
+
if (resolvedPath) {
|
|
122
|
+
resolvedFile = resolvedPath;
|
|
123
|
+
}
|
|
124
|
+
while ((methodMatch = methodRegex.exec(classBlock)) !== null) {
|
|
125
|
+
const httpMethod = methodMatch[1].toUpperCase();
|
|
126
|
+
routes.push({
|
|
127
|
+
framework: this.name,
|
|
128
|
+
method: httpMethod,
|
|
129
|
+
path: routePath,
|
|
130
|
+
handlerFile: resolvedFile,
|
|
131
|
+
handlerSymbol: `${className}.${methodMatch[1]}`,
|
|
132
|
+
metadata: {
|
|
133
|
+
confidence: "inferred",
|
|
134
|
+
resourceType: "flask_restx_resource"
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return routes;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
function getParenthesizedContent(str, startIndex) {
|
|
143
|
+
let depth = 0;
|
|
144
|
+
let inString = false;
|
|
145
|
+
let stringChar = "";
|
|
146
|
+
let escape = false;
|
|
147
|
+
for (let i = startIndex; i < str.length; i++) {
|
|
148
|
+
const char = str[i];
|
|
149
|
+
if (escape) {
|
|
150
|
+
escape = false;
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
if (char === "\\") {
|
|
154
|
+
escape = true;
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
if (inString) {
|
|
158
|
+
if (char === stringChar) {
|
|
159
|
+
inString = false;
|
|
160
|
+
}
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
if (char === "'" || char === '"') {
|
|
164
|
+
inString = true;
|
|
165
|
+
stringChar = char;
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
if (char === "(") {
|
|
169
|
+
depth++;
|
|
170
|
+
if (depth === 1) {
|
|
171
|
+
startIndex = i + 1;
|
|
172
|
+
}
|
|
173
|
+
} else if (char === ")") {
|
|
174
|
+
depth--;
|
|
175
|
+
if (depth === 0) {
|
|
176
|
+
return {
|
|
177
|
+
content: str.substring(startIndex, i),
|
|
178
|
+
endIndex: i + 1
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return null;
|
|
184
|
+
}
|
|
185
|
+
function parseArgs(argsStr) {
|
|
186
|
+
const args = [];
|
|
187
|
+
let current = "";
|
|
188
|
+
let depth = 0;
|
|
189
|
+
let bracketDepth = 0;
|
|
190
|
+
let inString = false;
|
|
191
|
+
let stringChar = "";
|
|
192
|
+
let escape = false;
|
|
193
|
+
for (let i = 0; i < argsStr.length; i++) {
|
|
194
|
+
const char = argsStr[i];
|
|
195
|
+
if (escape) {
|
|
196
|
+
escape = false;
|
|
197
|
+
current += char;
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
if (char === "\\") {
|
|
201
|
+
escape = true;
|
|
202
|
+
current += char;
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
if (inString) {
|
|
206
|
+
if (char === stringChar) {
|
|
207
|
+
inString = false;
|
|
208
|
+
}
|
|
209
|
+
current += char;
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
if (char === "'" || char === '"') {
|
|
213
|
+
inString = true;
|
|
214
|
+
stringChar = char;
|
|
215
|
+
current += char;
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
218
|
+
if (char === "(") {
|
|
219
|
+
depth++;
|
|
220
|
+
} else if (char === ")") {
|
|
221
|
+
depth--;
|
|
222
|
+
} else if (char === "[") {
|
|
223
|
+
bracketDepth++;
|
|
224
|
+
} else if (char === "]") {
|
|
225
|
+
bracketDepth--;
|
|
226
|
+
}
|
|
227
|
+
if (char === "," && depth === 0 && bracketDepth === 0) {
|
|
228
|
+
args.push(cleanQuotes(current.trim()));
|
|
229
|
+
current = "";
|
|
230
|
+
} else {
|
|
231
|
+
current += char;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
if (current.trim()) {
|
|
235
|
+
args.push(cleanQuotes(current.trim()));
|
|
236
|
+
}
|
|
237
|
+
return args;
|
|
238
|
+
}
|
|
239
|
+
function cleanQuotes(str) {
|
|
240
|
+
return str.replace(/^['"]|['"]$/g, "");
|
|
241
|
+
}
|
|
242
|
+
export {
|
|
243
|
+
FlaskDetector
|
|
244
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { FrameworkDetector, ScanContext, RouteBinding } from '../../types.js';
|
|
2
|
+
|
|
3
|
+
declare class GoDetector implements FrameworkDetector {
|
|
4
|
+
readonly name = "go-web";
|
|
5
|
+
readonly language = "go";
|
|
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 { GoDetector };
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
class GoDetector {
|
|
4
|
+
name = "go-web";
|
|
5
|
+
language = "go";
|
|
6
|
+
filePattern = /\.go$/;
|
|
7
|
+
async detect(projectRoot, files) {
|
|
8
|
+
const modPath = join(projectRoot, "go.mod");
|
|
9
|
+
return existsSync(modPath) || files.some((f) => f.endsWith(".go"));
|
|
10
|
+
}
|
|
11
|
+
async extractRoutes(filePath, content, ctx) {
|
|
12
|
+
const routes = [];
|
|
13
|
+
const verbRegex = /\b[a-zA-Z0-9_]+\.(GET|POST|PUT|DELETE|PATCH|OPTIONS|HEAD|Get|Post|Put|Delete|Patch|Options|Head)\s*\(\s*['"]([^'"]+)['"]\s*,\s*([a-zA-Z0-9_.]+)/g;
|
|
14
|
+
let match;
|
|
15
|
+
while ((match = verbRegex.exec(content)) !== null) {
|
|
16
|
+
const verb = match[1].toUpperCase();
|
|
17
|
+
const pathVal = match[2];
|
|
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 muxRegex = /\b[a-zA-Z0-9_]+\.HandleFunc\s*\(\s*['"]([^'"]+)['"]\s*,\s*([a-zA-Z0-9_.]+)\s*\)(?:\.Methods\s*\(\s*([^)]+)\s*\))?/g;
|
|
37
|
+
while ((match = muxRegex.exec(content)) !== null) {
|
|
38
|
+
const pathVal = match[1];
|
|
39
|
+
const handlerSymbol = match[2];
|
|
40
|
+
const methodsStr = match[3];
|
|
41
|
+
let resolvedFile = filePath;
|
|
42
|
+
const resolvedPath = ctx.resolveSymbolToFile(handlerSymbol);
|
|
43
|
+
if (resolvedPath) {
|
|
44
|
+
resolvedFile = resolvedPath;
|
|
45
|
+
}
|
|
46
|
+
const verbs = [];
|
|
47
|
+
if (methodsStr) {
|
|
48
|
+
const verbMatches = methodsStr.match(/['"]([A-Z]+)['"]/g);
|
|
49
|
+
if (verbMatches) {
|
|
50
|
+
verbs.push(...verbMatches.map((v) => v.replace(/['"]/g, "")));
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
if (verbs.length === 0) {
|
|
54
|
+
verbs.push("ALL");
|
|
55
|
+
}
|
|
56
|
+
for (const verb of verbs) {
|
|
57
|
+
routes.push({
|
|
58
|
+
framework: this.name,
|
|
59
|
+
method: verb,
|
|
60
|
+
path: pathVal,
|
|
61
|
+
handlerFile: resolvedFile,
|
|
62
|
+
handlerSymbol,
|
|
63
|
+
metadata: {
|
|
64
|
+
confidence: "inferred",
|
|
65
|
+
routeType: "server"
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return routes;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
export {
|
|
74
|
+
GoDetector
|
|
75
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { FrameworkDetector, ScanContext, RouteBinding } from '../../types.js';
|
|
2
|
+
|
|
3
|
+
declare class LaravelDetector implements FrameworkDetector {
|
|
4
|
+
readonly name = "laravel";
|
|
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
|
+
}
|
|
10
|
+
|
|
11
|
+
export { LaravelDetector };
|