@launchsecure/launch-kit 0.0.21 → 0.0.23
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 +354 -98
- package/dist/server/cli.js +355 -99
- 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 +359 -99
- 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,9 +1410,17 @@ 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
|
-
if (route)
|
|
1417
|
+
if (route) {
|
|
1418
|
+
routeToNodeId.set(route, id);
|
|
1419
|
+
const trimmed = route.replace(/\/\*[^/]+\?$/, "");
|
|
1420
|
+
if (trimmed && trimmed !== route && !routeToNodeId.has(trimmed)) {
|
|
1421
|
+
routeToNodeId.set(trimmed, id);
|
|
1422
|
+
}
|
|
1423
|
+
}
|
|
1313
1424
|
}
|
|
1314
1425
|
}
|
|
1315
1426
|
const uiEdges = [];
|
|
@@ -1331,6 +1442,29 @@ function generate(rootDir) {
|
|
|
1331
1442
|
uiEdges.push(...edges);
|
|
1332
1443
|
uiFlagged.push(...flagged);
|
|
1333
1444
|
}
|
|
1445
|
+
const layoutsById = /* @__PURE__ */ new Set();
|
|
1446
|
+
for (const n of uiNodes) {
|
|
1447
|
+
if (n.type === "layout") layoutsById.add(n.id);
|
|
1448
|
+
}
|
|
1449
|
+
function findClosestLayout(pageId) {
|
|
1450
|
+
let dir = pageId.replace(/\/page\.tsx$/, "");
|
|
1451
|
+
while (dir.length > 0) {
|
|
1452
|
+
const candidate = `${dir}/layout.tsx`;
|
|
1453
|
+
if (layoutsById.has(candidate)) return candidate;
|
|
1454
|
+
const slash = dir.lastIndexOf("/");
|
|
1455
|
+
if (slash < 0) break;
|
|
1456
|
+
dir = dir.slice(0, slash);
|
|
1457
|
+
}
|
|
1458
|
+
if (layoutsById.has("app/layout.tsx")) return "app/layout.tsx";
|
|
1459
|
+
return null;
|
|
1460
|
+
}
|
|
1461
|
+
for (const n of uiNodes) {
|
|
1462
|
+
if (n.type !== "page") continue;
|
|
1463
|
+
const layoutId = findClosestLayout(n.id);
|
|
1464
|
+
if (layoutId && layoutId !== n.id) {
|
|
1465
|
+
uiEdges.push({ source: layoutId, target: n.id, type: "wraps" });
|
|
1466
|
+
}
|
|
1467
|
+
}
|
|
1334
1468
|
const fetchCallEntries = [];
|
|
1335
1469
|
for (const absPath of fileSet) {
|
|
1336
1470
|
const sourceId = toNodeId(srcDir, rootDir, absPath);
|
|
@@ -1412,17 +1546,37 @@ function generate(rootDir) {
|
|
|
1412
1546
|
if (!dbCalls) continue;
|
|
1413
1547
|
const seenModels = /* @__PURE__ */ new Set();
|
|
1414
1548
|
for (const call of dbCalls) {
|
|
1415
|
-
|
|
1416
|
-
seenModels.
|
|
1549
|
+
const target = call.kind === "sql" ? call.model : camelToPascal(call.model);
|
|
1550
|
+
if (seenModels.has(target)) continue;
|
|
1551
|
+
seenModels.add(target);
|
|
1417
1552
|
apiCrossRefs.push({
|
|
1418
1553
|
source: node.id,
|
|
1419
|
-
target
|
|
1554
|
+
target,
|
|
1420
1555
|
type: call.isMutation ? "mutates" : "reads",
|
|
1421
1556
|
layer: "db"
|
|
1422
1557
|
});
|
|
1423
1558
|
}
|
|
1424
1559
|
delete node._dbCalls;
|
|
1425
1560
|
}
|
|
1561
|
+
const uiCrossRefs = [];
|
|
1562
|
+
for (const node of uiNodes) {
|
|
1563
|
+
const dbCalls = node._dbCalls;
|
|
1564
|
+
if (!dbCalls) continue;
|
|
1565
|
+
const seenModels = /* @__PURE__ */ new Set();
|
|
1566
|
+
for (const call of dbCalls) {
|
|
1567
|
+
const target = call.kind === "sql" ? call.model : camelToPascal(call.model);
|
|
1568
|
+
if (seenModels.has(target)) continue;
|
|
1569
|
+
seenModels.add(target);
|
|
1570
|
+
uiCrossRefs.push({
|
|
1571
|
+
source: node.id,
|
|
1572
|
+
target,
|
|
1573
|
+
type: call.isMutation ? "mutates" : "reads",
|
|
1574
|
+
layer: "db"
|
|
1575
|
+
});
|
|
1576
|
+
}
|
|
1577
|
+
delete node._dbCalls;
|
|
1578
|
+
}
|
|
1579
|
+
uiCrossRefs.sort((a, b) => a.source.localeCompare(b.source) || a.target.localeCompare(b.target));
|
|
1426
1580
|
const apiNodeIds = new Set(apiNodes.map((n) => n.id));
|
|
1427
1581
|
const apiEdges = [];
|
|
1428
1582
|
const uiOnlyEdges = [];
|
|
@@ -1484,7 +1638,7 @@ function generate(rootDir) {
|
|
|
1484
1638
|
},
|
|
1485
1639
|
nodes: stripLayer(uiNodes),
|
|
1486
1640
|
edges: uiOnlyEdges,
|
|
1487
|
-
cross_refs:
|
|
1641
|
+
cross_refs: uiCrossRefs,
|
|
1488
1642
|
contradictions: [],
|
|
1489
1643
|
warnings: [],
|
|
1490
1644
|
flagged_edges: dedupedFlagged,
|
|
@@ -1568,6 +1722,7 @@ var init_typescript_project = __esm({
|
|
|
1568
1722
|
HTTP_METHODS = /* @__PURE__ */ new Set(["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"]);
|
|
1569
1723
|
CLASSIFICATION_TO_LAYER = {
|
|
1570
1724
|
endpoint: "api",
|
|
1725
|
+
"server-action": "api",
|
|
1571
1726
|
page: "ui",
|
|
1572
1727
|
layout: "ui",
|
|
1573
1728
|
component: "ui",
|
|
@@ -2348,6 +2503,10 @@ function buildApiPathMap(routes) {
|
|
|
2348
2503
|
const map = /* @__PURE__ */ new Map();
|
|
2349
2504
|
for (const r of routes) {
|
|
2350
2505
|
if (!map.has(r.path)) map.set(r.path, r.nodeId);
|
|
2506
|
+
const trimmed = r.path.replace(/\/\*[^/]+\?$/, "");
|
|
2507
|
+
if (trimmed && trimmed !== r.path && !map.has(trimmed)) {
|
|
2508
|
+
map.set(trimmed, r.nodeId);
|
|
2509
|
+
}
|
|
2351
2510
|
}
|
|
2352
2511
|
return map;
|
|
2353
2512
|
}
|
|
@@ -2370,26 +2529,54 @@ function normalizeFetchUrl(raw) {
|
|
|
2370
2529
|
return { path: s || "/", hadInterpolation };
|
|
2371
2530
|
}
|
|
2372
2531
|
function scoreApiRouteMatch(candidate, known) {
|
|
2373
|
-
if (candidate.length !== known.length) return -1;
|
|
2374
2532
|
let score = 0;
|
|
2375
|
-
|
|
2533
|
+
let i = 0, j = 0;
|
|
2534
|
+
while (i < candidate.length && j < known.length) {
|
|
2376
2535
|
const a = candidate[i];
|
|
2377
|
-
const b = known[
|
|
2536
|
+
const b = known[j];
|
|
2537
|
+
if (b.startsWith("*") && b.endsWith("?")) {
|
|
2538
|
+
score += 1;
|
|
2539
|
+
return score;
|
|
2540
|
+
}
|
|
2541
|
+
if (b.startsWith("*")) {
|
|
2542
|
+
const remaining = candidate.length - i;
|
|
2543
|
+
if (remaining < 1) return -1;
|
|
2544
|
+
score += 1 + remaining;
|
|
2545
|
+
return score;
|
|
2546
|
+
}
|
|
2378
2547
|
if (a === b) {
|
|
2379
2548
|
score += 3;
|
|
2549
|
+
i++;
|
|
2550
|
+
j++;
|
|
2380
2551
|
continue;
|
|
2381
2552
|
}
|
|
2382
2553
|
if (a.startsWith(":") && b.startsWith(":")) {
|
|
2383
2554
|
score += 2;
|
|
2555
|
+
i++;
|
|
2556
|
+
j++;
|
|
2384
2557
|
continue;
|
|
2385
2558
|
}
|
|
2386
2559
|
if (a.startsWith(":") || b.startsWith(":")) {
|
|
2387
2560
|
score += 1;
|
|
2561
|
+
i++;
|
|
2562
|
+
j++;
|
|
2388
2563
|
continue;
|
|
2389
2564
|
}
|
|
2390
2565
|
return -1;
|
|
2391
2566
|
}
|
|
2392
|
-
|
|
2567
|
+
if (i === candidate.length) {
|
|
2568
|
+
while (j < known.length) {
|
|
2569
|
+
const b = known[j];
|
|
2570
|
+
if (b.startsWith("*") && b.endsWith("?")) {
|
|
2571
|
+
score += 1;
|
|
2572
|
+
j++;
|
|
2573
|
+
continue;
|
|
2574
|
+
}
|
|
2575
|
+
return -1;
|
|
2576
|
+
}
|
|
2577
|
+
return score;
|
|
2578
|
+
}
|
|
2579
|
+
return -1;
|
|
2393
2580
|
}
|
|
2394
2581
|
function resolveFetchCall(call, apiPathMap, apiRoutes) {
|
|
2395
2582
|
const raw = call.url;
|
|
@@ -2556,22 +2743,12 @@ var init_fetch_resolver = __esm({
|
|
|
2556
2743
|
});
|
|
2557
2744
|
|
|
2558
2745
|
// 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
|
-
}
|
|
2746
|
+
function toNodeId2(srcDir, rootDir, absPath) {
|
|
2747
|
+
const relFromSrc = (0, import_node_path8.relative)(srcDir, absPath).replace(/\\/g, "/");
|
|
2748
|
+
if (relFromSrc.startsWith("..")) {
|
|
2749
|
+
return (0, import_node_path8.relative)(rootDir, absPath).replace(/\\/g, "/");
|
|
2570
2750
|
}
|
|
2571
|
-
return
|
|
2572
|
-
}
|
|
2573
|
-
function toNodeId2(srcDir, absPath) {
|
|
2574
|
-
return (0, import_node_path8.relative)(srcDir, absPath).replace(/\\/g, "/");
|
|
2751
|
+
return relFromSrc;
|
|
2575
2752
|
}
|
|
2576
2753
|
var import_node_fs9, import_node_path8, API_ANNOTATION_RE, apiAnnotationsParser;
|
|
2577
2754
|
var init_api_annotations = __esm({
|
|
@@ -2580,31 +2757,53 @@ var init_api_annotations = __esm({
|
|
|
2580
2757
|
import_node_fs9 = require("node:fs");
|
|
2581
2758
|
import_node_path8 = require("node:path");
|
|
2582
2759
|
init_api_route_matching();
|
|
2760
|
+
init_config();
|
|
2761
|
+
init_resolve_paths();
|
|
2762
|
+
init_walk();
|
|
2583
2763
|
API_ANNOTATION_RE = /@api\s+(GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS)\s+(\/\S+)/g;
|
|
2584
2764
|
apiAnnotationsParser = {
|
|
2585
2765
|
id: "api-annotations",
|
|
2586
2766
|
layer: "crosslayer",
|
|
2587
2767
|
concern: "api-binding",
|
|
2588
2768
|
detect(rootDir) {
|
|
2589
|
-
return (
|
|
2769
|
+
return resolveProjectPaths(rootDir, loadConfig(rootDir)) !== null;
|
|
2590
2770
|
},
|
|
2591
2771
|
generate(rootDir, layerOutputs) {
|
|
2592
2772
|
const apiOutput = layerOutputs.get("api");
|
|
2593
2773
|
if (!apiOutput) {
|
|
2594
2774
|
return { cross_refs: [], flagged_edges: [], warnings: [] };
|
|
2595
2775
|
}
|
|
2776
|
+
const paths = resolveProjectPaths(rootDir, loadConfig(rootDir));
|
|
2777
|
+
if (!paths) {
|
|
2778
|
+
return { cross_refs: [], flagged_edges: [], warnings: [] };
|
|
2779
|
+
}
|
|
2596
2780
|
const uiOutput = layerOutputs.get("ui");
|
|
2597
2781
|
const uiNodeIds = new Set(uiOutput?.nodes.map((n) => n.id) ?? []);
|
|
2598
2782
|
const apiRoutes = loadApiRoutesFromOutput(apiOutput);
|
|
2599
2783
|
const apiPathMap = buildApiPathMap(apiRoutes);
|
|
2600
|
-
const srcDir =
|
|
2601
|
-
const
|
|
2784
|
+
const srcDir = paths.srcDir;
|
|
2785
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2786
|
+
const files = [];
|
|
2787
|
+
for (const root of paths.srcRoots) {
|
|
2788
|
+
for (const f of walk(root, [".ts", ".tsx"])) {
|
|
2789
|
+
if (!seen.has(f)) {
|
|
2790
|
+
seen.add(f);
|
|
2791
|
+
files.push(f);
|
|
2792
|
+
}
|
|
2793
|
+
}
|
|
2794
|
+
}
|
|
2795
|
+
for (const conv of paths.conventionFiles) {
|
|
2796
|
+
if (!seen.has(conv)) {
|
|
2797
|
+
seen.add(conv);
|
|
2798
|
+
files.push(conv);
|
|
2799
|
+
}
|
|
2800
|
+
}
|
|
2602
2801
|
const crossRefs = [];
|
|
2603
2802
|
const flaggedEdges = [];
|
|
2604
|
-
const
|
|
2803
|
+
const seenEdge = /* @__PURE__ */ new Set();
|
|
2605
2804
|
for (const absPath of files) {
|
|
2606
2805
|
const content = (0, import_node_fs9.readFileSync)(absPath, "utf-8");
|
|
2607
|
-
const sourceId = toNodeId2(srcDir, absPath);
|
|
2806
|
+
const sourceId = toNodeId2(srcDir, rootDir, absPath);
|
|
2608
2807
|
if (!uiNodeIds.has(sourceId)) continue;
|
|
2609
2808
|
let match;
|
|
2610
2809
|
API_ANNOTATION_RE.lastIndex = 0;
|
|
@@ -2614,8 +2813,8 @@ var init_api_annotations = __esm({
|
|
|
2614
2813
|
const result = resolveUrlPath(urlPath, apiPathMap, apiRoutes);
|
|
2615
2814
|
if (result.kind === "resolved" && result.nodeId) {
|
|
2616
2815
|
const key = `${sourceId}|${result.nodeId}|calls_api`;
|
|
2617
|
-
if (
|
|
2618
|
-
|
|
2816
|
+
if (seenEdge.has(key)) continue;
|
|
2817
|
+
seenEdge.add(key);
|
|
2619
2818
|
crossRefs.push({
|
|
2620
2819
|
source: sourceId,
|
|
2621
2820
|
target: result.nodeId,
|
|
@@ -2649,22 +2848,12 @@ var init_api_annotations = __esm({
|
|
|
2649
2848
|
});
|
|
2650
2849
|
|
|
2651
2850
|
// 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
|
-
}
|
|
2851
|
+
function toNodeId3(srcDir, rootDir, absPath) {
|
|
2852
|
+
const relFromSrc = (0, import_node_path9.relative)(srcDir, absPath).replace(/\\/g, "/");
|
|
2853
|
+
if (relFromSrc.startsWith("..")) {
|
|
2854
|
+
return (0, import_node_path9.relative)(rootDir, absPath).replace(/\\/g, "/");
|
|
2663
2855
|
}
|
|
2664
|
-
return
|
|
2665
|
-
}
|
|
2666
|
-
function toNodeId3(srcDir, absPath) {
|
|
2667
|
-
return (0, import_node_path9.relative)(srcDir, absPath).replace(/\\/g, "/");
|
|
2856
|
+
return relFromSrc;
|
|
2668
2857
|
}
|
|
2669
2858
|
var import_node_fs10, import_node_path9, URL_LITERAL_RE, urlLiteralScannerParser;
|
|
2670
2859
|
var init_url_literal_scanner = __esm({
|
|
@@ -2675,7 +2864,8 @@ var init_url_literal_scanner = __esm({
|
|
|
2675
2864
|
init_api_route_matching();
|
|
2676
2865
|
init_config();
|
|
2677
2866
|
init_resolve_paths();
|
|
2678
|
-
|
|
2867
|
+
init_walk();
|
|
2868
|
+
URL_LITERAL_RE = /['"`](\/[a-zA-Z][^'"`\s]*?)['"`]/g;
|
|
2679
2869
|
urlLiteralScannerParser = {
|
|
2680
2870
|
id: "url-literal-scanner",
|
|
2681
2871
|
layer: "crosslayer",
|
|
@@ -2695,15 +2885,26 @@ var init_url_literal_scanner = __esm({
|
|
|
2695
2885
|
const apiPathMap = buildApiPathMap(apiRoutes);
|
|
2696
2886
|
const paths = resolveProjectPaths(rootDir, loadConfig(rootDir));
|
|
2697
2887
|
const srcDir = paths.srcDir;
|
|
2698
|
-
const
|
|
2699
|
-
const files = [
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2888
|
+
const seenFile = /* @__PURE__ */ new Set();
|
|
2889
|
+
const files = [];
|
|
2890
|
+
for (const root of paths.srcRoots) {
|
|
2891
|
+
for (const f of walk(root, [".ts", ".tsx"])) {
|
|
2892
|
+
if (!seenFile.has(f)) {
|
|
2893
|
+
seenFile.add(f);
|
|
2894
|
+
files.push(f);
|
|
2895
|
+
}
|
|
2896
|
+
}
|
|
2897
|
+
}
|
|
2898
|
+
for (const conv of paths.conventionFiles) {
|
|
2899
|
+
if (!seenFile.has(conv)) {
|
|
2900
|
+
seenFile.add(conv);
|
|
2901
|
+
files.push(conv);
|
|
2902
|
+
}
|
|
2903
|
+
}
|
|
2703
2904
|
const crossRefs = [];
|
|
2704
2905
|
const seen = /* @__PURE__ */ new Set();
|
|
2705
2906
|
for (const absPath of files) {
|
|
2706
|
-
const sourceId = toNodeId3(srcDir, absPath);
|
|
2907
|
+
const sourceId = toNodeId3(srcDir, rootDir, absPath);
|
|
2707
2908
|
if (!uiNodeIds.has(sourceId)) continue;
|
|
2708
2909
|
const content = (0, import_node_fs10.readFileSync)(absPath, "utf-8");
|
|
2709
2910
|
let match;
|
|
@@ -2766,10 +2967,15 @@ function classifyScope(source, model) {
|
|
|
2766
2967
|
function extractEnumValues(rootDir) {
|
|
2767
2968
|
const nodes = [];
|
|
2768
2969
|
const edges = [];
|
|
2769
|
-
const
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2970
|
+
const paths = resolveProjectPaths(rootDir, loadConfig(rootDir));
|
|
2971
|
+
const schemaPaths = [];
|
|
2972
|
+
if (paths?.dbConfig.kind === "prisma" && paths.dbConfig.schemaPath) {
|
|
2973
|
+
schemaPaths.push(paths.dbConfig.schemaPath);
|
|
2974
|
+
schemaPaths.push((0, import_node_path10.join)((0, import_node_path10.dirname)(paths.dbConfig.schemaPath), "schema"));
|
|
2975
|
+
} else {
|
|
2976
|
+
schemaPaths.push((0, import_node_path10.join)(rootDir, "prisma", "schema.prisma"));
|
|
2977
|
+
schemaPaths.push((0, import_node_path10.join)(rootDir, "prisma", "schema"));
|
|
2978
|
+
}
|
|
2773
2979
|
let content = "";
|
|
2774
2980
|
for (const p of schemaPaths) {
|
|
2775
2981
|
if ((0, import_node_fs11.existsSync)(p)) {
|
|
@@ -2853,7 +3059,7 @@ function extractStringArrayFromNode(node) {
|
|
|
2853
3059
|
return values;
|
|
2854
3060
|
}
|
|
2855
3061
|
function findArrayDecl(root, varName) {
|
|
2856
|
-
function
|
|
3062
|
+
function walk2(node) {
|
|
2857
3063
|
if (node.type === "variable_declarator") {
|
|
2858
3064
|
const nameNode = node.childForFieldName("name");
|
|
2859
3065
|
const valueNode = node.childForFieldName("value");
|
|
@@ -2866,12 +3072,12 @@ function findArrayDecl(root, varName) {
|
|
|
2866
3072
|
}
|
|
2867
3073
|
}
|
|
2868
3074
|
for (const child of node.namedChildren) {
|
|
2869
|
-
const found =
|
|
3075
|
+
const found = walk2(child);
|
|
2870
3076
|
if (found) return found;
|
|
2871
3077
|
}
|
|
2872
3078
|
return null;
|
|
2873
3079
|
}
|
|
2874
|
-
return
|
|
3080
|
+
return walk2(root);
|
|
2875
3081
|
}
|
|
2876
3082
|
function extractObjectPropsRegex(objStr) {
|
|
2877
3083
|
const props = {};
|
|
@@ -2934,11 +3140,26 @@ function modelToNodeType(model) {
|
|
|
2934
3140
|
function extractSeedData(rootDir) {
|
|
2935
3141
|
const nodes = [];
|
|
2936
3142
|
const edges = [];
|
|
2937
|
-
const
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
(0, import_node_path10.join)(
|
|
2941
|
-
|
|
3143
|
+
const paths = resolveProjectPaths(rootDir, loadConfig(rootDir));
|
|
3144
|
+
const candidates = [];
|
|
3145
|
+
if (paths?.dbDir) {
|
|
3146
|
+
candidates.push((0, import_node_path10.join)(paths.dbDir, "seed.ts"));
|
|
3147
|
+
candidates.push((0, import_node_path10.join)(paths.dbDir, "seed.js"));
|
|
3148
|
+
} else {
|
|
3149
|
+
candidates.push((0, import_node_path10.join)(rootDir, "prisma", "seed.ts"));
|
|
3150
|
+
candidates.push((0, import_node_path10.join)(rootDir, "prisma", "seed.js"));
|
|
3151
|
+
}
|
|
3152
|
+
const baseRoots = paths?.srcRoots ?? [(0, import_node_path10.join)(rootDir, "src")];
|
|
3153
|
+
for (const root of baseRoots) {
|
|
3154
|
+
candidates.push((0, import_node_path10.join)(root, "server", "lib", "system-tags.ts"));
|
|
3155
|
+
}
|
|
3156
|
+
const seedFiles = candidates.filter((p) => {
|
|
3157
|
+
try {
|
|
3158
|
+
return (0, import_node_fs11.existsSync)(p) && (0, import_node_fs11.statSync)(p).isFile();
|
|
3159
|
+
} catch {
|
|
3160
|
+
return false;
|
|
3161
|
+
}
|
|
3162
|
+
});
|
|
2942
3163
|
const useTreeSitter = tryLoadTreeSitter();
|
|
2943
3164
|
for (const filePath of seedFiles) {
|
|
2944
3165
|
const content = (0, import_node_fs11.readFileSync)(filePath, "utf-8");
|
|
@@ -3048,9 +3269,20 @@ function walkDir(dir, exts) {
|
|
|
3048
3269
|
}
|
|
3049
3270
|
function extractConstants(rootDir) {
|
|
3050
3271
|
const nodes = [];
|
|
3051
|
-
const
|
|
3052
|
-
|
|
3053
|
-
|
|
3272
|
+
const paths = resolveProjectPaths(rootDir, loadConfig(rootDir));
|
|
3273
|
+
const roots = paths?.srcRoots ?? [(0, import_node_path10.join)(rootDir, "src")];
|
|
3274
|
+
const seenFile = /* @__PURE__ */ new Set();
|
|
3275
|
+
const allFiles = [];
|
|
3276
|
+
for (const root of roots) {
|
|
3277
|
+
for (const f of walkDir(root, [".ts", ".tsx"])) {
|
|
3278
|
+
if (!seenFile.has(f)) {
|
|
3279
|
+
seenFile.add(f);
|
|
3280
|
+
allFiles.push(f);
|
|
3281
|
+
}
|
|
3282
|
+
}
|
|
3283
|
+
}
|
|
3284
|
+
if (allFiles.length === 0) return { nodes };
|
|
3285
|
+
for (const filePath of allFiles) {
|
|
3054
3286
|
const content = (0, import_node_fs11.readFileSync)(filePath, "utf-8");
|
|
3055
3287
|
const relPath = (0, import_node_path10.relative)(rootDir, filePath);
|
|
3056
3288
|
const constArrayRe = /export\s+const\s+([A-Z][A-Z_0-9]+)\s*(?::[^=]+)?\s*=\s*\[/g;
|
|
@@ -3085,6 +3317,13 @@ function extractConstants(rootDir) {
|
|
|
3085
3317
|
return { nodes };
|
|
3086
3318
|
}
|
|
3087
3319
|
function detect4(rootDir) {
|
|
3320
|
+
const paths = resolveProjectPaths(rootDir, loadConfig(rootDir));
|
|
3321
|
+
if (paths?.dbConfig.kind === "prisma" && paths.dbConfig.schemaPath && (0, import_node_fs11.existsSync)(paths.dbConfig.schemaPath)) {
|
|
3322
|
+
return true;
|
|
3323
|
+
}
|
|
3324
|
+
if (paths?.dbDir) {
|
|
3325
|
+
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;
|
|
3326
|
+
}
|
|
3088
3327
|
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
3328
|
}
|
|
3090
3329
|
function generate4(rootDir) {
|
|
@@ -3158,6 +3397,8 @@ var init_static_values = __esm({
|
|
|
3158
3397
|
"use strict";
|
|
3159
3398
|
import_node_fs11 = require("node:fs");
|
|
3160
3399
|
import_node_path10 = require("node:path");
|
|
3400
|
+
init_config();
|
|
3401
|
+
init_resolve_paths();
|
|
3161
3402
|
parseCode = null;
|
|
3162
3403
|
SHARED_MODELS = /* @__PURE__ */ new Set(["permission", "role", "tag"]);
|
|
3163
3404
|
DB_MODELS = /* @__PURE__ */ new Set(["subscriptionPlan", "providerDefinition", "pipelineMasterTemplate"]);
|
|
@@ -3321,13 +3562,22 @@ var init_static_ref_scanner = __esm({
|
|
|
3321
3562
|
const paths = resolveProjectPaths(rootDir, loadConfig(rootDir));
|
|
3322
3563
|
if (!paths) return { cross_refs: [], flagged_edges: [], warnings: [] };
|
|
3323
3564
|
const srcDir = paths.srcDir;
|
|
3324
|
-
const
|
|
3325
|
-
|
|
3326
|
-
|
|
3327
|
-
|
|
3328
|
-
|
|
3329
|
-
|
|
3330
|
-
|
|
3565
|
+
const seenFile = /* @__PURE__ */ new Set();
|
|
3566
|
+
const files = [];
|
|
3567
|
+
for (const root of paths.srcRoots) {
|
|
3568
|
+
for (const f of walkWithIgnore(root, [".ts", ".tsx"])) {
|
|
3569
|
+
if (!seenFile.has(f)) {
|
|
3570
|
+
seenFile.add(f);
|
|
3571
|
+
files.push(f);
|
|
3572
|
+
}
|
|
3573
|
+
}
|
|
3574
|
+
}
|
|
3575
|
+
for (const conv of paths.conventionFiles) {
|
|
3576
|
+
if (!seenFile.has(conv)) {
|
|
3577
|
+
seenFile.add(conv);
|
|
3578
|
+
files.push(conv);
|
|
3579
|
+
}
|
|
3580
|
+
}
|
|
3331
3581
|
const uiOutput = layerOutputs.get("ui");
|
|
3332
3582
|
const apiOutput = layerOutputs.get("api");
|
|
3333
3583
|
const uiNodeIds = new Set(uiOutput?.nodes.map((n) => n.id) ?? []);
|
|
@@ -3344,7 +3594,8 @@ var init_static_ref_scanner = __esm({
|
|
|
3344
3594
|
const seen = /* @__PURE__ */ new Set();
|
|
3345
3595
|
let filesScanned = 0;
|
|
3346
3596
|
for (const absPath of files) {
|
|
3347
|
-
const
|
|
3597
|
+
const relFromSrc = (0, import_node_path11.relative)(srcDir, absPath).replace(/\\/g, "/");
|
|
3598
|
+
const sourceId = relFromSrc.startsWith("..") ? (0, import_node_path11.relative)(rootDir, absPath).replace(/\\/g, "/") : relFromSrc;
|
|
3348
3599
|
const sourceLayer = uiNodeIds.has(sourceId) ? "ui" : apiNodeIds.has(sourceId) ? "api" : null;
|
|
3349
3600
|
if (!sourceLayer) continue;
|
|
3350
3601
|
const content = (0, import_node_fs12.readFileSync)(absPath, "utf-8");
|
|
@@ -3816,13 +4067,17 @@ function isTrivialGroup(name, extraTrivial) {
|
|
|
3816
4067
|
function normalizeGroupName(name) {
|
|
3817
4068
|
return name.toLowerCase().replace(/-pages?$/, "").replace(/-layout$/, "").replace(/-wrapper$/, "");
|
|
3818
4069
|
}
|
|
3819
|
-
function extractModuleFromPath(id, extraTrivial, extraSkipSegments) {
|
|
4070
|
+
function extractModuleFromPath(id, extraTrivial, extraSkipSegments, extraGenericRoles) {
|
|
3820
4071
|
const segments = id.split("/");
|
|
3821
4072
|
const routeGroups = extractRouteGroups(id);
|
|
3822
4073
|
const skipSegments = new Set(SKIP_SEGMENTS_BUILTIN);
|
|
4074
|
+
for (const r of GENERIC_ROLE_NAMES_BUILTIN) skipSegments.add(r);
|
|
3823
4075
|
if (extraSkipSegments) {
|
|
3824
4076
|
for (const s of extraSkipSegments) skipSegments.add(s);
|
|
3825
4077
|
}
|
|
4078
|
+
if (extraGenericRoles) {
|
|
4079
|
+
for (const r of extraGenericRoles) skipSegments.add(r);
|
|
4080
|
+
}
|
|
3826
4081
|
const moduleGroups = routeGroups.filter((g) => !isTrivialGroup(g, extraTrivial)).map(normalizeGroupName);
|
|
3827
4082
|
if (moduleGroups.length > 0) {
|
|
3828
4083
|
return moduleGroups[moduleGroups.length - 1];
|
|
@@ -3888,6 +4143,9 @@ var init_module_tagger = __esm({
|
|
|
3888
4143
|
"__tests__",
|
|
3889
4144
|
"spec",
|
|
3890
4145
|
"specs",
|
|
4146
|
+
"scripts",
|
|
4147
|
+
"bin",
|
|
4148
|
+
"tools",
|
|
3891
4149
|
// Go
|
|
3892
4150
|
"cmd",
|
|
3893
4151
|
"pkg",
|
|
@@ -4398,7 +4656,7 @@ function checkDeadScreens(rootDir) {
|
|
|
4398
4656
|
const pages = ui.nodes.filter((n) => n.type === "page" || n.type === "layout");
|
|
4399
4657
|
const navTargets = /* @__PURE__ */ new Set();
|
|
4400
4658
|
for (const e of ui.edges) {
|
|
4401
|
-
if (e.type === "navigates" || e.type === "renders" || e.type === "imports") {
|
|
4659
|
+
if (e.type === "navigates" || e.type === "renders" || e.type === "imports" || e.type === "wraps") {
|
|
4402
4660
|
navTargets.add(e.target);
|
|
4403
4661
|
}
|
|
4404
4662
|
}
|
|
@@ -4408,13 +4666,15 @@ function checkDeadScreens(rootDir) {
|
|
|
4408
4666
|
for (const page of pages) {
|
|
4409
4667
|
if (page.id.endsWith("layout.tsx") && page.id.split("/").length <= 2) continue;
|
|
4410
4668
|
if (["error.tsx", "loading.tsx", "not-found.tsx", "template.tsx"].some((s) => page.id.endsWith(s))) continue;
|
|
4669
|
+
const route = page.route;
|
|
4670
|
+
if (route === "/" || route === "") continue;
|
|
4411
4671
|
if (!navTargets.has(page.id)) {
|
|
4412
4672
|
findings.push({
|
|
4413
4673
|
id: `dead:${page.id}`,
|
|
4414
4674
|
severity: "info",
|
|
4415
4675
|
category: "dead_screens",
|
|
4416
4676
|
title: page.name,
|
|
4417
|
-
detail: `Page "${page.id}" has no incoming navigation, render, or
|
|
4677
|
+
detail: `Page "${page.id}" has no incoming navigation, render, import, or layout-wrap edges.`,
|
|
4418
4678
|
file: page.id
|
|
4419
4679
|
});
|
|
4420
4680
|
}
|
|
@@ -6108,7 +6368,7 @@ function handleDetectProjectStack() {
|
|
|
6108
6368
|
const descriptions = {
|
|
6109
6369
|
"fetch-resolver": "Detects direct fetch()/api.get() calls with inline URLs",
|
|
6110
6370
|
"api-annotations": "Scans for @api METHOD /path annotations in JSDoc/comments",
|
|
6111
|
-
"url-literal-scanner": "Finds
|
|
6371
|
+
"url-literal-scanner": "Finds path-like string literals (any /\u2026) and resolves them to API routes",
|
|
6112
6372
|
"static-ref-scanner": "Finds references to static values (enums, permissions, roles)"
|
|
6113
6373
|
};
|
|
6114
6374
|
const grouped = {};
|