@launchsecure/launch-kit 0.0.9 → 0.0.10
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/dist/chart-client/assets/index-DIPKWwEJ.js +404 -0
- package/dist/chart-client/assets/index-DjnSR-Hf.css +1 -0
- package/dist/chart-client/index.html +2 -2
- package/dist/client/assets/index-BIpeUMYR.css +32 -0
- package/dist/client/index.html +2 -2
- package/dist/server/chart-serve.js +595 -324
- package/dist/server/cli.js +700 -325
- package/dist/server/graph/queries/db-calls.scm +8 -0
- package/dist/server/graph/queries/deep/conditions.scm +10 -0
- package/dist/server/graph/queries/deep/jsx-semantic.scm +21 -0
- package/dist/server/graph/queries/deep/request-params.scm +24 -0
- package/dist/server/graph/queries/deep/responses.scm +26 -0
- package/dist/server/graph/queries/deep/state-hooks.scm +23 -0
- package/dist/server/graph/queries/deep/variables.scm +17 -0
- package/dist/server/graph/queries/exports.scm +66 -0
- package/dist/server/graph/queries/fetch-calls.scm +57 -0
- package/dist/server/graph/queries/imports.scm +14 -0
- package/dist/server/graph/queries/jsx-elements.scm +14 -0
- package/dist/server/graph/queries/navigations.scm +85 -0
- package/dist/server/graph/queries/wrappers.scm +8 -0
- package/dist/server/graph-mcp-entry.js +709 -330
- package/package.json +5 -2
- package/dist/chart-client/assets/index-8p8Otm3A.js +0 -379
- package/dist/chart-client/assets/index-CuRWRjsg.css +0 -1
- package/dist/client/assets/index-DlwXprgf.css +0 -32
- /package/dist/client/assets/{index-B4wZOxci.js → index-qzK1AD2G.js} +0 -0
|
@@ -81,41 +81,160 @@ var import_node_path8 = require("node:path");
|
|
|
81
81
|
var import_node_fs3 = require("node:fs");
|
|
82
82
|
var import_node_path3 = require("node:path");
|
|
83
83
|
|
|
84
|
-
// src/server/graph/core/
|
|
84
|
+
// src/server/graph/core/ts-extractor.ts
|
|
85
85
|
var import_node_fs2 = require("node:fs");
|
|
86
86
|
var import_node_path2 = require("node:path");
|
|
87
|
-
var
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
87
|
+
var tsxLanguage;
|
|
88
|
+
var parserInstance;
|
|
89
|
+
var initPromise;
|
|
90
|
+
var initialized = false;
|
|
91
|
+
var queriesDir = (() => {
|
|
92
|
+
const srcPath = (0, import_node_path2.join)((0, import_node_path2.dirname)(__filename), "..", "queries");
|
|
93
|
+
if (require("fs").existsSync(srcPath)) return srcPath;
|
|
94
|
+
return (0, import_node_path2.join)((0, import_node_path2.dirname)(__filename), "graph", "queries");
|
|
95
|
+
})();
|
|
96
|
+
var queryCache = /* @__PURE__ */ new Map();
|
|
97
|
+
async function initTreeSitter() {
|
|
98
|
+
if (initialized) return;
|
|
99
|
+
if (initPromise) return initPromise;
|
|
100
|
+
initPromise = (async () => {
|
|
101
|
+
const TreeSitter = require("web-tree-sitter");
|
|
102
|
+
await TreeSitter.init();
|
|
103
|
+
const wasmPath = require.resolve("tree-sitter-typescript/tree-sitter-tsx.wasm");
|
|
104
|
+
tsxLanguage = await TreeSitter.Language.load(wasmPath);
|
|
105
|
+
parserInstance = new TreeSitter();
|
|
106
|
+
parserInstance.setLanguage(tsxLanguage);
|
|
107
|
+
initialized = true;
|
|
108
|
+
})();
|
|
109
|
+
return initPromise;
|
|
110
|
+
}
|
|
111
|
+
function ensureInit() {
|
|
112
|
+
if (!initialized || !tsxLanguage || !parserInstance) {
|
|
113
|
+
throw new Error("Tree-sitter not initialized. Call initTreeSitter() first.");
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
function getQuery(name) {
|
|
117
|
+
ensureInit();
|
|
118
|
+
const cached = queryCache.get(name);
|
|
119
|
+
if (cached) return cached;
|
|
120
|
+
const scmPath = (0, import_node_path2.join)(queriesDir, `${name}.scm`);
|
|
121
|
+
const scm = (0, import_node_fs2.readFileSync)(scmPath, "utf-8");
|
|
122
|
+
const query = tsxLanguage.query(scm);
|
|
123
|
+
queryCache.set(name, query);
|
|
124
|
+
return query;
|
|
125
|
+
}
|
|
126
|
+
function parseSource(absPath) {
|
|
127
|
+
ensureInit();
|
|
128
|
+
const content = (0, import_node_fs2.readFileSync)(absPath, "utf-8");
|
|
129
|
+
return parserInstance.parse(content);
|
|
130
|
+
}
|
|
131
|
+
var PRISMA_MUTATION_METHODS_BUILTIN = [
|
|
132
|
+
"create",
|
|
133
|
+
"createMany",
|
|
134
|
+
"createManyAndReturn",
|
|
135
|
+
"update",
|
|
136
|
+
"updateMany",
|
|
137
|
+
"updateManyAndReturn",
|
|
138
|
+
"upsert",
|
|
139
|
+
"delete",
|
|
140
|
+
"deleteMany"
|
|
141
|
+
];
|
|
142
|
+
var DB_IDENTIFIERS_FALLBACK = ["db", "prisma"];
|
|
143
|
+
var extraDbIdentifiers = [];
|
|
144
|
+
var extraMutationMethods = [];
|
|
145
|
+
function setExtractorConfig(config) {
|
|
146
|
+
extraDbIdentifiers = config.dbIdentifiers ?? [];
|
|
147
|
+
extraMutationMethods = config.mutationMethods ?? [];
|
|
148
|
+
}
|
|
149
|
+
function getMutationMethods() {
|
|
150
|
+
return /* @__PURE__ */ new Set([...PRISMA_MUTATION_METHODS_BUILTIN, ...extraMutationMethods]);
|
|
151
|
+
}
|
|
152
|
+
function getFallbackDbIdentifiers() {
|
|
153
|
+
return /* @__PURE__ */ new Set([...DB_IDENTIFIERS_FALLBACK, ...extraDbIdentifiers]);
|
|
154
|
+
}
|
|
155
|
+
function looksLikeUrl(s) {
|
|
156
|
+
return s.startsWith("/") || /^(https?:)?\/\//i.test(s);
|
|
157
|
+
}
|
|
158
|
+
function templateStartsWithSlash(text) {
|
|
159
|
+
const content = text.slice(1);
|
|
160
|
+
return content.startsWith("/");
|
|
161
|
+
}
|
|
162
|
+
function captureMap(match) {
|
|
163
|
+
const map = {};
|
|
164
|
+
for (const c of match.captures) {
|
|
165
|
+
map[c.name] = c.node.text;
|
|
91
166
|
}
|
|
92
|
-
return
|
|
167
|
+
return map;
|
|
93
168
|
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
169
|
+
function childrenOfType(node, type) {
|
|
170
|
+
return node.children.filter((n) => n.type === type);
|
|
171
|
+
}
|
|
172
|
+
function childOfType(node, type) {
|
|
173
|
+
return node.children.find((n) => n.type === type);
|
|
174
|
+
}
|
|
175
|
+
function parseFileTS(absPath) {
|
|
176
|
+
const tree = parseSource(absPath);
|
|
177
|
+
const root = tree.rootNode;
|
|
178
|
+
const imports = [];
|
|
179
|
+
const importStatements = childrenOfType(root, "import_statement");
|
|
180
|
+
for (const stmt of importStatements) {
|
|
181
|
+
const sourceNode = childOfType(stmt, "string");
|
|
182
|
+
const frag = sourceNode ? childOfType(sourceNode, "string_fragment") : void 0;
|
|
183
|
+
if (!frag) continue;
|
|
184
|
+
const specifier = frag.text;
|
|
185
|
+
const hasTypeKeyword = stmt.children.some(
|
|
186
|
+
(n) => n.type === "type" && n.text === "type"
|
|
187
|
+
);
|
|
188
|
+
const clause = childOfType(stmt, "import_clause");
|
|
189
|
+
if (!clause) {
|
|
190
|
+
imports.push({ names: [], specifier, isTypeOnly: false, typeNames: /* @__PURE__ */ new Set() });
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
const names = [];
|
|
194
|
+
const typeNames = /* @__PURE__ */ new Set();
|
|
195
|
+
const defaultId = childOfType(clause, "identifier");
|
|
196
|
+
if (defaultId) names.push(defaultId.text);
|
|
197
|
+
const nsImport = childOfType(clause, "namespace_import");
|
|
198
|
+
if (nsImport) {
|
|
199
|
+
const nsId = childOfType(nsImport, "identifier");
|
|
200
|
+
if (nsId) names.push(nsId.text);
|
|
201
|
+
}
|
|
202
|
+
const namedImports = childOfType(clause, "named_imports");
|
|
203
|
+
if (namedImports) {
|
|
204
|
+
for (const spec of childrenOfType(namedImports, "import_specifier")) {
|
|
205
|
+
const identifiers = childrenOfType(spec, "identifier");
|
|
206
|
+
const importedName = identifiers.length > 1 ? identifiers[identifiers.length - 1].text : identifiers[0]?.text;
|
|
207
|
+
if (importedName) {
|
|
208
|
+
names.push(importedName);
|
|
209
|
+
const specIsType = spec.children.some(
|
|
210
|
+
(n) => n.type === "type" && n.text === "type"
|
|
211
|
+
);
|
|
212
|
+
if (specIsType) typeNames.add(importedName);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
if (names.length > 0 || hasTypeKeyword) {
|
|
217
|
+
imports.push({ names, specifier, isTypeOnly: hasTypeKeyword, typeNames });
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
const importQuery = getQuery("imports");
|
|
221
|
+
const importMatches = importQuery.matches(root);
|
|
222
|
+
for (const m of importMatches) {
|
|
223
|
+
const caps = captureMap(m);
|
|
224
|
+
if (caps["import.dynamic"]) {
|
|
225
|
+
imports.push({
|
|
226
|
+
names: [],
|
|
227
|
+
specifier: caps["import.dynamic"],
|
|
228
|
+
isTypeOnly: false,
|
|
229
|
+
typeNames: /* @__PURE__ */ new Set()
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
}
|
|
108
233
|
const exportsSet = /* @__PURE__ */ new Set();
|
|
109
234
|
const exportsOrdered = [];
|
|
110
235
|
let defaultName = null;
|
|
111
236
|
let firstValueExport = null;
|
|
112
237
|
let firstTypeExport = null;
|
|
113
|
-
const imports = [];
|
|
114
|
-
const reExports = [];
|
|
115
|
-
const jsxElements = /* @__PURE__ */ new Set();
|
|
116
|
-
const navigations = [];
|
|
117
|
-
const fetchCalls = [];
|
|
118
|
-
const includeConcat = process.env.LAUNCH_CHART_INCLUDE_CONCAT_FETCHES === "1";
|
|
119
238
|
function addExport(name2, kind) {
|
|
120
239
|
if (!exportsSet.has(name2)) {
|
|
121
240
|
exportsSet.add(name2);
|
|
@@ -125,300 +244,339 @@ function parseFile(absPath) {
|
|
|
125
244
|
else if (kind === "value" && !firstValueExport) firstValueExport = name2;
|
|
126
245
|
else if (kind === "type" && !firstTypeExport) firstTypeExport = name2;
|
|
127
246
|
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
if (
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
if (
|
|
137
|
-
|
|
247
|
+
const exportQuery = getQuery("exports");
|
|
248
|
+
const exportMatches = exportQuery.matches(root);
|
|
249
|
+
for (const m of exportMatches) {
|
|
250
|
+
const caps = captureMap(m);
|
|
251
|
+
if (caps["export.default.func"]) addExport(caps["export.default.func"], "default");
|
|
252
|
+
else if (caps["export.default.class"]) addExport(caps["export.default.class"], "default");
|
|
253
|
+
else if (caps["export.default.value"]) addExport(caps["export.default.value"], "default");
|
|
254
|
+
else if (caps["export.named.func"]) addExport(caps["export.named.func"], "value");
|
|
255
|
+
else if (caps["export.named.class"]) addExport(caps["export.named.class"], "value");
|
|
256
|
+
else if (caps["export.named.const"]) addExport(caps["export.named.const"], "value");
|
|
257
|
+
else if (caps["export.named.enum"]) addExport(caps["export.named.enum"], "value");
|
|
258
|
+
else if (caps["export.named.type"]) addExport(caps["export.named.type"], "type");
|
|
259
|
+
else if (caps["export.named.interface"]) addExport(caps["export.named.interface"], "type");
|
|
260
|
+
else if (caps["export.bare.name"]) addExport(caps["export.bare.name"], "value");
|
|
261
|
+
if (caps["reexport.name"]) addExport(caps["reexport.name"], "value");
|
|
262
|
+
}
|
|
263
|
+
for (const stmt of childrenOfType(root, "export_statement")) {
|
|
264
|
+
const hasDefault = stmt.children.some((n) => n.text === "default");
|
|
265
|
+
if (hasDefault && !defaultName) {
|
|
266
|
+
defaultName = "default";
|
|
138
267
|
}
|
|
139
|
-
return null;
|
|
140
|
-
}
|
|
141
|
-
function looksLikeUrl(s) {
|
|
142
|
-
return s.startsWith("/") || /^(https?:)?\/\//i.test(s);
|
|
143
268
|
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
if (ts.isStringLiteral(arg) || ts.isNoSubstitutionTemplateLiteral(arg)) {
|
|
150
|
-
if (!looksLikeUrl(arg.text)) return null;
|
|
151
|
-
return { url: arg.text, isTemplate: false };
|
|
152
|
-
}
|
|
153
|
-
if (ts.isTemplateExpression(arg)) {
|
|
154
|
-
if (!templateStartsWithSlash(arg)) return null;
|
|
155
|
-
return { url: arg.getText(sourceFile), isTemplate: true };
|
|
269
|
+
const reExports = [];
|
|
270
|
+
for (const m of exportMatches) {
|
|
271
|
+
const caps = captureMap(m);
|
|
272
|
+
if (caps["reexport.name"] && caps["reexport.source"]) {
|
|
273
|
+
reExports.push({ name: caps["reexport.name"], from: caps["reexport.source"] });
|
|
156
274
|
}
|
|
157
|
-
if (
|
|
158
|
-
|
|
159
|
-
while (ts.isBinaryExpression(leftmost) && leftmost.operatorToken.kind === ts.SyntaxKind.PlusToken) {
|
|
160
|
-
leftmost = leftmost.left;
|
|
161
|
-
}
|
|
162
|
-
if ((ts.isStringLiteral(leftmost) || ts.isNoSubstitutionTemplateLiteral(leftmost)) && leftmost.text.startsWith("/")) {
|
|
163
|
-
return { url: arg.getText(sourceFile), isTemplate: false, isConcat: true };
|
|
164
|
-
}
|
|
275
|
+
if (caps["reexport.wildcard.source"]) {
|
|
276
|
+
reExports.push({ name: "*", from: caps["reexport.wildcard.source"], isWildcard: true });
|
|
165
277
|
}
|
|
166
|
-
return null;
|
|
167
278
|
}
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
const names = [];
|
|
176
|
-
const typeNames = /* @__PURE__ */ new Set();
|
|
177
|
-
if (clause) {
|
|
178
|
-
if (clause.name) names.push(clause.name.text);
|
|
179
|
-
const nb = clause.namedBindings;
|
|
180
|
-
if (nb && ts.isNamedImports(nb)) {
|
|
181
|
-
for (const el of nb.elements) {
|
|
182
|
-
names.push(el.name.text);
|
|
183
|
-
if (el.isTypeOnly) typeNames.add(el.name.text);
|
|
184
|
-
}
|
|
185
|
-
} else if (nb && ts.isNamespaceImport(nb)) {
|
|
186
|
-
names.push(nb.name.text);
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
if (names.length > 0 || isTypeOnly) {
|
|
190
|
-
imports.push({ names, specifier, isTypeOnly, typeNames });
|
|
191
|
-
} else if (!clause) {
|
|
192
|
-
imports.push({ names: [], specifier, isTypeOnly: false, typeNames: /* @__PURE__ */ new Set() });
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
if (ts.isExportDeclaration(node)) {
|
|
197
|
-
const fromSpec = node.moduleSpecifier && ts.isStringLiteral(node.moduleSpecifier) ? node.moduleSpecifier.text : null;
|
|
198
|
-
if (node.exportClause && ts.isNamedExports(node.exportClause)) {
|
|
199
|
-
for (const el of node.exportClause.elements) {
|
|
200
|
-
const exportedName = el.name.text;
|
|
201
|
-
addExport(exportedName, "value");
|
|
202
|
-
if (fromSpec) {
|
|
203
|
-
reExports.push({ name: exportedName, from: fromSpec });
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
} else if (!node.exportClause && fromSpec) {
|
|
207
|
-
reExports.push({ name: "*", from: fromSpec, isWildcard: true });
|
|
279
|
+
const jsxElements = /* @__PURE__ */ new Set();
|
|
280
|
+
const jsxQuery = getQuery("jsx-elements");
|
|
281
|
+
const jsxCaptures = jsxQuery.captures(root);
|
|
282
|
+
for (const c of jsxCaptures) {
|
|
283
|
+
if (c.name === "jsx.tag") {
|
|
284
|
+
if (/^[A-Z]/.test(c.node.text)) {
|
|
285
|
+
jsxElements.add(c.node.text);
|
|
208
286
|
}
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
imports.push({
|
|
214
|
-
names: [],
|
|
215
|
-
specifier: arg.text,
|
|
216
|
-
isTypeOnly: false,
|
|
217
|
-
typeNames: /* @__PURE__ */ new Set()
|
|
218
|
-
});
|
|
287
|
+
} else if (c.name === "jsx.member_tag") {
|
|
288
|
+
const rootName = c.node.text.split(".")[0];
|
|
289
|
+
if (rootName && /^[A-Z]/.test(rootName)) {
|
|
290
|
+
jsxElements.add(rootName);
|
|
219
291
|
}
|
|
220
292
|
}
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
293
|
+
}
|
|
294
|
+
const navigations = [];
|
|
295
|
+
const navQuery = getQuery("navigations");
|
|
296
|
+
const navMatches = navQuery.matches(root);
|
|
297
|
+
for (const m of navMatches) {
|
|
298
|
+
const caps = captureMap(m);
|
|
299
|
+
if (caps["nav.target.literal"] && caps["nav.method"]) {
|
|
300
|
+
navigations.push({
|
|
301
|
+
kind: caps["nav.method"] === "push" ? "router-push" : "router-replace",
|
|
302
|
+
target: caps["nav.target.literal"],
|
|
303
|
+
isTemplate: false
|
|
304
|
+
});
|
|
227
305
|
}
|
|
228
|
-
if (
|
|
229
|
-
|
|
230
|
-
|
|
306
|
+
if (caps["nav.target.template"] && caps["nav.method.template"]) {
|
|
307
|
+
navigations.push({
|
|
308
|
+
kind: caps["nav.method.template"] === "push" ? "router-push" : "router-replace",
|
|
309
|
+
target: caps["nav.target.template"],
|
|
310
|
+
isTemplate: true
|
|
311
|
+
});
|
|
231
312
|
}
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
addExport(decl.name.text, "value");
|
|
236
|
-
}
|
|
237
|
-
}
|
|
313
|
+
const linkLiteral = caps["nav.link.literal"] || caps["nav.link.literal.self"];
|
|
314
|
+
if (linkLiteral) {
|
|
315
|
+
navigations.push({ kind: "link-href", target: linkLiteral, isTemplate: false });
|
|
238
316
|
}
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
317
|
+
const linkTemplate = caps["nav.link.template"] || caps["nav.link.template.self"];
|
|
318
|
+
if (linkTemplate) {
|
|
319
|
+
navigations.push({ kind: "link-href", target: linkTemplate, isTemplate: true });
|
|
242
320
|
}
|
|
243
|
-
if (
|
|
244
|
-
|
|
321
|
+
if (caps["nav.window.literal"]) {
|
|
322
|
+
navigations.push({ kind: "window-location", target: caps["nav.window.literal"], isTemplate: false });
|
|
245
323
|
}
|
|
246
|
-
if (
|
|
247
|
-
|
|
324
|
+
if (caps["nav.window.assign.target"]) {
|
|
325
|
+
navigations.push({ kind: "window-location", target: caps["nav.window.assign.target"], isTemplate: false });
|
|
248
326
|
}
|
|
249
|
-
|
|
250
|
-
|
|
327
|
+
}
|
|
328
|
+
const fetchCalls = [];
|
|
329
|
+
const fetchQuery = getQuery("fetch-calls");
|
|
330
|
+
const fetchMatches = fetchQuery.matches(root);
|
|
331
|
+
for (const m of fetchMatches) {
|
|
332
|
+
const caps = captureMap(m);
|
|
333
|
+
if (caps["fetch.url.literal"] && looksLikeUrl(caps["fetch.url.literal"])) {
|
|
334
|
+
fetchCalls.push({ url: caps["fetch.url.literal"], isTemplate: false, kind: "fetch" });
|
|
335
|
+
}
|
|
336
|
+
if (caps["fetch.url.template"] && templateStartsWithSlash(caps["fetch.url.template"])) {
|
|
337
|
+
fetchCalls.push({ url: caps["fetch.url.template"], isTemplate: true, kind: "fetch" });
|
|
338
|
+
}
|
|
339
|
+
const clientUrl = caps["fetch.client.url.literal"] || caps["fetch.await.url.literal"];
|
|
340
|
+
const clientMethod = caps["fetch.method"] || caps["fetch.await.method"];
|
|
341
|
+
if (clientUrl && clientMethod && looksLikeUrl(clientUrl)) {
|
|
342
|
+
fetchCalls.push({
|
|
343
|
+
method: clientMethod.toUpperCase(),
|
|
344
|
+
url: clientUrl,
|
|
345
|
+
isTemplate: false,
|
|
346
|
+
kind: "client-method"
|
|
347
|
+
});
|
|
251
348
|
}
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
}
|
|
262
|
-
}
|
|
349
|
+
const clientUrlTpl = caps["fetch.client.url.template"] || caps["fetch.await.url.template"];
|
|
350
|
+
const clientMethodTpl = caps["fetch.method.template"] || caps["fetch.await.method.template"];
|
|
351
|
+
if (clientUrlTpl && clientMethodTpl && templateStartsWithSlash(clientUrlTpl)) {
|
|
352
|
+
fetchCalls.push({
|
|
353
|
+
method: clientMethodTpl.toUpperCase(),
|
|
354
|
+
url: clientUrlTpl,
|
|
355
|
+
isTemplate: true,
|
|
356
|
+
kind: "client-method"
|
|
357
|
+
});
|
|
263
358
|
}
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
359
|
+
}
|
|
360
|
+
const name = defaultName ?? firstValueExport ?? firstTypeExport ?? "";
|
|
361
|
+
return { name, exports: exportsOrdered, imports, reExports, jsxElements, navigations, fetchCalls };
|
|
362
|
+
}
|
|
363
|
+
function extractDbCallsTS(absPath) {
|
|
364
|
+
const tree = parseSource(absPath);
|
|
365
|
+
const root = tree.rootNode;
|
|
366
|
+
const dbQuery = getQuery("db-calls");
|
|
367
|
+
const matches = dbQuery.matches(root);
|
|
368
|
+
const dbIdentifiers = /* @__PURE__ */ new Set();
|
|
369
|
+
for (const stmt of childrenOfType(root, "import_statement")) {
|
|
370
|
+
const sourceNode = childOfType(stmt, "string");
|
|
371
|
+
const frag = sourceNode ? childOfType(sourceNode, "string_fragment") : void 0;
|
|
372
|
+
if (!frag) continue;
|
|
373
|
+
const spec = frag.text;
|
|
374
|
+
if (spec.includes("prisma") || spec.includes("/db") || spec === "@prisma/client") {
|
|
375
|
+
const clause = childOfType(stmt, "import_clause");
|
|
376
|
+
if (clause) {
|
|
377
|
+
const defaultId = childOfType(clause, "identifier");
|
|
378
|
+
if (defaultId) dbIdentifiers.add(defaultId.text);
|
|
379
|
+
const named = childOfType(clause, "named_imports");
|
|
380
|
+
if (named) {
|
|
381
|
+
for (const specNode of childrenOfType(named, "import_specifier")) {
|
|
382
|
+
const ids = childrenOfType(specNode, "identifier");
|
|
383
|
+
const importedName = ids[ids.length - 1];
|
|
384
|
+
if (importedName) dbIdentifiers.add(importedName.text);
|
|
276
385
|
}
|
|
277
386
|
}
|
|
278
387
|
}
|
|
279
388
|
}
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
389
|
+
}
|
|
390
|
+
if (dbIdentifiers.size === 0) {
|
|
391
|
+
for (const id of getFallbackDbIdentifiers()) dbIdentifiers.add(id);
|
|
392
|
+
} else {
|
|
393
|
+
for (const id of extraDbIdentifiers) dbIdentifiers.add(id);
|
|
394
|
+
}
|
|
395
|
+
const calls = [];
|
|
396
|
+
const seen = /* @__PURE__ */ new Set();
|
|
397
|
+
for (const m of matches) {
|
|
398
|
+
const caps = captureMap(m);
|
|
399
|
+
const identifier = caps["db.identifier"];
|
|
400
|
+
const model = caps["db.model"];
|
|
401
|
+
const method = caps["db.method"];
|
|
402
|
+
if (!identifier || !model || !method) continue;
|
|
403
|
+
if (!dbIdentifiers.has(identifier)) continue;
|
|
404
|
+
const key = `${model}.${method}`;
|
|
405
|
+
if (seen.has(key)) continue;
|
|
406
|
+
seen.add(key);
|
|
407
|
+
calls.push({ model, method, isMutation: getMutationMethods().has(method) });
|
|
408
|
+
}
|
|
409
|
+
return calls;
|
|
410
|
+
}
|
|
411
|
+
function extractAuthWrappersTS(absPath) {
|
|
412
|
+
const tree = parseSource(absPath);
|
|
413
|
+
const root = tree.rootNode;
|
|
414
|
+
const wrapperQuery = getQuery("wrappers");
|
|
415
|
+
const matches = wrapperQuery.matches(root);
|
|
416
|
+
const wrappers = /* @__PURE__ */ new Set();
|
|
417
|
+
for (const m of matches) {
|
|
418
|
+
const caps = captureMap(m);
|
|
419
|
+
if (caps["wrapper.fn_name"]) {
|
|
420
|
+
wrappers.add(caps["wrapper.fn_name"]);
|
|
309
421
|
}
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
422
|
+
}
|
|
423
|
+
return wrappers;
|
|
424
|
+
}
|
|
425
|
+
function trunc(s, max = 120) {
|
|
426
|
+
return s.length > max ? s.slice(0, max) + "..." : s;
|
|
427
|
+
}
|
|
428
|
+
function extractDeep(absPath) {
|
|
429
|
+
const tree = parseSource(absPath);
|
|
430
|
+
const root = tree.rootNode;
|
|
431
|
+
const elements = [];
|
|
432
|
+
const elQuery = getQuery("deep/jsx-semantic");
|
|
433
|
+
const elMatches = elQuery.matches(root);
|
|
434
|
+
const elementMap = /* @__PURE__ */ new Map();
|
|
435
|
+
for (const m of elMatches) {
|
|
436
|
+
const tag = m.captures.find((c) => c.name === "el.tag")?.node;
|
|
437
|
+
if (!tag || !/^[A-Z]/.test(tag.text)) continue;
|
|
438
|
+
const elNode = m.captures.find((c) => c.name === "el.self" || c.name === "el.open")?.node;
|
|
439
|
+
const key = elNode ? `${elNode.startPosition.row}:${elNode.startPosition.column}` : `${tag.startPosition.row}:${tag.startPosition.column}`;
|
|
440
|
+
if (!elementMap.has(key)) {
|
|
441
|
+
elementMap.set(key, { tag: tag.text, props: {}, nodeKey: key });
|
|
442
|
+
}
|
|
443
|
+
const entry = elementMap.get(key);
|
|
444
|
+
const propName = m.captures.find((c) => c.name === "el.prop.name")?.node.text;
|
|
445
|
+
const propVal = m.captures.find((c) => c.name === "el.prop.value.str")?.node.text;
|
|
446
|
+
const propExpr = m.captures.find((c) => c.name === "el.prop.value.expr")?.node.text;
|
|
447
|
+
if (propName) {
|
|
448
|
+
entry.props[propName] = propVal ?? (propExpr ? trunc(propExpr, 60) : "true");
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
for (const m of elMatches) {
|
|
452
|
+
const textTag = m.captures.find((c) => c.name === "el.text.tag")?.node;
|
|
453
|
+
const textContent = m.captures.find((c) => c.name === "el.text.content")?.node;
|
|
454
|
+
if (textTag && textContent && /^[A-Z]/.test(textTag.text)) {
|
|
455
|
+
const key = `${textTag.startPosition.row}:${textTag.startPosition.column}`;
|
|
456
|
+
if (!elementMap.has(key)) {
|
|
457
|
+
elementMap.set(key, { tag: textTag.text, props: {}, nodeKey: key });
|
|
326
458
|
}
|
|
459
|
+
const entry = elementMap.get(key);
|
|
460
|
+
const text = textContent.text.trim();
|
|
461
|
+
if (text) entry.props["_text"] = text;
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
for (const entry of elementMap.values()) {
|
|
465
|
+
const el = { tag: entry.tag, props: entry.props };
|
|
466
|
+
const hasExpr = Object.values(entry.props).some((v) => v.includes("{") || v.includes("("));
|
|
467
|
+
if (hasExpr) el.dynamic = true;
|
|
468
|
+
if (entry.props["_text"]) {
|
|
469
|
+
el.text = entry.props["_text"];
|
|
470
|
+
delete el.props["_text"];
|
|
471
|
+
}
|
|
472
|
+
elements.push(el);
|
|
473
|
+
}
|
|
474
|
+
const stateVars = [];
|
|
475
|
+
const hookQuery = getQuery("deep/state-hooks");
|
|
476
|
+
const hookMatches = hookQuery.matches(root);
|
|
477
|
+
for (const m of hookMatches) {
|
|
478
|
+
const caps = captureMap(m);
|
|
479
|
+
if (caps["hook.var"] && caps["hook.setter"] && caps["hook.fn"]) {
|
|
480
|
+
stateVars.push({
|
|
481
|
+
name: caps["hook.var"],
|
|
482
|
+
setter: caps["hook.setter"],
|
|
483
|
+
hook: caps["hook.fn"],
|
|
484
|
+
init: trunc(caps["hook.init"] ?? "", 80)
|
|
485
|
+
});
|
|
327
486
|
}
|
|
328
|
-
if (
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
}
|
|
487
|
+
if (caps["reducer.var"] && caps["reducer.dispatch"]) {
|
|
488
|
+
stateVars.push({
|
|
489
|
+
name: caps["reducer.var"],
|
|
490
|
+
setter: caps["reducer.dispatch"],
|
|
491
|
+
hook: "useReducer",
|
|
492
|
+
init: trunc(caps["reducer.init"] ?? "", 80)
|
|
493
|
+
});
|
|
336
494
|
}
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
495
|
+
}
|
|
496
|
+
const conditions = [];
|
|
497
|
+
const condQuery = getQuery("deep/conditions");
|
|
498
|
+
const condMatches = condQuery.matches(root);
|
|
499
|
+
for (const m of condMatches) {
|
|
500
|
+
const caps = captureMap(m);
|
|
501
|
+
if (caps["cond.test"]) {
|
|
502
|
+
conditions.push({
|
|
503
|
+
kind: "if",
|
|
504
|
+
test: trunc(caps["cond.test"], 100),
|
|
505
|
+
consequence: caps["cond.consequence"] ? trunc(caps["cond.consequence"], 80) : void 0
|
|
506
|
+
});
|
|
507
|
+
}
|
|
508
|
+
if (caps["ternary.test"]) {
|
|
509
|
+
conditions.push({
|
|
510
|
+
kind: "ternary",
|
|
511
|
+
test: trunc(caps["ternary.test"], 100)
|
|
512
|
+
});
|
|
348
513
|
}
|
|
349
|
-
ts.forEachChild(node, visit);
|
|
350
514
|
}
|
|
351
|
-
|
|
352
|
-
const
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
const scriptKind = ext === ".tsx" ? ts.ScriptKind.TSX : ts.ScriptKind.TS;
|
|
379
|
-
const sourceFile = ts.createSourceFile(absPath, content, ts.ScriptTarget.Latest, true, scriptKind);
|
|
380
|
-
const calls = [];
|
|
381
|
-
const seen = /* @__PURE__ */ new Set();
|
|
382
|
-
function visit(node) {
|
|
383
|
-
if (ts.isCallExpression(node)) {
|
|
384
|
-
const expr = node.expression;
|
|
385
|
-
if (ts.isPropertyAccessExpression(expr) && ts.isPropertyAccessExpression(expr.expression) && ts.isIdentifier(expr.expression.expression) && expr.expression.expression.text === "db") {
|
|
386
|
-
const model = expr.expression.name.text;
|
|
387
|
-
const method = expr.name.text;
|
|
388
|
-
const key = `${model}.${method}`;
|
|
389
|
-
if (!seen.has(key)) {
|
|
390
|
-
seen.add(key);
|
|
391
|
-
calls.push({
|
|
392
|
-
model,
|
|
393
|
-
method,
|
|
394
|
-
isMutation: MUTATION_METHODS.has(method)
|
|
395
|
-
});
|
|
396
|
-
}
|
|
397
|
-
}
|
|
515
|
+
const variables = [];
|
|
516
|
+
const varQuery = getQuery("deep/variables");
|
|
517
|
+
const varMatches = varQuery.matches(root);
|
|
518
|
+
for (const m of varMatches) {
|
|
519
|
+
const caps = captureMap(m);
|
|
520
|
+
const declNode = m.captures.find((c) => c.name === "var.decl" || c.name === "var.destructured" || c.name === "var.array")?.node;
|
|
521
|
+
const kind = declNode?.children.find((n) => n.type === "const" || n.type === "let" || n.type === "var")?.type ?? "const";
|
|
522
|
+
if (caps["var.name"] && caps["var.init"]) {
|
|
523
|
+
variables.push({
|
|
524
|
+
name: caps["var.name"],
|
|
525
|
+
kind,
|
|
526
|
+
init: trunc(caps["var.init"], 100)
|
|
527
|
+
});
|
|
528
|
+
}
|
|
529
|
+
if (caps["var.destructured.obj"]) {
|
|
530
|
+
variables.push({
|
|
531
|
+
name: trunc(caps["var.destructured.obj"], 60),
|
|
532
|
+
kind,
|
|
533
|
+
init: trunc(caps["var.destructured.init"], 100)
|
|
534
|
+
});
|
|
535
|
+
}
|
|
536
|
+
if (caps["var.array.pattern"]) {
|
|
537
|
+
variables.push({
|
|
538
|
+
name: trunc(caps["var.array.pattern"], 60),
|
|
539
|
+
kind,
|
|
540
|
+
init: trunc(caps["var.array.init"], 100)
|
|
541
|
+
});
|
|
398
542
|
}
|
|
399
|
-
ts.forEachChild(node, visit);
|
|
400
543
|
}
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
const
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
544
|
+
const responses = [];
|
|
545
|
+
const respQuery = getQuery("deep/responses");
|
|
546
|
+
const respMatches = respQuery.matches(root);
|
|
547
|
+
const explicitBodies = /* @__PURE__ */ new Set();
|
|
548
|
+
for (const m of respMatches) {
|
|
549
|
+
const caps = captureMap(m);
|
|
550
|
+
if (caps["resp.status"] && caps["resp.body"]) {
|
|
551
|
+
explicitBodies.add(trunc(caps["resp.body"], 80));
|
|
552
|
+
responses.push({
|
|
553
|
+
status: caps["resp.status"],
|
|
554
|
+
body: trunc(caps["resp.body"], 80)
|
|
555
|
+
});
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
for (const m of respMatches) {
|
|
559
|
+
const caps = captureMap(m);
|
|
560
|
+
if (caps["resp.body.default"] && !caps["resp.status"]) {
|
|
561
|
+
const bodyText = trunc(caps["resp.body.default"], 80);
|
|
562
|
+
if (!explicitBodies.has(bodyText)) {
|
|
563
|
+
responses.push({ status: "200", body: bodyText });
|
|
416
564
|
}
|
|
417
565
|
}
|
|
418
|
-
ts.forEachChild(node, visit);
|
|
419
566
|
}
|
|
420
|
-
|
|
421
|
-
|
|
567
|
+
const params = [];
|
|
568
|
+
const paramQuery = getQuery("deep/request-params");
|
|
569
|
+
const paramMatches = paramQuery.matches(root);
|
|
570
|
+
for (const m of paramMatches) {
|
|
571
|
+
const caps = captureMap(m);
|
|
572
|
+
if (caps["param.name"]) {
|
|
573
|
+
params.push({ name: caps["param.name"], source: "body-field" });
|
|
574
|
+
}
|
|
575
|
+
if (caps["param.body"]) {
|
|
576
|
+
params.push({ name: caps["param.body"], source: "body" });
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
return { elements, stateVars, conditions, variables, responses, params };
|
|
422
580
|
}
|
|
423
581
|
|
|
424
582
|
// src/server/graph/parsers/ui/react-nextjs.ts
|
|
@@ -744,7 +902,7 @@ function generate(rootDir) {
|
|
|
744
902
|
const allDiscovered = [...appFiles, ...clientFiles, ...serverFiles, ...libFiles, ...configFiles];
|
|
745
903
|
const parsedByPath = /* @__PURE__ */ new Map();
|
|
746
904
|
for (const absPath of allDiscovered) {
|
|
747
|
-
parsedByPath.set(absPath,
|
|
905
|
+
parsedByPath.set(absPath, parseFileTS(absPath));
|
|
748
906
|
}
|
|
749
907
|
const barrelMaps = buildAllBarrelMaps(srcDir, parsedByPath);
|
|
750
908
|
const fileSet = allDiscovered.filter((f) => !(0, import_node_path3.basename)(f).startsWith("index."));
|
|
@@ -758,7 +916,18 @@ function generate(rootDir) {
|
|
|
758
916
|
const parsed = parsedByPath.get(absPath);
|
|
759
917
|
const name = parsed.name || nameFromFilename(absPath);
|
|
760
918
|
const route = extractRoute(id);
|
|
761
|
-
|
|
919
|
+
const deep = extractDeep(absPath);
|
|
920
|
+
nodes.push({
|
|
921
|
+
id,
|
|
922
|
+
type,
|
|
923
|
+
name,
|
|
924
|
+
route,
|
|
925
|
+
exports: parsed.exports,
|
|
926
|
+
elements: deep.elements,
|
|
927
|
+
stateVars: deep.stateVars,
|
|
928
|
+
conditions: deep.conditions,
|
|
929
|
+
variables: deep.variables
|
|
930
|
+
});
|
|
762
931
|
nodeIdSet.add(id);
|
|
763
932
|
nodeTypeMap.set(id, type);
|
|
764
933
|
if (route) routeToNodeId.set(route, id);
|
|
@@ -817,7 +986,7 @@ function generate(rootDir) {
|
|
|
817
986
|
if (externalScanned.has(normalized)) continue;
|
|
818
987
|
let parsed;
|
|
819
988
|
try {
|
|
820
|
-
parsed =
|
|
989
|
+
parsed = parseFileTS(absPath);
|
|
821
990
|
} catch {
|
|
822
991
|
continue;
|
|
823
992
|
}
|
|
@@ -941,7 +1110,7 @@ var reactNextjsParser = {
|
|
|
941
1110
|
// src/server/graph/parsers/api/nextjs-routes.ts
|
|
942
1111
|
var import_node_fs4 = require("node:fs");
|
|
943
1112
|
var import_node_path4 = require("node:path");
|
|
944
|
-
var
|
|
1113
|
+
var HTTP_METHODS = /* @__PURE__ */ new Set(["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"]);
|
|
945
1114
|
function walk2(dir) {
|
|
946
1115
|
const results = [];
|
|
947
1116
|
if (!(0, import_node_fs4.existsSync)(dir)) return results;
|
|
@@ -980,12 +1149,12 @@ function generate2(rootDir) {
|
|
|
980
1149
|
let endpointsWithAuth = 0;
|
|
981
1150
|
let endpointsWithDbAccess = 0;
|
|
982
1151
|
for (const absPath of routeFiles) {
|
|
983
|
-
const parsed =
|
|
984
|
-
const dbCalls =
|
|
985
|
-
const authWrappers =
|
|
1152
|
+
const parsed = parseFileTS(absPath);
|
|
1153
|
+
const dbCalls = extractDbCallsTS(absPath);
|
|
1154
|
+
const authWrappers = extractAuthWrappersTS(absPath);
|
|
986
1155
|
const methods = [];
|
|
987
1156
|
for (const exp of parsed.exports) {
|
|
988
|
-
if (
|
|
1157
|
+
if (HTTP_METHODS.has(exp)) methods.push(exp);
|
|
989
1158
|
}
|
|
990
1159
|
const routePath = filePathToRoute(apiDir, absPath);
|
|
991
1160
|
const relPath = (0, import_node_path4.relative)(rootDir, absPath).replace(/\\/g, "/");
|
|
@@ -1004,6 +1173,7 @@ function generate2(rootDir) {
|
|
|
1004
1173
|
authUsage[w] = (authUsage[w] ?? 0) + 1;
|
|
1005
1174
|
}
|
|
1006
1175
|
if (authStrategy.length > 0) endpointsWithAuth++;
|
|
1176
|
+
const deep = extractDeep(absPath);
|
|
1007
1177
|
nodes.push({
|
|
1008
1178
|
id: relPath,
|
|
1009
1179
|
type: "endpoint",
|
|
@@ -1015,7 +1185,12 @@ function generate2(rootDir) {
|
|
|
1015
1185
|
mutates,
|
|
1016
1186
|
auth: authStrategy.length > 0 ? authStrategy : ["public"],
|
|
1017
1187
|
db_models: [...new Set(dbCalls.map((c) => c.model))],
|
|
1018
|
-
db_operations: [...new Set(dbCalls.map((c) => `${c.model}.${c.method}`))]
|
|
1188
|
+
db_operations: [...new Set(dbCalls.map((c) => `${c.model}.${c.method}`))],
|
|
1189
|
+
// Deep extraction fields
|
|
1190
|
+
conditions: deep.conditions,
|
|
1191
|
+
variables: deep.variables,
|
|
1192
|
+
responses: deep.responses,
|
|
1193
|
+
params: deep.params
|
|
1019
1194
|
});
|
|
1020
1195
|
const seenModels = /* @__PURE__ */ new Set();
|
|
1021
1196
|
for (const call of dbCalls) {
|
|
@@ -1055,7 +1230,7 @@ function generate2(rootDir) {
|
|
|
1055
1230
|
flagged_edges: [],
|
|
1056
1231
|
patterns: {
|
|
1057
1232
|
total_endpoints: nodes.length,
|
|
1058
|
-
methods_breakdown: [...
|
|
1233
|
+
methods_breakdown: [...HTTP_METHODS].reduce((acc, m) => {
|
|
1059
1234
|
acc[m] = nodes.filter((n) => n.methods.includes(m)).length;
|
|
1060
1235
|
return acc;
|
|
1061
1236
|
}, {}),
|
|
@@ -1910,9 +2085,10 @@ function matchParts(pat, pi, id, ii) {
|
|
|
1910
2085
|
while (pi < pat.length && pat[pi] === "**") pi++;
|
|
1911
2086
|
return pi === pat.length && ii === id.length;
|
|
1912
2087
|
}
|
|
1913
|
-
var
|
|
1914
|
-
function detectConventionDirs(rootDir) {
|
|
2088
|
+
var CONVENTION_DIRS_BUILTIN = ["features", "modules", "domains", "areas"];
|
|
2089
|
+
function detectConventionDirs(rootDir, extraConventionDirs = []) {
|
|
1915
2090
|
const result = /* @__PURE__ */ new Map();
|
|
2091
|
+
const conventionDirs = [...CONVENTION_DIRS_BUILTIN, ...extraConventionDirs];
|
|
1916
2092
|
const searchDirs = [
|
|
1917
2093
|
rootDir,
|
|
1918
2094
|
(0, import_node_path10.join)(rootDir, "src"),
|
|
@@ -1920,7 +2096,7 @@ function detectConventionDirs(rootDir) {
|
|
|
1920
2096
|
(0, import_node_path10.join)(rootDir, "lib")
|
|
1921
2097
|
];
|
|
1922
2098
|
for (const base of searchDirs) {
|
|
1923
|
-
for (const convention of
|
|
2099
|
+
for (const convention of conventionDirs) {
|
|
1924
2100
|
const dir = (0, import_node_path10.join)(base, convention);
|
|
1925
2101
|
if (!(0, import_node_fs9.existsSync)(dir)) continue;
|
|
1926
2102
|
try {
|
|
@@ -1946,7 +2122,76 @@ function extractRouteGroups(id) {
|
|
|
1946
2122
|
}
|
|
1947
2123
|
return groups;
|
|
1948
2124
|
}
|
|
1949
|
-
var
|
|
2125
|
+
var GENERIC_ROLE_NAMES_BUILTIN = /* @__PURE__ */ new Set([
|
|
2126
|
+
// JS/TS
|
|
2127
|
+
"components",
|
|
2128
|
+
"hooks",
|
|
2129
|
+
"pages",
|
|
2130
|
+
"views",
|
|
2131
|
+
"screens",
|
|
2132
|
+
"layouts",
|
|
2133
|
+
"utils",
|
|
2134
|
+
"helpers",
|
|
2135
|
+
"lib",
|
|
2136
|
+
"libs",
|
|
2137
|
+
"services",
|
|
2138
|
+
"api",
|
|
2139
|
+
"apis",
|
|
2140
|
+
"stores",
|
|
2141
|
+
"state",
|
|
2142
|
+
"store",
|
|
2143
|
+
"context",
|
|
2144
|
+
"contexts",
|
|
2145
|
+
"providers",
|
|
2146
|
+
"types",
|
|
2147
|
+
"interfaces",
|
|
2148
|
+
"models",
|
|
2149
|
+
"schemas",
|
|
2150
|
+
"constants",
|
|
2151
|
+
"config",
|
|
2152
|
+
"configs",
|
|
2153
|
+
"assets",
|
|
2154
|
+
"styles",
|
|
2155
|
+
"public",
|
|
2156
|
+
"middleware",
|
|
2157
|
+
"middlewares",
|
|
2158
|
+
"routes",
|
|
2159
|
+
"router",
|
|
2160
|
+
"tests",
|
|
2161
|
+
"test",
|
|
2162
|
+
"__tests__",
|
|
2163
|
+
"spec",
|
|
2164
|
+
"specs",
|
|
2165
|
+
// Go
|
|
2166
|
+
"cmd",
|
|
2167
|
+
"pkg",
|
|
2168
|
+
"internal",
|
|
2169
|
+
// Python
|
|
2170
|
+
"management",
|
|
2171
|
+
"migrations",
|
|
2172
|
+
"templatetags",
|
|
2173
|
+
"templates",
|
|
2174
|
+
// Java
|
|
2175
|
+
"controller",
|
|
2176
|
+
"controllers",
|
|
2177
|
+
"service",
|
|
2178
|
+
"repository",
|
|
2179
|
+
"repositories",
|
|
2180
|
+
"entity",
|
|
2181
|
+
"entities",
|
|
2182
|
+
"dto",
|
|
2183
|
+
"dtos",
|
|
2184
|
+
// General
|
|
2185
|
+
"shared",
|
|
2186
|
+
"common",
|
|
2187
|
+
"core",
|
|
2188
|
+
"base",
|
|
2189
|
+
"app",
|
|
2190
|
+
// Next.js specific
|
|
2191
|
+
"client",
|
|
2192
|
+
"server"
|
|
2193
|
+
]);
|
|
2194
|
+
var SKIP_SEGMENTS_BUILTIN = /* @__PURE__ */ new Set([
|
|
1950
2195
|
"src",
|
|
1951
2196
|
"app",
|
|
1952
2197
|
"client",
|
|
@@ -2004,9 +2249,13 @@ function isTrivialGroup(name, extraTrivial) {
|
|
|
2004
2249
|
function normalizeGroupName(name) {
|
|
2005
2250
|
return name.toLowerCase().replace(/-pages?$/, "").replace(/-layout$/, "").replace(/-wrapper$/, "");
|
|
2006
2251
|
}
|
|
2007
|
-
function extractModuleFromPath(id, extraTrivial) {
|
|
2252
|
+
function extractModuleFromPath(id, extraTrivial, extraSkipSegments) {
|
|
2008
2253
|
const segments = id.split("/");
|
|
2009
2254
|
const routeGroups = extractRouteGroups(id);
|
|
2255
|
+
const skipSegments = new Set(SKIP_SEGMENTS_BUILTIN);
|
|
2256
|
+
if (extraSkipSegments) {
|
|
2257
|
+
for (const s of extraSkipSegments) skipSegments.add(s);
|
|
2258
|
+
}
|
|
2010
2259
|
const moduleGroups = routeGroups.filter((g) => !isTrivialGroup(g, extraTrivial)).map(normalizeGroupName);
|
|
2011
2260
|
if (moduleGroups.length > 0) {
|
|
2012
2261
|
return moduleGroups[moduleGroups.length - 1];
|
|
@@ -2017,7 +2266,7 @@ function extractModuleFromPath(id, extraTrivial) {
|
|
|
2017
2266
|
if (isRouteGroup(seg)) continue;
|
|
2018
2267
|
if (isDynamicSegment(seg)) continue;
|
|
2019
2268
|
if (isDomainDir(seg)) continue;
|
|
2020
|
-
if (
|
|
2269
|
+
if (skipSegments.has(seg)) continue;
|
|
2021
2270
|
meaningful.push(seg);
|
|
2022
2271
|
}
|
|
2023
2272
|
if (meaningful.length > 0) {
|
|
@@ -2034,12 +2283,10 @@ var moduleTagger = {
|
|
|
2034
2283
|
layers: null,
|
|
2035
2284
|
// applies to all layers
|
|
2036
2285
|
tag(nodes, layer, rootDir) {
|
|
2037
|
-
if (cachedRootDir !== rootDir) {
|
|
2038
|
-
cachedConventionDirs = detectConventionDirs(rootDir);
|
|
2039
|
-
cachedRootDir = rootDir;
|
|
2040
|
-
}
|
|
2041
2286
|
let configRules = [];
|
|
2042
2287
|
let extraTrivial;
|
|
2288
|
+
let extraSkipSegments;
|
|
2289
|
+
let extraConventionDirs = [];
|
|
2043
2290
|
try {
|
|
2044
2291
|
const { loadConfig: loadConfig2 } = (init_config(), __toCommonJS(config_exports));
|
|
2045
2292
|
const config = loadConfig2(rootDir);
|
|
@@ -2048,8 +2295,21 @@ var moduleTagger = {
|
|
|
2048
2295
|
if (trivialFromConfig?.length) {
|
|
2049
2296
|
extraTrivial = new Set(trivialFromConfig);
|
|
2050
2297
|
}
|
|
2298
|
+
const skipFromConfig = config.taggers?.module?.skipSegments;
|
|
2299
|
+
if (skipFromConfig?.length) {
|
|
2300
|
+
extraSkipSegments = new Set(skipFromConfig);
|
|
2301
|
+
}
|
|
2302
|
+
extraConventionDirs = config.taggers?.module?.conventionDirs ?? [];
|
|
2303
|
+
const roleNamesFromConfig = config.taggers?.module?.genericRoleNames;
|
|
2304
|
+
if (roleNamesFromConfig?.length) {
|
|
2305
|
+
for (const name of roleNamesFromConfig) GENERIC_ROLE_NAMES_BUILTIN.add(name);
|
|
2306
|
+
}
|
|
2051
2307
|
} catch {
|
|
2052
2308
|
}
|
|
2309
|
+
if (cachedRootDir !== rootDir) {
|
|
2310
|
+
cachedConventionDirs = detectConventionDirs(rootDir, extraConventionDirs);
|
|
2311
|
+
cachedRootDir = rootDir;
|
|
2312
|
+
}
|
|
2053
2313
|
const result = /* @__PURE__ */ new Map();
|
|
2054
2314
|
for (const node of nodes) {
|
|
2055
2315
|
const id = node.id;
|
|
@@ -2075,7 +2335,7 @@ var moduleTagger = {
|
|
|
2075
2335
|
}
|
|
2076
2336
|
}
|
|
2077
2337
|
if (matched) continue;
|
|
2078
|
-
const module2 = extractModuleFromPath(id, extraTrivial);
|
|
2338
|
+
const module2 = extractModuleFromPath(id, extraTrivial, extraSkipSegments);
|
|
2079
2339
|
result.set(id, module2);
|
|
2080
2340
|
}
|
|
2081
2341
|
return result;
|
|
@@ -2303,7 +2563,13 @@ function readAllGraphs(rootDir) {
|
|
|
2303
2563
|
}
|
|
2304
2564
|
return result;
|
|
2305
2565
|
}
|
|
2306
|
-
function generateGraph(rootDir, layer) {
|
|
2566
|
+
async function generateGraph(rootDir, layer) {
|
|
2567
|
+
await initTreeSitter();
|
|
2568
|
+
const config = loadConfig(rootDir);
|
|
2569
|
+
setExtractorConfig({
|
|
2570
|
+
dbIdentifiers: config.parsers?.patterns?.dbIdentifiers,
|
|
2571
|
+
mutationMethods: config.parsers?.patterns?.mutationMethods
|
|
2572
|
+
});
|
|
2307
2573
|
const dir = graphsDir(rootDir);
|
|
2308
2574
|
(0, import_node_fs11.mkdirSync)(dir, { recursive: true });
|
|
2309
2575
|
const results = layer ? [generateLayer(rootDir, layer)].filter((r) => r !== null) : generateAll(rootDir);
|
|
@@ -2443,10 +2709,10 @@ function findProjectRoot(startDir) {
|
|
|
2443
2709
|
}
|
|
2444
2710
|
return startDir;
|
|
2445
2711
|
}
|
|
2446
|
-
function buildMergedGraph(projectRoot) {
|
|
2712
|
+
async function buildMergedGraph(projectRoot) {
|
|
2447
2713
|
let graphs = readAllGraphs(projectRoot);
|
|
2448
2714
|
if (!graphs.ui && !graphs.api && !graphs.db) {
|
|
2449
|
-
generateGraph(projectRoot);
|
|
2715
|
+
await generateGraph(projectRoot);
|
|
2450
2716
|
graphs = readAllGraphs(projectRoot);
|
|
2451
2717
|
}
|
|
2452
2718
|
const nodes = [];
|
|
@@ -2556,13 +2822,18 @@ async function startChartServer(opts = {}) {
|
|
|
2556
2822
|
const url2 = new URL(req.url ?? "/", `http://${req.headers.host}`);
|
|
2557
2823
|
if (req.method === "GET" && url2.pathname === "/api/project-graph") {
|
|
2558
2824
|
const regenerate = url2.searchParams.get("regenerate") === "1";
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2825
|
+
(async () => {
|
|
2826
|
+
if (regenerate) await generateGraph(projectRoot);
|
|
2827
|
+
const merged = await buildMergedGraph(projectRoot);
|
|
2828
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
2829
|
+
res.end(JSON.stringify({
|
|
2830
|
+
...merged,
|
|
2831
|
+
debug: { cwd, projectRoot }
|
|
2832
|
+
}));
|
|
2833
|
+
})().catch((e) => {
|
|
2834
|
+
res.writeHead(500);
|
|
2835
|
+
res.end(String(e));
|
|
2836
|
+
});
|
|
2566
2837
|
return;
|
|
2567
2838
|
}
|
|
2568
2839
|
if (req.method === "GET" && url2.pathname === "/api/raw-graphs") {
|
|
@@ -2572,8 +2843,8 @@ async function startChartServer(opts = {}) {
|
|
|
2572
2843
|
return;
|
|
2573
2844
|
}
|
|
2574
2845
|
if (req.method === "POST" && url2.pathname === "/api/generate-graph") {
|
|
2575
|
-
|
|
2576
|
-
generateGraph(projectRoot);
|
|
2846
|
+
(async () => {
|
|
2847
|
+
await generateGraph(projectRoot);
|
|
2577
2848
|
const graphs = readAllGraphs(projectRoot);
|
|
2578
2849
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
2579
2850
|
res.end(JSON.stringify({
|
|
@@ -2582,10 +2853,10 @@ async function startChartServer(opts = {}) {
|
|
|
2582
2853
|
api: graphs.api ?? null,
|
|
2583
2854
|
db: graphs.db ?? null
|
|
2584
2855
|
}));
|
|
2585
|
-
}
|
|
2856
|
+
})().catch((err) => {
|
|
2586
2857
|
res.writeHead(500, { "Content-Type": "application/json" });
|
|
2587
2858
|
res.end(JSON.stringify({ ok: false, error: String(err) }));
|
|
2588
|
-
}
|
|
2859
|
+
});
|
|
2589
2860
|
return;
|
|
2590
2861
|
}
|
|
2591
2862
|
if (req.method === "GET" && url2.pathname === "/api/file-content") {
|