@launchsecure/launch-kit 0.0.20 → 0.0.22
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/server/chart-serve.js +336 -95
- package/dist/server/cli.js +338 -97
- package/dist/server/graph/queries/classify.scm +8 -0
- package/dist/server/graph/queries/db-calls.scm +21 -0
- package/dist/server/graph/queries/deep/jsx-semantic.scm +13 -2
- package/dist/server/graph/queries/navigations.scm +28 -12
- package/dist/server/graph-mcp-entry.js +342 -97
- package/package.json +1 -1
|
@@ -631,6 +631,12 @@ function parseFileTS(absPath) {
|
|
|
631
631
|
if (linkTemplate) {
|
|
632
632
|
navigations.push({ kind: "link-href", target: linkTemplate, isTemplate: true });
|
|
633
633
|
}
|
|
634
|
+
if (caps["nav.redirect.literal"]) {
|
|
635
|
+
navigations.push({ kind: "router-replace", target: caps["nav.redirect.literal"], isTemplate: false });
|
|
636
|
+
}
|
|
637
|
+
if (caps["nav.redirect.template"]) {
|
|
638
|
+
navigations.push({ kind: "router-replace", target: caps["nav.redirect.template"], isTemplate: true });
|
|
639
|
+
}
|
|
634
640
|
if (caps["nav.window.literal"]) {
|
|
635
641
|
navigations.push({ kind: "window-location", target: caps["nav.window.literal"], isTemplate: false });
|
|
636
642
|
}
|
|
@@ -709,15 +715,25 @@ function extractDbCallsTS(absPath) {
|
|
|
709
715
|
const seen = /* @__PURE__ */ new Set();
|
|
710
716
|
for (const m of matches) {
|
|
711
717
|
const caps = captureMap(m);
|
|
718
|
+
const sbTable = caps["sb.table"];
|
|
719
|
+
const sbMethod = caps["sb.method"];
|
|
720
|
+
if (sbTable && sbMethod) {
|
|
721
|
+
const key2 = `sql:${sbTable}.${sbMethod}`;
|
|
722
|
+
if (seen.has(key2)) continue;
|
|
723
|
+
seen.add(key2);
|
|
724
|
+
const isMutation = SUPABASE_MUTATION_METHODS_BUILTIN.has(sbMethod) || extraMutationMethods.includes(sbMethod);
|
|
725
|
+
calls.push({ model: sbTable, method: sbMethod, isMutation, kind: "sql" });
|
|
726
|
+
continue;
|
|
727
|
+
}
|
|
712
728
|
const identifier = caps["db.identifier"];
|
|
713
729
|
const model = caps["db.model"];
|
|
714
730
|
const method = caps["db.method"];
|
|
715
731
|
if (!identifier || !model || !method) continue;
|
|
716
732
|
if (!dbIdentifiers.has(identifier)) continue;
|
|
717
|
-
const key =
|
|
733
|
+
const key = `orm:${model}.${method}`;
|
|
718
734
|
if (seen.has(key)) continue;
|
|
719
735
|
seen.add(key);
|
|
720
|
-
calls.push({ model, method, isMutation: getMutationMethods().has(method) });
|
|
736
|
+
calls.push({ model, method, isMutation: getMutationMethods().has(method), kind: "orm" });
|
|
721
737
|
}
|
|
722
738
|
return calls;
|
|
723
739
|
}
|
|
@@ -731,6 +747,9 @@ function classifyFile(absPath) {
|
|
|
731
747
|
const captures = classifyQuery.captures(root);
|
|
732
748
|
const capNames = new Set(captures.map((c) => c.name));
|
|
733
749
|
if (capNames.has("http_export") || capNames.has("http_export_fn")) return "endpoint";
|
|
750
|
+
if (capNames.has("use_server_directive") && fileName !== "page.tsx" && fileName !== "layout.tsx") {
|
|
751
|
+
return "server-action";
|
|
752
|
+
}
|
|
734
753
|
if (fileName === "page.tsx" && capNames.has("has_jsx")) return "page";
|
|
735
754
|
if (fileName === "layout.tsx" && capNames.has("has_jsx")) return "layout";
|
|
736
755
|
if (capNames.has("has_create_context") || capNames.has("has_create_context_bare")) return "context";
|
|
@@ -751,6 +770,36 @@ function extractAuthWrappersTS(absPath) {
|
|
|
751
770
|
wrappers.add(caps["wrapper.fn_name"]);
|
|
752
771
|
}
|
|
753
772
|
}
|
|
773
|
+
const inlineHelpers = /* @__PURE__ */ new Set();
|
|
774
|
+
for (const stmt of childrenOfType(root, "import_statement")) {
|
|
775
|
+
const sourceNode = childOfType(stmt, "string");
|
|
776
|
+
const frag = sourceNode ? childOfType(sourceNode, "string_fragment") : void 0;
|
|
777
|
+
if (!frag) continue;
|
|
778
|
+
const provider = INLINE_AUTH_IMPORTS.find((p) => p.module.test(frag.text));
|
|
779
|
+
if (!provider) continue;
|
|
780
|
+
const clause = childOfType(stmt, "import_clause");
|
|
781
|
+
if (!clause) continue;
|
|
782
|
+
const named = childOfType(clause, "named_imports");
|
|
783
|
+
if (!named) continue;
|
|
784
|
+
for (const specNode of childrenOfType(named, "import_specifier")) {
|
|
785
|
+
const ids = childrenOfType(specNode, "identifier");
|
|
786
|
+
const importedName = ids[0]?.text;
|
|
787
|
+
const localName = ids[ids.length - 1]?.text;
|
|
788
|
+
if (importedName && provider.helpers.includes(importedName) && localName) {
|
|
789
|
+
inlineHelpers.add(localName);
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
if (inlineHelpers.size > 0) {
|
|
794
|
+
const text = root.text;
|
|
795
|
+
for (const name of inlineHelpers) {
|
|
796
|
+
const re = new RegExp(`\\b${name}\\s*\\(`);
|
|
797
|
+
if (re.test(text)) {
|
|
798
|
+
wrappers.add("inline");
|
|
799
|
+
break;
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
}
|
|
754
803
|
return wrappers;
|
|
755
804
|
}
|
|
756
805
|
function trunc(s, max = 120) {
|
|
@@ -909,7 +958,7 @@ function extractDeep(absPath) {
|
|
|
909
958
|
}
|
|
910
959
|
return { elements, stateVars, conditions, variables, responses, params };
|
|
911
960
|
}
|
|
912
|
-
var import_node_fs5, import_node_path5, tsxLanguage, parserInstance, initPromise, initialized, queriesDir, queryCache, PRISMA_MUTATION_METHODS_BUILTIN, DB_IDENTIFIERS_FALLBACK, extraDbIdentifiers, extraMutationMethods;
|
|
961
|
+
var import_node_fs5, import_node_path5, tsxLanguage, parserInstance, initPromise, initialized, queriesDir, queryCache, PRISMA_MUTATION_METHODS_BUILTIN, SUPABASE_MUTATION_METHODS_BUILTIN, DB_IDENTIFIERS_FALLBACK, extraDbIdentifiers, extraMutationMethods, INLINE_AUTH_IMPORTS;
|
|
913
962
|
var init_ts_extractor = __esm({
|
|
914
963
|
"src/server/graph/core/ts-extractor.ts"() {
|
|
915
964
|
"use strict";
|
|
@@ -933,9 +982,21 @@ var init_ts_extractor = __esm({
|
|
|
933
982
|
"delete",
|
|
934
983
|
"deleteMany"
|
|
935
984
|
];
|
|
985
|
+
SUPABASE_MUTATION_METHODS_BUILTIN = /* @__PURE__ */ new Set([
|
|
986
|
+
"insert",
|
|
987
|
+
"update",
|
|
988
|
+
"delete",
|
|
989
|
+
"upsert"
|
|
990
|
+
]);
|
|
936
991
|
DB_IDENTIFIERS_FALLBACK = ["db", "prisma"];
|
|
937
992
|
extraDbIdentifiers = [];
|
|
938
993
|
extraMutationMethods = [];
|
|
994
|
+
INLINE_AUTH_IMPORTS = [
|
|
995
|
+
{ module: /^@clerk\/nextjs(\/server)?$/, helpers: ["auth", "currentUser"] },
|
|
996
|
+
{ module: /^next-auth(\/.+)?$/, helpers: ["auth", "getServerSession"] },
|
|
997
|
+
{ module: /^@auth\//, helpers: ["auth"] },
|
|
998
|
+
{ module: /^@supabase\/auth-helpers/, helpers: ["createServerClient"] }
|
|
999
|
+
];
|
|
939
1000
|
}
|
|
940
1001
|
});
|
|
941
1002
|
|
|
@@ -1027,6 +1088,8 @@ function extractRoute(id) {
|
|
|
1027
1088
|
if (!id.endsWith("/page.tsx")) return null;
|
|
1028
1089
|
let route = id.replace(/^app\//, "/").replace(/\/page\.tsx$/, "");
|
|
1029
1090
|
route = route.replace(/\/\([^)]+\)/g, "");
|
|
1091
|
+
route = route.replace(/\[\[\.\.\.([^\]]+)\]\]/g, "*$1?");
|
|
1092
|
+
route = route.replace(/\[\.\.\.([^\]]+)\]/g, "*$1");
|
|
1030
1093
|
route = route.replace(/\[([^\]]+)\]/g, ":$1");
|
|
1031
1094
|
route = route.replace(/\/+/g, "/");
|
|
1032
1095
|
if (!route.startsWith("/")) route = "/" + route;
|
|
@@ -1038,6 +1101,7 @@ function nameFromFilename(absPath) {
|
|
|
1038
1101
|
function filePathToAppRoute(appDir, absPath) {
|
|
1039
1102
|
let route = ("/" + (0, import_node_path6.relative)(appDir, absPath).replace(/\\/g, "/")).replace(/\/route\.tsx?$/, "");
|
|
1040
1103
|
route = route.replace(/\/\([^)]+\)/g, "");
|
|
1104
|
+
route = route.replace(/\[\[\.\.\.([^\]]+)\]\]/g, "*$1?");
|
|
1041
1105
|
route = route.replace(/\[\.\.\.([^\]]+)\]/g, "*$1");
|
|
1042
1106
|
route = route.replace(/\[([^\]]+)\]/g, ":$1");
|
|
1043
1107
|
route = route.replace(/\/+/g, "/");
|
|
@@ -1086,25 +1150,52 @@ function resolveTemplateLiteralRoute(template, routeToNodeId) {
|
|
|
1086
1150
|
function routeMatchScore(candidate, known) {
|
|
1087
1151
|
const segsA = candidate.split("/");
|
|
1088
1152
|
const segsB = known.split("/");
|
|
1089
|
-
if (segsA.length !== segsB.length) return -1;
|
|
1090
1153
|
let score = 0;
|
|
1091
|
-
|
|
1092
|
-
|
|
1154
|
+
let i = 0, j = 0;
|
|
1155
|
+
while (i < segsA.length && j < segsB.length) {
|
|
1156
|
+
const a = segsA[i], b = segsB[j];
|
|
1157
|
+
if (b.startsWith("*") && b.endsWith("?")) {
|
|
1158
|
+
score += 1;
|
|
1159
|
+
return score;
|
|
1160
|
+
}
|
|
1161
|
+
if (b.startsWith("*")) {
|
|
1162
|
+
const remaining = segsA.length - i;
|
|
1163
|
+
if (remaining < 1) return -1;
|
|
1164
|
+
score += 1 + remaining;
|
|
1165
|
+
return score;
|
|
1166
|
+
}
|
|
1093
1167
|
if (a === b) {
|
|
1094
1168
|
score += 3;
|
|
1169
|
+
i++;
|
|
1170
|
+
j++;
|
|
1095
1171
|
continue;
|
|
1096
1172
|
}
|
|
1097
1173
|
if (a.startsWith(":") && b.startsWith(":")) {
|
|
1098
1174
|
score += 2;
|
|
1175
|
+
i++;
|
|
1176
|
+
j++;
|
|
1099
1177
|
continue;
|
|
1100
1178
|
}
|
|
1101
1179
|
if (a.startsWith(":") || b.startsWith(":")) {
|
|
1102
|
-
|
|
1180
|
+
i++;
|
|
1181
|
+
j++;
|
|
1103
1182
|
continue;
|
|
1104
1183
|
}
|
|
1105
1184
|
return -1;
|
|
1106
1185
|
}
|
|
1107
|
-
|
|
1186
|
+
if (i === segsA.length) {
|
|
1187
|
+
while (j < segsB.length) {
|
|
1188
|
+
const b = segsB[j];
|
|
1189
|
+
if (b.startsWith("*") && b.endsWith("?")) {
|
|
1190
|
+
score += 1;
|
|
1191
|
+
j++;
|
|
1192
|
+
continue;
|
|
1193
|
+
}
|
|
1194
|
+
return -1;
|
|
1195
|
+
}
|
|
1196
|
+
return score;
|
|
1197
|
+
}
|
|
1198
|
+
return -1;
|
|
1108
1199
|
}
|
|
1109
1200
|
function templateToRoute(template) {
|
|
1110
1201
|
return template.replace(/\$\{([^}]+)\}/g, (_, expr) => {
|
|
@@ -1139,7 +1230,7 @@ function extractEdges(srcDir, rootDir, absPath, sourceId, parsed, nodeIdSet, bar
|
|
|
1139
1230
|
edges.push(edge);
|
|
1140
1231
|
}
|
|
1141
1232
|
function edgeTypeFor(isTypeOnlyImport, importedNames) {
|
|
1142
|
-
if (isTypeOnlyImport) return "
|
|
1233
|
+
if (isTypeOnlyImport) return "imports_type";
|
|
1143
1234
|
const anyRendered = importedNames.some((n) => parsed.jsxElements.has(n));
|
|
1144
1235
|
if (anyRendered) return "renders";
|
|
1145
1236
|
return "imports";
|
|
@@ -1170,7 +1261,8 @@ function extractEdges(srcDir, rootDir, absPath, sourceId, parsed, nodeIdSet, bar
|
|
|
1170
1261
|
if (resolved) {
|
|
1171
1262
|
const targetId = toNodeId(srcDir, rootDir, resolved);
|
|
1172
1263
|
if (nodeIdSet.has(targetId) && !targetId.endsWith("/index.ts") && !targetId.endsWith("/index.tsx")) {
|
|
1173
|
-
|
|
1264
|
+
const allType = isTypeOnly || names.length > 0 && names.every((n) => typeNames.has(n));
|
|
1265
|
+
addEdge(targetId, edgeTypeFor(allType, names));
|
|
1174
1266
|
}
|
|
1175
1267
|
}
|
|
1176
1268
|
}
|
|
@@ -1179,7 +1271,8 @@ function extractEdges(srcDir, rootDir, absPath, sourceId, parsed, nodeIdSet, bar
|
|
|
1179
1271
|
if (resolved) {
|
|
1180
1272
|
const targetId = toNodeId(srcDir, rootDir, resolved);
|
|
1181
1273
|
if (nodeIdSet.has(targetId) && !targetId.endsWith("/index.ts") && !targetId.endsWith("/index.tsx")) {
|
|
1182
|
-
|
|
1274
|
+
const allType = isTypeOnly || names.length > 0 && names.every((n) => typeNames.has(n));
|
|
1275
|
+
addEdge(targetId, edgeTypeFor(allType, names));
|
|
1183
1276
|
}
|
|
1184
1277
|
}
|
|
1185
1278
|
}
|
|
@@ -1264,26 +1357,34 @@ function generate(rootDir) {
|
|
|
1264
1357
|
const layer = CLASSIFICATION_TO_LAYER[type] ?? "ui";
|
|
1265
1358
|
nodeIdSet.add(id);
|
|
1266
1359
|
if (layer === "api") {
|
|
1267
|
-
const methods = [];
|
|
1268
|
-
for (const exp of parsed.exports) {
|
|
1269
|
-
if (HTTP_METHODS.has(exp)) methods.push(exp);
|
|
1270
|
-
}
|
|
1271
1360
|
const dbCalls = extractDbCallsTS(absPath);
|
|
1272
1361
|
const authWrappers = extractAuthWrappersTS(absPath);
|
|
1273
1362
|
const deep = extractDeep(absPath);
|
|
1274
|
-
const routePath = filePathToAppRoute(paths.appDir, absPath);
|
|
1275
1363
|
const mutations = dbCalls.filter((c) => c.isMutation);
|
|
1276
1364
|
const mutates = mutations.length > 0;
|
|
1277
1365
|
const authStrategy = [...authWrappers];
|
|
1366
|
+
const isServerAction = type === "server-action";
|
|
1367
|
+
const methods = [];
|
|
1368
|
+
if (!isServerAction) {
|
|
1369
|
+
for (const exp of parsed.exports) {
|
|
1370
|
+
if (HTTP_METHODS.has(exp)) methods.push(exp);
|
|
1371
|
+
}
|
|
1372
|
+
}
|
|
1373
|
+
const routePath = isServerAction ? null : filePathToAppRoute(paths.appDir, absPath);
|
|
1374
|
+
const actions = isServerAction ? parsed.exports.filter((e) => !HTTP_METHODS.has(e)) : [];
|
|
1278
1375
|
apiNodes.push({
|
|
1279
1376
|
id,
|
|
1280
|
-
type: "endpoint",
|
|
1281
|
-
name:
|
|
1377
|
+
type: isServerAction ? "server-action" : "endpoint",
|
|
1378
|
+
// For HTTP routes: name = URL path. For Server Actions: name = file id +
|
|
1379
|
+
// exported action names — the callable surface, not a URL.
|
|
1380
|
+
name: isServerAction ? actions.length > 0 ? `${id} (${actions.join(", ")})` : id : routePath,
|
|
1282
1381
|
layer: "api",
|
|
1283
1382
|
path: routePath,
|
|
1383
|
+
// null for Server Actions
|
|
1284
1384
|
methods,
|
|
1285
1385
|
handler: id,
|
|
1286
1386
|
mutates,
|
|
1387
|
+
...isServerAction ? { actions } : {},
|
|
1287
1388
|
auth: authStrategy.length > 0 ? authStrategy : ["public"],
|
|
1288
1389
|
db_models: [...new Set(dbCalls.map((c) => c.model))],
|
|
1289
1390
|
db_operations: [...new Set(dbCalls.map((c) => `${c.model}.${c.method}`))],
|
|
@@ -1297,6 +1398,8 @@ function generate(rootDir) {
|
|
|
1297
1398
|
} else {
|
|
1298
1399
|
const route = extractRoute(id);
|
|
1299
1400
|
const deep = extractDeep(absPath);
|
|
1401
|
+
const dbCalls = extractDbCallsTS(absPath);
|
|
1402
|
+
const authWrappers = type === "page" || type === "layout" ? [...extractAuthWrappersTS(absPath)] : [];
|
|
1300
1403
|
uiNodes.push({
|
|
1301
1404
|
id,
|
|
1302
1405
|
type,
|
|
@@ -1307,7 +1410,9 @@ function generate(rootDir) {
|
|
|
1307
1410
|
elements: deep.elements,
|
|
1308
1411
|
stateVars: deep.stateVars,
|
|
1309
1412
|
conditions: deep.conditions,
|
|
1310
|
-
variables: deep.variables
|
|
1413
|
+
variables: deep.variables,
|
|
1414
|
+
...authWrappers.length > 0 ? { auth: authWrappers } : {},
|
|
1415
|
+
...dbCalls.length > 0 ? { _dbCalls: dbCalls } : {}
|
|
1311
1416
|
});
|
|
1312
1417
|
if (route) routeToNodeId.set(route, id);
|
|
1313
1418
|
}
|
|
@@ -1331,6 +1436,29 @@ function generate(rootDir) {
|
|
|
1331
1436
|
uiEdges.push(...edges);
|
|
1332
1437
|
uiFlagged.push(...flagged);
|
|
1333
1438
|
}
|
|
1439
|
+
const layoutsById = /* @__PURE__ */ new Set();
|
|
1440
|
+
for (const n of uiNodes) {
|
|
1441
|
+
if (n.type === "layout") layoutsById.add(n.id);
|
|
1442
|
+
}
|
|
1443
|
+
function findClosestLayout(pageId) {
|
|
1444
|
+
let dir = pageId.replace(/\/page\.tsx$/, "");
|
|
1445
|
+
while (dir.length > 0) {
|
|
1446
|
+
const candidate = `${dir}/layout.tsx`;
|
|
1447
|
+
if (layoutsById.has(candidate)) return candidate;
|
|
1448
|
+
const slash = dir.lastIndexOf("/");
|
|
1449
|
+
if (slash < 0) break;
|
|
1450
|
+
dir = dir.slice(0, slash);
|
|
1451
|
+
}
|
|
1452
|
+
if (layoutsById.has("app/layout.tsx")) return "app/layout.tsx";
|
|
1453
|
+
return null;
|
|
1454
|
+
}
|
|
1455
|
+
for (const n of uiNodes) {
|
|
1456
|
+
if (n.type !== "page") continue;
|
|
1457
|
+
const layoutId = findClosestLayout(n.id);
|
|
1458
|
+
if (layoutId && layoutId !== n.id) {
|
|
1459
|
+
uiEdges.push({ source: layoutId, target: n.id, type: "wraps" });
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1334
1462
|
const fetchCallEntries = [];
|
|
1335
1463
|
for (const absPath of fileSet) {
|
|
1336
1464
|
const sourceId = toNodeId(srcDir, rootDir, absPath);
|
|
@@ -1412,17 +1540,37 @@ function generate(rootDir) {
|
|
|
1412
1540
|
if (!dbCalls) continue;
|
|
1413
1541
|
const seenModels = /* @__PURE__ */ new Set();
|
|
1414
1542
|
for (const call of dbCalls) {
|
|
1415
|
-
|
|
1416
|
-
seenModels.
|
|
1543
|
+
const target = call.kind === "sql" ? call.model : camelToPascal(call.model);
|
|
1544
|
+
if (seenModels.has(target)) continue;
|
|
1545
|
+
seenModels.add(target);
|
|
1417
1546
|
apiCrossRefs.push({
|
|
1418
1547
|
source: node.id,
|
|
1419
|
-
target
|
|
1548
|
+
target,
|
|
1549
|
+
type: call.isMutation ? "mutates" : "reads",
|
|
1550
|
+
layer: "db"
|
|
1551
|
+
});
|
|
1552
|
+
}
|
|
1553
|
+
delete node._dbCalls;
|
|
1554
|
+
}
|
|
1555
|
+
const uiCrossRefs = [];
|
|
1556
|
+
for (const node of uiNodes) {
|
|
1557
|
+
const dbCalls = node._dbCalls;
|
|
1558
|
+
if (!dbCalls) continue;
|
|
1559
|
+
const seenModels = /* @__PURE__ */ new Set();
|
|
1560
|
+
for (const call of dbCalls) {
|
|
1561
|
+
const target = call.kind === "sql" ? call.model : camelToPascal(call.model);
|
|
1562
|
+
if (seenModels.has(target)) continue;
|
|
1563
|
+
seenModels.add(target);
|
|
1564
|
+
uiCrossRefs.push({
|
|
1565
|
+
source: node.id,
|
|
1566
|
+
target,
|
|
1420
1567
|
type: call.isMutation ? "mutates" : "reads",
|
|
1421
1568
|
layer: "db"
|
|
1422
1569
|
});
|
|
1423
1570
|
}
|
|
1424
1571
|
delete node._dbCalls;
|
|
1425
1572
|
}
|
|
1573
|
+
uiCrossRefs.sort((a, b) => a.source.localeCompare(b.source) || a.target.localeCompare(b.target));
|
|
1426
1574
|
const apiNodeIds = new Set(apiNodes.map((n) => n.id));
|
|
1427
1575
|
const apiEdges = [];
|
|
1428
1576
|
const uiOnlyEdges = [];
|
|
@@ -1484,7 +1632,7 @@ function generate(rootDir) {
|
|
|
1484
1632
|
},
|
|
1485
1633
|
nodes: stripLayer(uiNodes),
|
|
1486
1634
|
edges: uiOnlyEdges,
|
|
1487
|
-
cross_refs:
|
|
1635
|
+
cross_refs: uiCrossRefs,
|
|
1488
1636
|
contradictions: [],
|
|
1489
1637
|
warnings: [],
|
|
1490
1638
|
flagged_edges: dedupedFlagged,
|
|
@@ -1568,6 +1716,7 @@ var init_typescript_project = __esm({
|
|
|
1568
1716
|
HTTP_METHODS = /* @__PURE__ */ new Set(["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"]);
|
|
1569
1717
|
CLASSIFICATION_TO_LAYER = {
|
|
1570
1718
|
endpoint: "api",
|
|
1719
|
+
"server-action": "api",
|
|
1571
1720
|
page: "ui",
|
|
1572
1721
|
layout: "ui",
|
|
1573
1722
|
component: "ui",
|
|
@@ -2370,26 +2519,54 @@ function normalizeFetchUrl(raw) {
|
|
|
2370
2519
|
return { path: s || "/", hadInterpolation };
|
|
2371
2520
|
}
|
|
2372
2521
|
function scoreApiRouteMatch(candidate, known) {
|
|
2373
|
-
if (candidate.length !== known.length) return -1;
|
|
2374
2522
|
let score = 0;
|
|
2375
|
-
|
|
2523
|
+
let i = 0, j = 0;
|
|
2524
|
+
while (i < candidate.length && j < known.length) {
|
|
2376
2525
|
const a = candidate[i];
|
|
2377
|
-
const b = known[
|
|
2526
|
+
const b = known[j];
|
|
2527
|
+
if (b.startsWith("*") && b.endsWith("?")) {
|
|
2528
|
+
score += 1;
|
|
2529
|
+
return score;
|
|
2530
|
+
}
|
|
2531
|
+
if (b.startsWith("*")) {
|
|
2532
|
+
const remaining = candidate.length - i;
|
|
2533
|
+
if (remaining < 1) return -1;
|
|
2534
|
+
score += 1 + remaining;
|
|
2535
|
+
return score;
|
|
2536
|
+
}
|
|
2378
2537
|
if (a === b) {
|
|
2379
2538
|
score += 3;
|
|
2539
|
+
i++;
|
|
2540
|
+
j++;
|
|
2380
2541
|
continue;
|
|
2381
2542
|
}
|
|
2382
2543
|
if (a.startsWith(":") && b.startsWith(":")) {
|
|
2383
2544
|
score += 2;
|
|
2545
|
+
i++;
|
|
2546
|
+
j++;
|
|
2384
2547
|
continue;
|
|
2385
2548
|
}
|
|
2386
2549
|
if (a.startsWith(":") || b.startsWith(":")) {
|
|
2387
2550
|
score += 1;
|
|
2551
|
+
i++;
|
|
2552
|
+
j++;
|
|
2388
2553
|
continue;
|
|
2389
2554
|
}
|
|
2390
2555
|
return -1;
|
|
2391
2556
|
}
|
|
2392
|
-
|
|
2557
|
+
if (i === candidate.length) {
|
|
2558
|
+
while (j < known.length) {
|
|
2559
|
+
const b = known[j];
|
|
2560
|
+
if (b.startsWith("*") && b.endsWith("?")) {
|
|
2561
|
+
score += 1;
|
|
2562
|
+
j++;
|
|
2563
|
+
continue;
|
|
2564
|
+
}
|
|
2565
|
+
return -1;
|
|
2566
|
+
}
|
|
2567
|
+
return score;
|
|
2568
|
+
}
|
|
2569
|
+
return -1;
|
|
2393
2570
|
}
|
|
2394
2571
|
function resolveFetchCall(call, apiPathMap, apiRoutes) {
|
|
2395
2572
|
const raw = call.url;
|
|
@@ -2556,22 +2733,12 @@ var init_fetch_resolver = __esm({
|
|
|
2556
2733
|
});
|
|
2557
2734
|
|
|
2558
2735
|
// src/server/graph/parsers/crosslayer/api-annotations.ts
|
|
2559
|
-
function
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
|
|
2564
|
-
const full = (0, import_node_path8.join)(dir, entry.name);
|
|
2565
|
-
if (entry.isDirectory()) {
|
|
2566
|
-
results.push(...walk2(full, exts));
|
|
2567
|
-
} else if (exts.includes((0, import_node_path8.extname)(entry.name))) {
|
|
2568
|
-
results.push(full);
|
|
2569
|
-
}
|
|
2736
|
+
function toNodeId2(srcDir, rootDir, absPath) {
|
|
2737
|
+
const relFromSrc = (0, import_node_path8.relative)(srcDir, absPath).replace(/\\/g, "/");
|
|
2738
|
+
if (relFromSrc.startsWith("..")) {
|
|
2739
|
+
return (0, import_node_path8.relative)(rootDir, absPath).replace(/\\/g, "/");
|
|
2570
2740
|
}
|
|
2571
|
-
return
|
|
2572
|
-
}
|
|
2573
|
-
function toNodeId2(srcDir, absPath) {
|
|
2574
|
-
return (0, import_node_path8.relative)(srcDir, absPath).replace(/\\/g, "/");
|
|
2741
|
+
return relFromSrc;
|
|
2575
2742
|
}
|
|
2576
2743
|
var import_node_fs9, import_node_path8, API_ANNOTATION_RE, apiAnnotationsParser;
|
|
2577
2744
|
var init_api_annotations = __esm({
|
|
@@ -2580,31 +2747,53 @@ var init_api_annotations = __esm({
|
|
|
2580
2747
|
import_node_fs9 = require("node:fs");
|
|
2581
2748
|
import_node_path8 = require("node:path");
|
|
2582
2749
|
init_api_route_matching();
|
|
2750
|
+
init_config();
|
|
2751
|
+
init_resolve_paths();
|
|
2752
|
+
init_walk();
|
|
2583
2753
|
API_ANNOTATION_RE = /@api\s+(GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS)\s+(\/\S+)/g;
|
|
2584
2754
|
apiAnnotationsParser = {
|
|
2585
2755
|
id: "api-annotations",
|
|
2586
2756
|
layer: "crosslayer",
|
|
2587
2757
|
concern: "api-binding",
|
|
2588
2758
|
detect(rootDir) {
|
|
2589
|
-
return (
|
|
2759
|
+
return resolveProjectPaths(rootDir, loadConfig(rootDir)) !== null;
|
|
2590
2760
|
},
|
|
2591
2761
|
generate(rootDir, layerOutputs) {
|
|
2592
2762
|
const apiOutput = layerOutputs.get("api");
|
|
2593
2763
|
if (!apiOutput) {
|
|
2594
2764
|
return { cross_refs: [], flagged_edges: [], warnings: [] };
|
|
2595
2765
|
}
|
|
2766
|
+
const paths = resolveProjectPaths(rootDir, loadConfig(rootDir));
|
|
2767
|
+
if (!paths) {
|
|
2768
|
+
return { cross_refs: [], flagged_edges: [], warnings: [] };
|
|
2769
|
+
}
|
|
2596
2770
|
const uiOutput = layerOutputs.get("ui");
|
|
2597
2771
|
const uiNodeIds = new Set(uiOutput?.nodes.map((n) => n.id) ?? []);
|
|
2598
2772
|
const apiRoutes = loadApiRoutesFromOutput(apiOutput);
|
|
2599
2773
|
const apiPathMap = buildApiPathMap(apiRoutes);
|
|
2600
|
-
const srcDir =
|
|
2601
|
-
const
|
|
2774
|
+
const srcDir = paths.srcDir;
|
|
2775
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2776
|
+
const files = [];
|
|
2777
|
+
for (const root of paths.srcRoots) {
|
|
2778
|
+
for (const f of walk(root, [".ts", ".tsx"])) {
|
|
2779
|
+
if (!seen.has(f)) {
|
|
2780
|
+
seen.add(f);
|
|
2781
|
+
files.push(f);
|
|
2782
|
+
}
|
|
2783
|
+
}
|
|
2784
|
+
}
|
|
2785
|
+
for (const conv of paths.conventionFiles) {
|
|
2786
|
+
if (!seen.has(conv)) {
|
|
2787
|
+
seen.add(conv);
|
|
2788
|
+
files.push(conv);
|
|
2789
|
+
}
|
|
2790
|
+
}
|
|
2602
2791
|
const crossRefs = [];
|
|
2603
2792
|
const flaggedEdges = [];
|
|
2604
|
-
const
|
|
2793
|
+
const seenEdge = /* @__PURE__ */ new Set();
|
|
2605
2794
|
for (const absPath of files) {
|
|
2606
2795
|
const content = (0, import_node_fs9.readFileSync)(absPath, "utf-8");
|
|
2607
|
-
const sourceId = toNodeId2(srcDir, absPath);
|
|
2796
|
+
const sourceId = toNodeId2(srcDir, rootDir, absPath);
|
|
2608
2797
|
if (!uiNodeIds.has(sourceId)) continue;
|
|
2609
2798
|
let match;
|
|
2610
2799
|
API_ANNOTATION_RE.lastIndex = 0;
|
|
@@ -2614,8 +2803,8 @@ var init_api_annotations = __esm({
|
|
|
2614
2803
|
const result = resolveUrlPath(urlPath, apiPathMap, apiRoutes);
|
|
2615
2804
|
if (result.kind === "resolved" && result.nodeId) {
|
|
2616
2805
|
const key = `${sourceId}|${result.nodeId}|calls_api`;
|
|
2617
|
-
if (
|
|
2618
|
-
|
|
2806
|
+
if (seenEdge.has(key)) continue;
|
|
2807
|
+
seenEdge.add(key);
|
|
2619
2808
|
crossRefs.push({
|
|
2620
2809
|
source: sourceId,
|
|
2621
2810
|
target: result.nodeId,
|
|
@@ -2649,22 +2838,12 @@ var init_api_annotations = __esm({
|
|
|
2649
2838
|
});
|
|
2650
2839
|
|
|
2651
2840
|
// src/server/graph/parsers/crosslayer/url-literal-scanner.ts
|
|
2652
|
-
function
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
|
|
2657
|
-
const full = (0, import_node_path9.join)(dir, entry.name);
|
|
2658
|
-
if (entry.isDirectory()) {
|
|
2659
|
-
results.push(...walk3(full, exts));
|
|
2660
|
-
} else if (exts.includes((0, import_node_path9.extname)(entry.name))) {
|
|
2661
|
-
results.push(full);
|
|
2662
|
-
}
|
|
2841
|
+
function toNodeId3(srcDir, rootDir, absPath) {
|
|
2842
|
+
const relFromSrc = (0, import_node_path9.relative)(srcDir, absPath).replace(/\\/g, "/");
|
|
2843
|
+
if (relFromSrc.startsWith("..")) {
|
|
2844
|
+
return (0, import_node_path9.relative)(rootDir, absPath).replace(/\\/g, "/");
|
|
2663
2845
|
}
|
|
2664
|
-
return
|
|
2665
|
-
}
|
|
2666
|
-
function toNodeId3(srcDir, absPath) {
|
|
2667
|
-
return (0, import_node_path9.relative)(srcDir, absPath).replace(/\\/g, "/");
|
|
2846
|
+
return relFromSrc;
|
|
2668
2847
|
}
|
|
2669
2848
|
var import_node_fs10, import_node_path9, URL_LITERAL_RE, urlLiteralScannerParser;
|
|
2670
2849
|
var init_url_literal_scanner = __esm({
|
|
@@ -2675,7 +2854,8 @@ var init_url_literal_scanner = __esm({
|
|
|
2675
2854
|
init_api_route_matching();
|
|
2676
2855
|
init_config();
|
|
2677
2856
|
init_resolve_paths();
|
|
2678
|
-
|
|
2857
|
+
init_walk();
|
|
2858
|
+
URL_LITERAL_RE = /['"`](\/[a-zA-Z][^'"`\s]*?)['"`]/g;
|
|
2679
2859
|
urlLiteralScannerParser = {
|
|
2680
2860
|
id: "url-literal-scanner",
|
|
2681
2861
|
layer: "crosslayer",
|
|
@@ -2695,15 +2875,26 @@ var init_url_literal_scanner = __esm({
|
|
|
2695
2875
|
const apiPathMap = buildApiPathMap(apiRoutes);
|
|
2696
2876
|
const paths = resolveProjectPaths(rootDir, loadConfig(rootDir));
|
|
2697
2877
|
const srcDir = paths.srcDir;
|
|
2698
|
-
const
|
|
2699
|
-
const files = [
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2878
|
+
const seenFile = /* @__PURE__ */ new Set();
|
|
2879
|
+
const files = [];
|
|
2880
|
+
for (const root of paths.srcRoots) {
|
|
2881
|
+
for (const f of walk(root, [".ts", ".tsx"])) {
|
|
2882
|
+
if (!seenFile.has(f)) {
|
|
2883
|
+
seenFile.add(f);
|
|
2884
|
+
files.push(f);
|
|
2885
|
+
}
|
|
2886
|
+
}
|
|
2887
|
+
}
|
|
2888
|
+
for (const conv of paths.conventionFiles) {
|
|
2889
|
+
if (!seenFile.has(conv)) {
|
|
2890
|
+
seenFile.add(conv);
|
|
2891
|
+
files.push(conv);
|
|
2892
|
+
}
|
|
2893
|
+
}
|
|
2703
2894
|
const crossRefs = [];
|
|
2704
2895
|
const seen = /* @__PURE__ */ new Set();
|
|
2705
2896
|
for (const absPath of files) {
|
|
2706
|
-
const sourceId = toNodeId3(srcDir, absPath);
|
|
2897
|
+
const sourceId = toNodeId3(srcDir, rootDir, absPath);
|
|
2707
2898
|
if (!uiNodeIds.has(sourceId)) continue;
|
|
2708
2899
|
const content = (0, import_node_fs10.readFileSync)(absPath, "utf-8");
|
|
2709
2900
|
let match;
|
|
@@ -2766,10 +2957,15 @@ function classifyScope(source, model) {
|
|
|
2766
2957
|
function extractEnumValues(rootDir) {
|
|
2767
2958
|
const nodes = [];
|
|
2768
2959
|
const edges = [];
|
|
2769
|
-
const
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2960
|
+
const paths = resolveProjectPaths(rootDir, loadConfig(rootDir));
|
|
2961
|
+
const schemaPaths = [];
|
|
2962
|
+
if (paths?.dbConfig.kind === "prisma" && paths.dbConfig.schemaPath) {
|
|
2963
|
+
schemaPaths.push(paths.dbConfig.schemaPath);
|
|
2964
|
+
schemaPaths.push((0, import_node_path10.join)((0, import_node_path10.dirname)(paths.dbConfig.schemaPath), "schema"));
|
|
2965
|
+
} else {
|
|
2966
|
+
schemaPaths.push((0, import_node_path10.join)(rootDir, "prisma", "schema.prisma"));
|
|
2967
|
+
schemaPaths.push((0, import_node_path10.join)(rootDir, "prisma", "schema"));
|
|
2968
|
+
}
|
|
2773
2969
|
let content = "";
|
|
2774
2970
|
for (const p of schemaPaths) {
|
|
2775
2971
|
if ((0, import_node_fs11.existsSync)(p)) {
|
|
@@ -2853,7 +3049,7 @@ function extractStringArrayFromNode(node) {
|
|
|
2853
3049
|
return values;
|
|
2854
3050
|
}
|
|
2855
3051
|
function findArrayDecl(root, varName) {
|
|
2856
|
-
function
|
|
3052
|
+
function walk2(node) {
|
|
2857
3053
|
if (node.type === "variable_declarator") {
|
|
2858
3054
|
const nameNode = node.childForFieldName("name");
|
|
2859
3055
|
const valueNode = node.childForFieldName("value");
|
|
@@ -2866,12 +3062,12 @@ function findArrayDecl(root, varName) {
|
|
|
2866
3062
|
}
|
|
2867
3063
|
}
|
|
2868
3064
|
for (const child of node.namedChildren) {
|
|
2869
|
-
const found =
|
|
3065
|
+
const found = walk2(child);
|
|
2870
3066
|
if (found) return found;
|
|
2871
3067
|
}
|
|
2872
3068
|
return null;
|
|
2873
3069
|
}
|
|
2874
|
-
return
|
|
3070
|
+
return walk2(root);
|
|
2875
3071
|
}
|
|
2876
3072
|
function extractObjectPropsRegex(objStr) {
|
|
2877
3073
|
const props = {};
|
|
@@ -2934,11 +3130,26 @@ function modelToNodeType(model) {
|
|
|
2934
3130
|
function extractSeedData(rootDir) {
|
|
2935
3131
|
const nodes = [];
|
|
2936
3132
|
const edges = [];
|
|
2937
|
-
const
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
(0, import_node_path10.join)(
|
|
2941
|
-
|
|
3133
|
+
const paths = resolveProjectPaths(rootDir, loadConfig(rootDir));
|
|
3134
|
+
const candidates = [];
|
|
3135
|
+
if (paths?.dbDir) {
|
|
3136
|
+
candidates.push((0, import_node_path10.join)(paths.dbDir, "seed.ts"));
|
|
3137
|
+
candidates.push((0, import_node_path10.join)(paths.dbDir, "seed.js"));
|
|
3138
|
+
} else {
|
|
3139
|
+
candidates.push((0, import_node_path10.join)(rootDir, "prisma", "seed.ts"));
|
|
3140
|
+
candidates.push((0, import_node_path10.join)(rootDir, "prisma", "seed.js"));
|
|
3141
|
+
}
|
|
3142
|
+
const baseRoots = paths?.srcRoots ?? [(0, import_node_path10.join)(rootDir, "src")];
|
|
3143
|
+
for (const root of baseRoots) {
|
|
3144
|
+
candidates.push((0, import_node_path10.join)(root, "server", "lib", "system-tags.ts"));
|
|
3145
|
+
}
|
|
3146
|
+
const seedFiles = candidates.filter((p) => {
|
|
3147
|
+
try {
|
|
3148
|
+
return (0, import_node_fs11.existsSync)(p) && (0, import_node_fs11.statSync)(p).isFile();
|
|
3149
|
+
} catch {
|
|
3150
|
+
return false;
|
|
3151
|
+
}
|
|
3152
|
+
});
|
|
2942
3153
|
const useTreeSitter = tryLoadTreeSitter();
|
|
2943
3154
|
for (const filePath of seedFiles) {
|
|
2944
3155
|
const content = (0, import_node_fs11.readFileSync)(filePath, "utf-8");
|
|
@@ -3048,9 +3259,20 @@ function walkDir(dir, exts) {
|
|
|
3048
3259
|
}
|
|
3049
3260
|
function extractConstants(rootDir) {
|
|
3050
3261
|
const nodes = [];
|
|
3051
|
-
const
|
|
3052
|
-
|
|
3053
|
-
|
|
3262
|
+
const paths = resolveProjectPaths(rootDir, loadConfig(rootDir));
|
|
3263
|
+
const roots = paths?.srcRoots ?? [(0, import_node_path10.join)(rootDir, "src")];
|
|
3264
|
+
const seenFile = /* @__PURE__ */ new Set();
|
|
3265
|
+
const allFiles = [];
|
|
3266
|
+
for (const root of roots) {
|
|
3267
|
+
for (const f of walkDir(root, [".ts", ".tsx"])) {
|
|
3268
|
+
if (!seenFile.has(f)) {
|
|
3269
|
+
seenFile.add(f);
|
|
3270
|
+
allFiles.push(f);
|
|
3271
|
+
}
|
|
3272
|
+
}
|
|
3273
|
+
}
|
|
3274
|
+
if (allFiles.length === 0) return { nodes };
|
|
3275
|
+
for (const filePath of allFiles) {
|
|
3054
3276
|
const content = (0, import_node_fs11.readFileSync)(filePath, "utf-8");
|
|
3055
3277
|
const relPath = (0, import_node_path10.relative)(rootDir, filePath);
|
|
3056
3278
|
const constArrayRe = /export\s+const\s+([A-Z][A-Z_0-9]+)\s*(?::[^=]+)?\s*=\s*\[/g;
|
|
@@ -3085,6 +3307,13 @@ function extractConstants(rootDir) {
|
|
|
3085
3307
|
return { nodes };
|
|
3086
3308
|
}
|
|
3087
3309
|
function detect4(rootDir) {
|
|
3310
|
+
const paths = resolveProjectPaths(rootDir, loadConfig(rootDir));
|
|
3311
|
+
if (paths?.dbConfig.kind === "prisma" && paths.dbConfig.schemaPath && (0, import_node_fs11.existsSync)(paths.dbConfig.schemaPath)) {
|
|
3312
|
+
return true;
|
|
3313
|
+
}
|
|
3314
|
+
if (paths?.dbDir) {
|
|
3315
|
+
if ((0, import_node_fs11.existsSync)((0, import_node_path10.join)(paths.dbDir, "seed.ts")) || (0, import_node_fs11.existsSync)((0, import_node_path10.join)(paths.dbDir, "seed.js"))) return true;
|
|
3316
|
+
}
|
|
3088
3317
|
return (0, import_node_fs11.existsSync)((0, import_node_path10.join)(rootDir, "prisma", "schema.prisma")) || (0, import_node_fs11.existsSync)((0, import_node_path10.join)(rootDir, "prisma", "seed.ts"));
|
|
3089
3318
|
}
|
|
3090
3319
|
function generate4(rootDir) {
|
|
@@ -3158,6 +3387,8 @@ var init_static_values = __esm({
|
|
|
3158
3387
|
"use strict";
|
|
3159
3388
|
import_node_fs11 = require("node:fs");
|
|
3160
3389
|
import_node_path10 = require("node:path");
|
|
3390
|
+
init_config();
|
|
3391
|
+
init_resolve_paths();
|
|
3161
3392
|
parseCode = null;
|
|
3162
3393
|
SHARED_MODELS = /* @__PURE__ */ new Set(["permission", "role", "tag"]);
|
|
3163
3394
|
DB_MODELS = /* @__PURE__ */ new Set(["subscriptionPlan", "providerDefinition", "pipelineMasterTemplate"]);
|
|
@@ -3321,13 +3552,22 @@ var init_static_ref_scanner = __esm({
|
|
|
3321
3552
|
const paths = resolveProjectPaths(rootDir, loadConfig(rootDir));
|
|
3322
3553
|
if (!paths) return { cross_refs: [], flagged_edges: [], warnings: [] };
|
|
3323
3554
|
const srcDir = paths.srcDir;
|
|
3324
|
-
const
|
|
3325
|
-
|
|
3326
|
-
|
|
3327
|
-
|
|
3328
|
-
|
|
3329
|
-
|
|
3330
|
-
|
|
3555
|
+
const seenFile = /* @__PURE__ */ new Set();
|
|
3556
|
+
const files = [];
|
|
3557
|
+
for (const root of paths.srcRoots) {
|
|
3558
|
+
for (const f of walkWithIgnore(root, [".ts", ".tsx"])) {
|
|
3559
|
+
if (!seenFile.has(f)) {
|
|
3560
|
+
seenFile.add(f);
|
|
3561
|
+
files.push(f);
|
|
3562
|
+
}
|
|
3563
|
+
}
|
|
3564
|
+
}
|
|
3565
|
+
for (const conv of paths.conventionFiles) {
|
|
3566
|
+
if (!seenFile.has(conv)) {
|
|
3567
|
+
seenFile.add(conv);
|
|
3568
|
+
files.push(conv);
|
|
3569
|
+
}
|
|
3570
|
+
}
|
|
3331
3571
|
const uiOutput = layerOutputs.get("ui");
|
|
3332
3572
|
const apiOutput = layerOutputs.get("api");
|
|
3333
3573
|
const uiNodeIds = new Set(uiOutput?.nodes.map((n) => n.id) ?? []);
|
|
@@ -3344,7 +3584,8 @@ var init_static_ref_scanner = __esm({
|
|
|
3344
3584
|
const seen = /* @__PURE__ */ new Set();
|
|
3345
3585
|
let filesScanned = 0;
|
|
3346
3586
|
for (const absPath of files) {
|
|
3347
|
-
const
|
|
3587
|
+
const relFromSrc = (0, import_node_path11.relative)(srcDir, absPath).replace(/\\/g, "/");
|
|
3588
|
+
const sourceId = relFromSrc.startsWith("..") ? (0, import_node_path11.relative)(rootDir, absPath).replace(/\\/g, "/") : relFromSrc;
|
|
3348
3589
|
const sourceLayer = uiNodeIds.has(sourceId) ? "ui" : apiNodeIds.has(sourceId) ? "api" : null;
|
|
3349
3590
|
if (!sourceLayer) continue;
|
|
3350
3591
|
const content = (0, import_node_fs12.readFileSync)(absPath, "utf-8");
|
|
@@ -3816,13 +4057,17 @@ function isTrivialGroup(name, extraTrivial) {
|
|
|
3816
4057
|
function normalizeGroupName(name) {
|
|
3817
4058
|
return name.toLowerCase().replace(/-pages?$/, "").replace(/-layout$/, "").replace(/-wrapper$/, "");
|
|
3818
4059
|
}
|
|
3819
|
-
function extractModuleFromPath(id, extraTrivial, extraSkipSegments) {
|
|
4060
|
+
function extractModuleFromPath(id, extraTrivial, extraSkipSegments, extraGenericRoles) {
|
|
3820
4061
|
const segments = id.split("/");
|
|
3821
4062
|
const routeGroups = extractRouteGroups(id);
|
|
3822
4063
|
const skipSegments = new Set(SKIP_SEGMENTS_BUILTIN);
|
|
4064
|
+
for (const r of GENERIC_ROLE_NAMES_BUILTIN) skipSegments.add(r);
|
|
3823
4065
|
if (extraSkipSegments) {
|
|
3824
4066
|
for (const s of extraSkipSegments) skipSegments.add(s);
|
|
3825
4067
|
}
|
|
4068
|
+
if (extraGenericRoles) {
|
|
4069
|
+
for (const r of extraGenericRoles) skipSegments.add(r);
|
|
4070
|
+
}
|
|
3826
4071
|
const moduleGroups = routeGroups.filter((g) => !isTrivialGroup(g, extraTrivial)).map(normalizeGroupName);
|
|
3827
4072
|
if (moduleGroups.length > 0) {
|
|
3828
4073
|
return moduleGroups[moduleGroups.length - 1];
|
|
@@ -6042,7 +6287,7 @@ function handleAuditLayer(args) {
|
|
|
6042
6287
|
}
|
|
6043
6288
|
lines.push("");
|
|
6044
6289
|
}
|
|
6045
|
-
return
|
|
6290
|
+
return ok(lines.join("\n"));
|
|
6046
6291
|
}
|
|
6047
6292
|
function handleDetectProjectStack() {
|
|
6048
6293
|
const rootDir = process.cwd();
|
|
@@ -6108,7 +6353,7 @@ function handleDetectProjectStack() {
|
|
|
6108
6353
|
const descriptions = {
|
|
6109
6354
|
"fetch-resolver": "Detects direct fetch()/api.get() calls with inline URLs",
|
|
6110
6355
|
"api-annotations": "Scans for @api METHOD /path annotations in JSDoc/comments",
|
|
6111
|
-
"url-literal-scanner": "Finds
|
|
6356
|
+
"url-literal-scanner": "Finds path-like string literals (any /\u2026) and resolves them to API routes",
|
|
6112
6357
|
"static-ref-scanner": "Finds references to static values (enums, permissions, roles)"
|
|
6113
6358
|
};
|
|
6114
6359
|
const grouped = {};
|