@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
|
@@ -285,6 +285,12 @@ function parseFileTS(absPath) {
|
|
|
285
285
|
if (linkTemplate) {
|
|
286
286
|
navigations.push({ kind: "link-href", target: linkTemplate, isTemplate: true });
|
|
287
287
|
}
|
|
288
|
+
if (caps["nav.redirect.literal"]) {
|
|
289
|
+
navigations.push({ kind: "router-replace", target: caps["nav.redirect.literal"], isTemplate: false });
|
|
290
|
+
}
|
|
291
|
+
if (caps["nav.redirect.template"]) {
|
|
292
|
+
navigations.push({ kind: "router-replace", target: caps["nav.redirect.template"], isTemplate: true });
|
|
293
|
+
}
|
|
288
294
|
if (caps["nav.window.literal"]) {
|
|
289
295
|
navigations.push({ kind: "window-location", target: caps["nav.window.literal"], isTemplate: false });
|
|
290
296
|
}
|
|
@@ -363,15 +369,25 @@ function extractDbCallsTS(absPath) {
|
|
|
363
369
|
const seen = /* @__PURE__ */ new Set();
|
|
364
370
|
for (const m of matches) {
|
|
365
371
|
const caps = captureMap(m);
|
|
372
|
+
const sbTable = caps["sb.table"];
|
|
373
|
+
const sbMethod = caps["sb.method"];
|
|
374
|
+
if (sbTable && sbMethod) {
|
|
375
|
+
const key2 = `sql:${sbTable}.${sbMethod}`;
|
|
376
|
+
if (seen.has(key2)) continue;
|
|
377
|
+
seen.add(key2);
|
|
378
|
+
const isMutation = SUPABASE_MUTATION_METHODS_BUILTIN.has(sbMethod) || extraMutationMethods.includes(sbMethod);
|
|
379
|
+
calls.push({ model: sbTable, method: sbMethod, isMutation, kind: "sql" });
|
|
380
|
+
continue;
|
|
381
|
+
}
|
|
366
382
|
const identifier = caps["db.identifier"];
|
|
367
383
|
const model = caps["db.model"];
|
|
368
384
|
const method = caps["db.method"];
|
|
369
385
|
if (!identifier || !model || !method) continue;
|
|
370
386
|
if (!dbIdentifiers.has(identifier)) continue;
|
|
371
|
-
const key =
|
|
387
|
+
const key = `orm:${model}.${method}`;
|
|
372
388
|
if (seen.has(key)) continue;
|
|
373
389
|
seen.add(key);
|
|
374
|
-
calls.push({ model, method, isMutation: getMutationMethods().has(method) });
|
|
390
|
+
calls.push({ model, method, isMutation: getMutationMethods().has(method), kind: "orm" });
|
|
375
391
|
}
|
|
376
392
|
return calls;
|
|
377
393
|
}
|
|
@@ -385,6 +401,9 @@ function classifyFile(absPath) {
|
|
|
385
401
|
const captures = classifyQuery.captures(root);
|
|
386
402
|
const capNames = new Set(captures.map((c) => c.name));
|
|
387
403
|
if (capNames.has("http_export") || capNames.has("http_export_fn")) return "endpoint";
|
|
404
|
+
if (capNames.has("use_server_directive") && fileName !== "page.tsx" && fileName !== "layout.tsx") {
|
|
405
|
+
return "server-action";
|
|
406
|
+
}
|
|
388
407
|
if (fileName === "page.tsx" && capNames.has("has_jsx")) return "page";
|
|
389
408
|
if (fileName === "layout.tsx" && capNames.has("has_jsx")) return "layout";
|
|
390
409
|
if (capNames.has("has_create_context") || capNames.has("has_create_context_bare")) return "context";
|
|
@@ -405,6 +424,36 @@ function extractAuthWrappersTS(absPath) {
|
|
|
405
424
|
wrappers.add(caps["wrapper.fn_name"]);
|
|
406
425
|
}
|
|
407
426
|
}
|
|
427
|
+
const inlineHelpers = /* @__PURE__ */ new Set();
|
|
428
|
+
for (const stmt of childrenOfType(root, "import_statement")) {
|
|
429
|
+
const sourceNode = childOfType(stmt, "string");
|
|
430
|
+
const frag = sourceNode ? childOfType(sourceNode, "string_fragment") : void 0;
|
|
431
|
+
if (!frag) continue;
|
|
432
|
+
const provider = INLINE_AUTH_IMPORTS.find((p) => p.module.test(frag.text));
|
|
433
|
+
if (!provider) continue;
|
|
434
|
+
const clause = childOfType(stmt, "import_clause");
|
|
435
|
+
if (!clause) continue;
|
|
436
|
+
const named = childOfType(clause, "named_imports");
|
|
437
|
+
if (!named) continue;
|
|
438
|
+
for (const specNode of childrenOfType(named, "import_specifier")) {
|
|
439
|
+
const ids = childrenOfType(specNode, "identifier");
|
|
440
|
+
const importedName = ids[0]?.text;
|
|
441
|
+
const localName = ids[ids.length - 1]?.text;
|
|
442
|
+
if (importedName && provider.helpers.includes(importedName) && localName) {
|
|
443
|
+
inlineHelpers.add(localName);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
if (inlineHelpers.size > 0) {
|
|
448
|
+
const text = root.text;
|
|
449
|
+
for (const name of inlineHelpers) {
|
|
450
|
+
const re = new RegExp(`\\b${name}\\s*\\(`);
|
|
451
|
+
if (re.test(text)) {
|
|
452
|
+
wrappers.add("inline");
|
|
453
|
+
break;
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
}
|
|
408
457
|
return wrappers;
|
|
409
458
|
}
|
|
410
459
|
function trunc(s, max = 120) {
|
|
@@ -563,7 +612,7 @@ function extractDeep(absPath) {
|
|
|
563
612
|
}
|
|
564
613
|
return { elements, stateVars, conditions, variables, responses, params };
|
|
565
614
|
}
|
|
566
|
-
var import_node_fs4, import_node_path4, tsxLanguage, parserInstance, initPromise, initialized, queriesDir, queryCache, PRISMA_MUTATION_METHODS_BUILTIN, DB_IDENTIFIERS_FALLBACK, extraDbIdentifiers, extraMutationMethods;
|
|
615
|
+
var import_node_fs4, import_node_path4, tsxLanguage, parserInstance, initPromise, initialized, queriesDir, queryCache, PRISMA_MUTATION_METHODS_BUILTIN, SUPABASE_MUTATION_METHODS_BUILTIN, DB_IDENTIFIERS_FALLBACK, extraDbIdentifiers, extraMutationMethods, INLINE_AUTH_IMPORTS;
|
|
567
616
|
var init_ts_extractor = __esm({
|
|
568
617
|
"src/server/graph/core/ts-extractor.ts"() {
|
|
569
618
|
"use strict";
|
|
@@ -587,9 +636,21 @@ var init_ts_extractor = __esm({
|
|
|
587
636
|
"delete",
|
|
588
637
|
"deleteMany"
|
|
589
638
|
];
|
|
639
|
+
SUPABASE_MUTATION_METHODS_BUILTIN = /* @__PURE__ */ new Set([
|
|
640
|
+
"insert",
|
|
641
|
+
"update",
|
|
642
|
+
"delete",
|
|
643
|
+
"upsert"
|
|
644
|
+
]);
|
|
590
645
|
DB_IDENTIFIERS_FALLBACK = ["db", "prisma"];
|
|
591
646
|
extraDbIdentifiers = [];
|
|
592
647
|
extraMutationMethods = [];
|
|
648
|
+
INLINE_AUTH_IMPORTS = [
|
|
649
|
+
{ module: /^@clerk\/nextjs(\/server)?$/, helpers: ["auth", "currentUser"] },
|
|
650
|
+
{ module: /^next-auth(\/.+)?$/, helpers: ["auth", "getServerSession"] },
|
|
651
|
+
{ module: /^@auth\//, helpers: ["auth"] },
|
|
652
|
+
{ module: /^@supabase\/auth-helpers/, helpers: ["createServerClient"] }
|
|
653
|
+
];
|
|
593
654
|
}
|
|
594
655
|
});
|
|
595
656
|
|
|
@@ -860,6 +921,7 @@ init_ts_extractor();
|
|
|
860
921
|
var HTTP_METHODS = /* @__PURE__ */ new Set(["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"]);
|
|
861
922
|
var CLASSIFICATION_TO_LAYER = {
|
|
862
923
|
endpoint: "api",
|
|
924
|
+
"server-action": "api",
|
|
863
925
|
page: "ui",
|
|
864
926
|
layout: "ui",
|
|
865
927
|
component: "ui",
|
|
@@ -958,6 +1020,8 @@ function extractRoute(id) {
|
|
|
958
1020
|
if (!id.endsWith("/page.tsx")) return null;
|
|
959
1021
|
let route = id.replace(/^app\//, "/").replace(/\/page\.tsx$/, "");
|
|
960
1022
|
route = route.replace(/\/\([^)]+\)/g, "");
|
|
1023
|
+
route = route.replace(/\[\[\.\.\.([^\]]+)\]\]/g, "*$1?");
|
|
1024
|
+
route = route.replace(/\[\.\.\.([^\]]+)\]/g, "*$1");
|
|
961
1025
|
route = route.replace(/\[([^\]]+)\]/g, ":$1");
|
|
962
1026
|
route = route.replace(/\/+/g, "/");
|
|
963
1027
|
if (!route.startsWith("/")) route = "/" + route;
|
|
@@ -969,6 +1033,7 @@ function nameFromFilename(absPath) {
|
|
|
969
1033
|
function filePathToAppRoute(appDir, absPath) {
|
|
970
1034
|
let route = ("/" + (0, import_node_path5.relative)(appDir, absPath).replace(/\\/g, "/")).replace(/\/route\.tsx?$/, "");
|
|
971
1035
|
route = route.replace(/\/\([^)]+\)/g, "");
|
|
1036
|
+
route = route.replace(/\[\[\.\.\.([^\]]+)\]\]/g, "*$1?");
|
|
972
1037
|
route = route.replace(/\[\.\.\.([^\]]+)\]/g, "*$1");
|
|
973
1038
|
route = route.replace(/\[([^\]]+)\]/g, ":$1");
|
|
974
1039
|
route = route.replace(/\/+/g, "/");
|
|
@@ -1017,25 +1082,52 @@ function resolveTemplateLiteralRoute(template, routeToNodeId) {
|
|
|
1017
1082
|
function routeMatchScore(candidate, known) {
|
|
1018
1083
|
const segsA = candidate.split("/");
|
|
1019
1084
|
const segsB = known.split("/");
|
|
1020
|
-
if (segsA.length !== segsB.length) return -1;
|
|
1021
1085
|
let score = 0;
|
|
1022
|
-
|
|
1023
|
-
|
|
1086
|
+
let i = 0, j = 0;
|
|
1087
|
+
while (i < segsA.length && j < segsB.length) {
|
|
1088
|
+
const a = segsA[i], b = segsB[j];
|
|
1089
|
+
if (b.startsWith("*") && b.endsWith("?")) {
|
|
1090
|
+
score += 1;
|
|
1091
|
+
return score;
|
|
1092
|
+
}
|
|
1093
|
+
if (b.startsWith("*")) {
|
|
1094
|
+
const remaining = segsA.length - i;
|
|
1095
|
+
if (remaining < 1) return -1;
|
|
1096
|
+
score += 1 + remaining;
|
|
1097
|
+
return score;
|
|
1098
|
+
}
|
|
1024
1099
|
if (a === b) {
|
|
1025
1100
|
score += 3;
|
|
1101
|
+
i++;
|
|
1102
|
+
j++;
|
|
1026
1103
|
continue;
|
|
1027
1104
|
}
|
|
1028
1105
|
if (a.startsWith(":") && b.startsWith(":")) {
|
|
1029
1106
|
score += 2;
|
|
1107
|
+
i++;
|
|
1108
|
+
j++;
|
|
1030
1109
|
continue;
|
|
1031
1110
|
}
|
|
1032
1111
|
if (a.startsWith(":") || b.startsWith(":")) {
|
|
1033
|
-
|
|
1112
|
+
i++;
|
|
1113
|
+
j++;
|
|
1034
1114
|
continue;
|
|
1035
1115
|
}
|
|
1036
1116
|
return -1;
|
|
1037
1117
|
}
|
|
1038
|
-
|
|
1118
|
+
if (i === segsA.length) {
|
|
1119
|
+
while (j < segsB.length) {
|
|
1120
|
+
const b = segsB[j];
|
|
1121
|
+
if (b.startsWith("*") && b.endsWith("?")) {
|
|
1122
|
+
score += 1;
|
|
1123
|
+
j++;
|
|
1124
|
+
continue;
|
|
1125
|
+
}
|
|
1126
|
+
return -1;
|
|
1127
|
+
}
|
|
1128
|
+
return score;
|
|
1129
|
+
}
|
|
1130
|
+
return -1;
|
|
1039
1131
|
}
|
|
1040
1132
|
function templateToRoute(template) {
|
|
1041
1133
|
return template.replace(/\$\{([^}]+)\}/g, (_, expr) => {
|
|
@@ -1070,7 +1162,7 @@ function extractEdges(srcDir, rootDir, absPath, sourceId, parsed, nodeIdSet, bar
|
|
|
1070
1162
|
edges.push(edge);
|
|
1071
1163
|
}
|
|
1072
1164
|
function edgeTypeFor(isTypeOnlyImport, importedNames) {
|
|
1073
|
-
if (isTypeOnlyImport) return "
|
|
1165
|
+
if (isTypeOnlyImport) return "imports_type";
|
|
1074
1166
|
const anyRendered = importedNames.some((n) => parsed.jsxElements.has(n));
|
|
1075
1167
|
if (anyRendered) return "renders";
|
|
1076
1168
|
return "imports";
|
|
@@ -1101,7 +1193,8 @@ function extractEdges(srcDir, rootDir, absPath, sourceId, parsed, nodeIdSet, bar
|
|
|
1101
1193
|
if (resolved) {
|
|
1102
1194
|
const targetId = toNodeId(srcDir, rootDir, resolved);
|
|
1103
1195
|
if (nodeIdSet.has(targetId) && !targetId.endsWith("/index.ts") && !targetId.endsWith("/index.tsx")) {
|
|
1104
|
-
|
|
1196
|
+
const allType = isTypeOnly || names.length > 0 && names.every((n) => typeNames.has(n));
|
|
1197
|
+
addEdge(targetId, edgeTypeFor(allType, names));
|
|
1105
1198
|
}
|
|
1106
1199
|
}
|
|
1107
1200
|
}
|
|
@@ -1110,7 +1203,8 @@ function extractEdges(srcDir, rootDir, absPath, sourceId, parsed, nodeIdSet, bar
|
|
|
1110
1203
|
if (resolved) {
|
|
1111
1204
|
const targetId = toNodeId(srcDir, rootDir, resolved);
|
|
1112
1205
|
if (nodeIdSet.has(targetId) && !targetId.endsWith("/index.ts") && !targetId.endsWith("/index.tsx")) {
|
|
1113
|
-
|
|
1206
|
+
const allType = isTypeOnly || names.length > 0 && names.every((n) => typeNames.has(n));
|
|
1207
|
+
addEdge(targetId, edgeTypeFor(allType, names));
|
|
1114
1208
|
}
|
|
1115
1209
|
}
|
|
1116
1210
|
}
|
|
@@ -1195,26 +1289,34 @@ function generate(rootDir) {
|
|
|
1195
1289
|
const layer = CLASSIFICATION_TO_LAYER[type] ?? "ui";
|
|
1196
1290
|
nodeIdSet.add(id);
|
|
1197
1291
|
if (layer === "api") {
|
|
1198
|
-
const methods = [];
|
|
1199
|
-
for (const exp of parsed.exports) {
|
|
1200
|
-
if (HTTP_METHODS.has(exp)) methods.push(exp);
|
|
1201
|
-
}
|
|
1202
1292
|
const dbCalls = extractDbCallsTS(absPath);
|
|
1203
1293
|
const authWrappers = extractAuthWrappersTS(absPath);
|
|
1204
1294
|
const deep = extractDeep(absPath);
|
|
1205
|
-
const routePath = filePathToAppRoute(paths.appDir, absPath);
|
|
1206
1295
|
const mutations = dbCalls.filter((c) => c.isMutation);
|
|
1207
1296
|
const mutates = mutations.length > 0;
|
|
1208
1297
|
const authStrategy = [...authWrappers];
|
|
1298
|
+
const isServerAction = type === "server-action";
|
|
1299
|
+
const methods = [];
|
|
1300
|
+
if (!isServerAction) {
|
|
1301
|
+
for (const exp of parsed.exports) {
|
|
1302
|
+
if (HTTP_METHODS.has(exp)) methods.push(exp);
|
|
1303
|
+
}
|
|
1304
|
+
}
|
|
1305
|
+
const routePath = isServerAction ? null : filePathToAppRoute(paths.appDir, absPath);
|
|
1306
|
+
const actions = isServerAction ? parsed.exports.filter((e) => !HTTP_METHODS.has(e)) : [];
|
|
1209
1307
|
apiNodes.push({
|
|
1210
1308
|
id,
|
|
1211
|
-
type: "endpoint",
|
|
1212
|
-
name:
|
|
1309
|
+
type: isServerAction ? "server-action" : "endpoint",
|
|
1310
|
+
// For HTTP routes: name = URL path. For Server Actions: name = file id +
|
|
1311
|
+
// exported action names — the callable surface, not a URL.
|
|
1312
|
+
name: isServerAction ? actions.length > 0 ? `${id} (${actions.join(", ")})` : id : routePath,
|
|
1213
1313
|
layer: "api",
|
|
1214
1314
|
path: routePath,
|
|
1315
|
+
// null for Server Actions
|
|
1215
1316
|
methods,
|
|
1216
1317
|
handler: id,
|
|
1217
1318
|
mutates,
|
|
1319
|
+
...isServerAction ? { actions } : {},
|
|
1218
1320
|
auth: authStrategy.length > 0 ? authStrategy : ["public"],
|
|
1219
1321
|
db_models: [...new Set(dbCalls.map((c) => c.model))],
|
|
1220
1322
|
db_operations: [...new Set(dbCalls.map((c) => `${c.model}.${c.method}`))],
|
|
@@ -1228,6 +1330,8 @@ function generate(rootDir) {
|
|
|
1228
1330
|
} else {
|
|
1229
1331
|
const route = extractRoute(id);
|
|
1230
1332
|
const deep = extractDeep(absPath);
|
|
1333
|
+
const dbCalls = extractDbCallsTS(absPath);
|
|
1334
|
+
const authWrappers = type === "page" || type === "layout" ? [...extractAuthWrappersTS(absPath)] : [];
|
|
1231
1335
|
uiNodes.push({
|
|
1232
1336
|
id,
|
|
1233
1337
|
type,
|
|
@@ -1238,9 +1342,17 @@ function generate(rootDir) {
|
|
|
1238
1342
|
elements: deep.elements,
|
|
1239
1343
|
stateVars: deep.stateVars,
|
|
1240
1344
|
conditions: deep.conditions,
|
|
1241
|
-
variables: deep.variables
|
|
1345
|
+
variables: deep.variables,
|
|
1346
|
+
...authWrappers.length > 0 ? { auth: authWrappers } : {},
|
|
1347
|
+
...dbCalls.length > 0 ? { _dbCalls: dbCalls } : {}
|
|
1242
1348
|
});
|
|
1243
|
-
if (route)
|
|
1349
|
+
if (route) {
|
|
1350
|
+
routeToNodeId.set(route, id);
|
|
1351
|
+
const trimmed = route.replace(/\/\*[^/]+\?$/, "");
|
|
1352
|
+
if (trimmed && trimmed !== route && !routeToNodeId.has(trimmed)) {
|
|
1353
|
+
routeToNodeId.set(trimmed, id);
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1244
1356
|
}
|
|
1245
1357
|
}
|
|
1246
1358
|
const uiEdges = [];
|
|
@@ -1262,6 +1374,29 @@ function generate(rootDir) {
|
|
|
1262
1374
|
uiEdges.push(...edges);
|
|
1263
1375
|
uiFlagged.push(...flagged);
|
|
1264
1376
|
}
|
|
1377
|
+
const layoutsById = /* @__PURE__ */ new Set();
|
|
1378
|
+
for (const n of uiNodes) {
|
|
1379
|
+
if (n.type === "layout") layoutsById.add(n.id);
|
|
1380
|
+
}
|
|
1381
|
+
function findClosestLayout(pageId) {
|
|
1382
|
+
let dir = pageId.replace(/\/page\.tsx$/, "");
|
|
1383
|
+
while (dir.length > 0) {
|
|
1384
|
+
const candidate = `${dir}/layout.tsx`;
|
|
1385
|
+
if (layoutsById.has(candidate)) return candidate;
|
|
1386
|
+
const slash = dir.lastIndexOf("/");
|
|
1387
|
+
if (slash < 0) break;
|
|
1388
|
+
dir = dir.slice(0, slash);
|
|
1389
|
+
}
|
|
1390
|
+
if (layoutsById.has("app/layout.tsx")) return "app/layout.tsx";
|
|
1391
|
+
return null;
|
|
1392
|
+
}
|
|
1393
|
+
for (const n of uiNodes) {
|
|
1394
|
+
if (n.type !== "page") continue;
|
|
1395
|
+
const layoutId = findClosestLayout(n.id);
|
|
1396
|
+
if (layoutId && layoutId !== n.id) {
|
|
1397
|
+
uiEdges.push({ source: layoutId, target: n.id, type: "wraps" });
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1265
1400
|
const fetchCallEntries = [];
|
|
1266
1401
|
for (const absPath of fileSet) {
|
|
1267
1402
|
const sourceId = toNodeId(srcDir, rootDir, absPath);
|
|
@@ -1343,17 +1478,37 @@ function generate(rootDir) {
|
|
|
1343
1478
|
if (!dbCalls) continue;
|
|
1344
1479
|
const seenModels = /* @__PURE__ */ new Set();
|
|
1345
1480
|
for (const call of dbCalls) {
|
|
1346
|
-
|
|
1347
|
-
seenModels.
|
|
1481
|
+
const target = call.kind === "sql" ? call.model : camelToPascal(call.model);
|
|
1482
|
+
if (seenModels.has(target)) continue;
|
|
1483
|
+
seenModels.add(target);
|
|
1348
1484
|
apiCrossRefs.push({
|
|
1349
1485
|
source: node.id,
|
|
1350
|
-
target
|
|
1486
|
+
target,
|
|
1487
|
+
type: call.isMutation ? "mutates" : "reads",
|
|
1488
|
+
layer: "db"
|
|
1489
|
+
});
|
|
1490
|
+
}
|
|
1491
|
+
delete node._dbCalls;
|
|
1492
|
+
}
|
|
1493
|
+
const uiCrossRefs = [];
|
|
1494
|
+
for (const node of uiNodes) {
|
|
1495
|
+
const dbCalls = node._dbCalls;
|
|
1496
|
+
if (!dbCalls) continue;
|
|
1497
|
+
const seenModels = /* @__PURE__ */ new Set();
|
|
1498
|
+
for (const call of dbCalls) {
|
|
1499
|
+
const target = call.kind === "sql" ? call.model : camelToPascal(call.model);
|
|
1500
|
+
if (seenModels.has(target)) continue;
|
|
1501
|
+
seenModels.add(target);
|
|
1502
|
+
uiCrossRefs.push({
|
|
1503
|
+
source: node.id,
|
|
1504
|
+
target,
|
|
1351
1505
|
type: call.isMutation ? "mutates" : "reads",
|
|
1352
1506
|
layer: "db"
|
|
1353
1507
|
});
|
|
1354
1508
|
}
|
|
1355
1509
|
delete node._dbCalls;
|
|
1356
1510
|
}
|
|
1511
|
+
uiCrossRefs.sort((a, b) => a.source.localeCompare(b.source) || a.target.localeCompare(b.target));
|
|
1357
1512
|
const apiNodeIds = new Set(apiNodes.map((n) => n.id));
|
|
1358
1513
|
const apiEdges = [];
|
|
1359
1514
|
const uiOnlyEdges = [];
|
|
@@ -1415,7 +1570,7 @@ function generate(rootDir) {
|
|
|
1415
1570
|
},
|
|
1416
1571
|
nodes: stripLayer(uiNodes),
|
|
1417
1572
|
edges: uiOnlyEdges,
|
|
1418
|
-
cross_refs:
|
|
1573
|
+
cross_refs: uiCrossRefs,
|
|
1419
1574
|
contradictions: [],
|
|
1420
1575
|
warnings: [],
|
|
1421
1576
|
flagged_edges: dedupedFlagged,
|
|
@@ -2239,6 +2394,10 @@ function buildApiPathMap(routes) {
|
|
|
2239
2394
|
const map = /* @__PURE__ */ new Map();
|
|
2240
2395
|
for (const r of routes) {
|
|
2241
2396
|
if (!map.has(r.path)) map.set(r.path, r.nodeId);
|
|
2397
|
+
const trimmed = r.path.replace(/\/\*[^/]+\?$/, "");
|
|
2398
|
+
if (trimmed && trimmed !== r.path && !map.has(trimmed)) {
|
|
2399
|
+
map.set(trimmed, r.nodeId);
|
|
2400
|
+
}
|
|
2242
2401
|
}
|
|
2243
2402
|
return map;
|
|
2244
2403
|
}
|
|
@@ -2261,26 +2420,54 @@ function normalizeFetchUrl(raw) {
|
|
|
2261
2420
|
return { path: s || "/", hadInterpolation };
|
|
2262
2421
|
}
|
|
2263
2422
|
function scoreApiRouteMatch(candidate, known) {
|
|
2264
|
-
if (candidate.length !== known.length) return -1;
|
|
2265
2423
|
let score = 0;
|
|
2266
|
-
|
|
2424
|
+
let i = 0, j = 0;
|
|
2425
|
+
while (i < candidate.length && j < known.length) {
|
|
2267
2426
|
const a = candidate[i];
|
|
2268
|
-
const b = known[
|
|
2427
|
+
const b = known[j];
|
|
2428
|
+
if (b.startsWith("*") && b.endsWith("?")) {
|
|
2429
|
+
score += 1;
|
|
2430
|
+
return score;
|
|
2431
|
+
}
|
|
2432
|
+
if (b.startsWith("*")) {
|
|
2433
|
+
const remaining = candidate.length - i;
|
|
2434
|
+
if (remaining < 1) return -1;
|
|
2435
|
+
score += 1 + remaining;
|
|
2436
|
+
return score;
|
|
2437
|
+
}
|
|
2269
2438
|
if (a === b) {
|
|
2270
2439
|
score += 3;
|
|
2440
|
+
i++;
|
|
2441
|
+
j++;
|
|
2271
2442
|
continue;
|
|
2272
2443
|
}
|
|
2273
2444
|
if (a.startsWith(":") && b.startsWith(":")) {
|
|
2274
2445
|
score += 2;
|
|
2446
|
+
i++;
|
|
2447
|
+
j++;
|
|
2275
2448
|
continue;
|
|
2276
2449
|
}
|
|
2277
2450
|
if (a.startsWith(":") || b.startsWith(":")) {
|
|
2278
2451
|
score += 1;
|
|
2452
|
+
i++;
|
|
2453
|
+
j++;
|
|
2279
2454
|
continue;
|
|
2280
2455
|
}
|
|
2281
2456
|
return -1;
|
|
2282
2457
|
}
|
|
2283
|
-
|
|
2458
|
+
if (i === candidate.length) {
|
|
2459
|
+
while (j < known.length) {
|
|
2460
|
+
const b = known[j];
|
|
2461
|
+
if (b.startsWith("*") && b.endsWith("?")) {
|
|
2462
|
+
score += 1;
|
|
2463
|
+
j++;
|
|
2464
|
+
continue;
|
|
2465
|
+
}
|
|
2466
|
+
return -1;
|
|
2467
|
+
}
|
|
2468
|
+
return score;
|
|
2469
|
+
}
|
|
2470
|
+
return -1;
|
|
2284
2471
|
}
|
|
2285
2472
|
function resolveFetchCall(call, apiPathMap, apiRoutes) {
|
|
2286
2473
|
const raw = call.url;
|
|
@@ -2437,48 +2624,58 @@ var fetchResolverParser = {
|
|
|
2437
2624
|
// src/server/graph/parsers/crosslayer/api-annotations.ts
|
|
2438
2625
|
var import_node_fs8 = require("node:fs");
|
|
2439
2626
|
var import_node_path7 = require("node:path");
|
|
2627
|
+
init_config();
|
|
2440
2628
|
var API_ANNOTATION_RE = /@api\s+(GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS)\s+(\/\S+)/g;
|
|
2441
|
-
function
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
|
|
2446
|
-
const full = (0, import_node_path7.join)(dir, entry.name);
|
|
2447
|
-
if (entry.isDirectory()) {
|
|
2448
|
-
results.push(...walk2(full, exts));
|
|
2449
|
-
} else if (exts.includes((0, import_node_path7.extname)(entry.name))) {
|
|
2450
|
-
results.push(full);
|
|
2451
|
-
}
|
|
2629
|
+
function toNodeId2(srcDir, rootDir, absPath) {
|
|
2630
|
+
const relFromSrc = (0, import_node_path7.relative)(srcDir, absPath).replace(/\\/g, "/");
|
|
2631
|
+
if (relFromSrc.startsWith("..")) {
|
|
2632
|
+
return (0, import_node_path7.relative)(rootDir, absPath).replace(/\\/g, "/");
|
|
2452
2633
|
}
|
|
2453
|
-
return
|
|
2454
|
-
}
|
|
2455
|
-
function toNodeId2(srcDir, absPath) {
|
|
2456
|
-
return (0, import_node_path7.relative)(srcDir, absPath).replace(/\\/g, "/");
|
|
2634
|
+
return relFromSrc;
|
|
2457
2635
|
}
|
|
2458
2636
|
var apiAnnotationsParser = {
|
|
2459
2637
|
id: "api-annotations",
|
|
2460
2638
|
layer: "crosslayer",
|
|
2461
2639
|
concern: "api-binding",
|
|
2462
2640
|
detect(rootDir) {
|
|
2463
|
-
return (
|
|
2641
|
+
return resolveProjectPaths(rootDir, loadConfig(rootDir)) !== null;
|
|
2464
2642
|
},
|
|
2465
2643
|
generate(rootDir, layerOutputs) {
|
|
2466
2644
|
const apiOutput = layerOutputs.get("api");
|
|
2467
2645
|
if (!apiOutput) {
|
|
2468
2646
|
return { cross_refs: [], flagged_edges: [], warnings: [] };
|
|
2469
2647
|
}
|
|
2648
|
+
const paths = resolveProjectPaths(rootDir, loadConfig(rootDir));
|
|
2649
|
+
if (!paths) {
|
|
2650
|
+
return { cross_refs: [], flagged_edges: [], warnings: [] };
|
|
2651
|
+
}
|
|
2470
2652
|
const uiOutput = layerOutputs.get("ui");
|
|
2471
2653
|
const uiNodeIds = new Set(uiOutput?.nodes.map((n) => n.id) ?? []);
|
|
2472
2654
|
const apiRoutes = loadApiRoutesFromOutput(apiOutput);
|
|
2473
2655
|
const apiPathMap = buildApiPathMap(apiRoutes);
|
|
2474
|
-
const srcDir =
|
|
2475
|
-
const
|
|
2656
|
+
const srcDir = paths.srcDir;
|
|
2657
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2658
|
+
const files = [];
|
|
2659
|
+
for (const root of paths.srcRoots) {
|
|
2660
|
+
for (const f of walk(root, [".ts", ".tsx"])) {
|
|
2661
|
+
if (!seen.has(f)) {
|
|
2662
|
+
seen.add(f);
|
|
2663
|
+
files.push(f);
|
|
2664
|
+
}
|
|
2665
|
+
}
|
|
2666
|
+
}
|
|
2667
|
+
for (const conv of paths.conventionFiles) {
|
|
2668
|
+
if (!seen.has(conv)) {
|
|
2669
|
+
seen.add(conv);
|
|
2670
|
+
files.push(conv);
|
|
2671
|
+
}
|
|
2672
|
+
}
|
|
2476
2673
|
const crossRefs = [];
|
|
2477
2674
|
const flaggedEdges = [];
|
|
2478
|
-
const
|
|
2675
|
+
const seenEdge = /* @__PURE__ */ new Set();
|
|
2479
2676
|
for (const absPath of files) {
|
|
2480
2677
|
const content = (0, import_node_fs8.readFileSync)(absPath, "utf-8");
|
|
2481
|
-
const sourceId = toNodeId2(srcDir, absPath);
|
|
2678
|
+
const sourceId = toNodeId2(srcDir, rootDir, absPath);
|
|
2482
2679
|
if (!uiNodeIds.has(sourceId)) continue;
|
|
2483
2680
|
let match;
|
|
2484
2681
|
API_ANNOTATION_RE.lastIndex = 0;
|
|
@@ -2488,8 +2685,8 @@ var apiAnnotationsParser = {
|
|
|
2488
2685
|
const result = resolveUrlPath(urlPath, apiPathMap, apiRoutes);
|
|
2489
2686
|
if (result.kind === "resolved" && result.nodeId) {
|
|
2490
2687
|
const key = `${sourceId}|${result.nodeId}|calls_api`;
|
|
2491
|
-
if (
|
|
2492
|
-
|
|
2688
|
+
if (seenEdge.has(key)) continue;
|
|
2689
|
+
seenEdge.add(key);
|
|
2493
2690
|
crossRefs.push({
|
|
2494
2691
|
source: sourceId,
|
|
2495
2692
|
target: result.nodeId,
|
|
@@ -2524,23 +2721,13 @@ var apiAnnotationsParser = {
|
|
|
2524
2721
|
var import_node_fs9 = require("node:fs");
|
|
2525
2722
|
var import_node_path8 = require("node:path");
|
|
2526
2723
|
init_config();
|
|
2527
|
-
var URL_LITERAL_RE = /['"`](\/
|
|
2528
|
-
function
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
|
|
2533
|
-
const full = (0, import_node_path8.join)(dir, entry.name);
|
|
2534
|
-
if (entry.isDirectory()) {
|
|
2535
|
-
results.push(...walk3(full, exts));
|
|
2536
|
-
} else if (exts.includes((0, import_node_path8.extname)(entry.name))) {
|
|
2537
|
-
results.push(full);
|
|
2538
|
-
}
|
|
2724
|
+
var URL_LITERAL_RE = /['"`](\/[a-zA-Z][^'"`\s]*?)['"`]/g;
|
|
2725
|
+
function toNodeId3(srcDir, rootDir, absPath) {
|
|
2726
|
+
const relFromSrc = (0, import_node_path8.relative)(srcDir, absPath).replace(/\\/g, "/");
|
|
2727
|
+
if (relFromSrc.startsWith("..")) {
|
|
2728
|
+
return (0, import_node_path8.relative)(rootDir, absPath).replace(/\\/g, "/");
|
|
2539
2729
|
}
|
|
2540
|
-
return
|
|
2541
|
-
}
|
|
2542
|
-
function toNodeId3(srcDir, absPath) {
|
|
2543
|
-
return (0, import_node_path8.relative)(srcDir, absPath).replace(/\\/g, "/");
|
|
2730
|
+
return relFromSrc;
|
|
2544
2731
|
}
|
|
2545
2732
|
var urlLiteralScannerParser = {
|
|
2546
2733
|
id: "url-literal-scanner",
|
|
@@ -2561,15 +2748,26 @@ var urlLiteralScannerParser = {
|
|
|
2561
2748
|
const apiPathMap = buildApiPathMap(apiRoutes);
|
|
2562
2749
|
const paths = resolveProjectPaths(rootDir, loadConfig(rootDir));
|
|
2563
2750
|
const srcDir = paths.srcDir;
|
|
2564
|
-
const
|
|
2565
|
-
const files = [
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2751
|
+
const seenFile = /* @__PURE__ */ new Set();
|
|
2752
|
+
const files = [];
|
|
2753
|
+
for (const root of paths.srcRoots) {
|
|
2754
|
+
for (const f of walk(root, [".ts", ".tsx"])) {
|
|
2755
|
+
if (!seenFile.has(f)) {
|
|
2756
|
+
seenFile.add(f);
|
|
2757
|
+
files.push(f);
|
|
2758
|
+
}
|
|
2759
|
+
}
|
|
2760
|
+
}
|
|
2761
|
+
for (const conv of paths.conventionFiles) {
|
|
2762
|
+
if (!seenFile.has(conv)) {
|
|
2763
|
+
seenFile.add(conv);
|
|
2764
|
+
files.push(conv);
|
|
2765
|
+
}
|
|
2766
|
+
}
|
|
2569
2767
|
const crossRefs = [];
|
|
2570
2768
|
const seen = /* @__PURE__ */ new Set();
|
|
2571
2769
|
for (const absPath of files) {
|
|
2572
|
-
const sourceId = toNodeId3(srcDir, absPath);
|
|
2770
|
+
const sourceId = toNodeId3(srcDir, rootDir, absPath);
|
|
2573
2771
|
if (!uiNodeIds.has(sourceId)) continue;
|
|
2574
2772
|
const content = (0, import_node_fs9.readFileSync)(absPath, "utf-8");
|
|
2575
2773
|
let match;
|
|
@@ -2604,6 +2802,7 @@ var urlLiteralScannerParser = {
|
|
|
2604
2802
|
// src/server/graph/parsers/static/static-values.ts
|
|
2605
2803
|
var import_node_fs10 = require("node:fs");
|
|
2606
2804
|
var import_node_path9 = require("node:path");
|
|
2805
|
+
init_config();
|
|
2607
2806
|
var parseCode = null;
|
|
2608
2807
|
function tryLoadTreeSitter() {
|
|
2609
2808
|
if (parseCode) return true;
|
|
@@ -2635,10 +2834,15 @@ function classifyScope(source, model) {
|
|
|
2635
2834
|
function extractEnumValues(rootDir) {
|
|
2636
2835
|
const nodes = [];
|
|
2637
2836
|
const edges = [];
|
|
2638
|
-
const
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2837
|
+
const paths = resolveProjectPaths(rootDir, loadConfig(rootDir));
|
|
2838
|
+
const schemaPaths = [];
|
|
2839
|
+
if (paths?.dbConfig.kind === "prisma" && paths.dbConfig.schemaPath) {
|
|
2840
|
+
schemaPaths.push(paths.dbConfig.schemaPath);
|
|
2841
|
+
schemaPaths.push((0, import_node_path9.join)((0, import_node_path9.dirname)(paths.dbConfig.schemaPath), "schema"));
|
|
2842
|
+
} else {
|
|
2843
|
+
schemaPaths.push((0, import_node_path9.join)(rootDir, "prisma", "schema.prisma"));
|
|
2844
|
+
schemaPaths.push((0, import_node_path9.join)(rootDir, "prisma", "schema"));
|
|
2845
|
+
}
|
|
2642
2846
|
let content = "";
|
|
2643
2847
|
for (const p of schemaPaths) {
|
|
2644
2848
|
if ((0, import_node_fs10.existsSync)(p)) {
|
|
@@ -2722,7 +2926,7 @@ function extractStringArrayFromNode(node) {
|
|
|
2722
2926
|
return values;
|
|
2723
2927
|
}
|
|
2724
2928
|
function findArrayDecl(root, varName) {
|
|
2725
|
-
function
|
|
2929
|
+
function walk2(node) {
|
|
2726
2930
|
if (node.type === "variable_declarator") {
|
|
2727
2931
|
const nameNode = node.childForFieldName("name");
|
|
2728
2932
|
const valueNode = node.childForFieldName("value");
|
|
@@ -2735,12 +2939,12 @@ function findArrayDecl(root, varName) {
|
|
|
2735
2939
|
}
|
|
2736
2940
|
}
|
|
2737
2941
|
for (const child of node.namedChildren) {
|
|
2738
|
-
const found =
|
|
2942
|
+
const found = walk2(child);
|
|
2739
2943
|
if (found) return found;
|
|
2740
2944
|
}
|
|
2741
2945
|
return null;
|
|
2742
2946
|
}
|
|
2743
|
-
return
|
|
2947
|
+
return walk2(root);
|
|
2744
2948
|
}
|
|
2745
2949
|
function extractObjectPropsRegex(objStr) {
|
|
2746
2950
|
const props = {};
|
|
@@ -2803,11 +3007,26 @@ function modelToNodeType(model) {
|
|
|
2803
3007
|
function extractSeedData(rootDir) {
|
|
2804
3008
|
const nodes = [];
|
|
2805
3009
|
const edges = [];
|
|
2806
|
-
const
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
(0, import_node_path9.join)(
|
|
2810
|
-
|
|
3010
|
+
const paths = resolveProjectPaths(rootDir, loadConfig(rootDir));
|
|
3011
|
+
const candidates = [];
|
|
3012
|
+
if (paths?.dbDir) {
|
|
3013
|
+
candidates.push((0, import_node_path9.join)(paths.dbDir, "seed.ts"));
|
|
3014
|
+
candidates.push((0, import_node_path9.join)(paths.dbDir, "seed.js"));
|
|
3015
|
+
} else {
|
|
3016
|
+
candidates.push((0, import_node_path9.join)(rootDir, "prisma", "seed.ts"));
|
|
3017
|
+
candidates.push((0, import_node_path9.join)(rootDir, "prisma", "seed.js"));
|
|
3018
|
+
}
|
|
3019
|
+
const baseRoots = paths?.srcRoots ?? [(0, import_node_path9.join)(rootDir, "src")];
|
|
3020
|
+
for (const root of baseRoots) {
|
|
3021
|
+
candidates.push((0, import_node_path9.join)(root, "server", "lib", "system-tags.ts"));
|
|
3022
|
+
}
|
|
3023
|
+
const seedFiles = candidates.filter((p) => {
|
|
3024
|
+
try {
|
|
3025
|
+
return (0, import_node_fs10.existsSync)(p) && (0, import_node_fs10.statSync)(p).isFile();
|
|
3026
|
+
} catch {
|
|
3027
|
+
return false;
|
|
3028
|
+
}
|
|
3029
|
+
});
|
|
2811
3030
|
const useTreeSitter = tryLoadTreeSitter();
|
|
2812
3031
|
for (const filePath of seedFiles) {
|
|
2813
3032
|
const content = (0, import_node_fs10.readFileSync)(filePath, "utf-8");
|
|
@@ -2917,9 +3136,20 @@ function walkDir(dir, exts) {
|
|
|
2917
3136
|
}
|
|
2918
3137
|
function extractConstants(rootDir) {
|
|
2919
3138
|
const nodes = [];
|
|
2920
|
-
const
|
|
2921
|
-
|
|
2922
|
-
|
|
3139
|
+
const paths = resolveProjectPaths(rootDir, loadConfig(rootDir));
|
|
3140
|
+
const roots = paths?.srcRoots ?? [(0, import_node_path9.join)(rootDir, "src")];
|
|
3141
|
+
const seenFile = /* @__PURE__ */ new Set();
|
|
3142
|
+
const allFiles = [];
|
|
3143
|
+
for (const root of roots) {
|
|
3144
|
+
for (const f of walkDir(root, [".ts", ".tsx"])) {
|
|
3145
|
+
if (!seenFile.has(f)) {
|
|
3146
|
+
seenFile.add(f);
|
|
3147
|
+
allFiles.push(f);
|
|
3148
|
+
}
|
|
3149
|
+
}
|
|
3150
|
+
}
|
|
3151
|
+
if (allFiles.length === 0) return { nodes };
|
|
3152
|
+
for (const filePath of allFiles) {
|
|
2923
3153
|
const content = (0, import_node_fs10.readFileSync)(filePath, "utf-8");
|
|
2924
3154
|
const relPath = (0, import_node_path9.relative)(rootDir, filePath);
|
|
2925
3155
|
const constArrayRe = /export\s+const\s+([A-Z][A-Z_0-9]+)\s*(?::[^=]+)?\s*=\s*\[/g;
|
|
@@ -2954,6 +3184,13 @@ function extractConstants(rootDir) {
|
|
|
2954
3184
|
return { nodes };
|
|
2955
3185
|
}
|
|
2956
3186
|
function detect4(rootDir) {
|
|
3187
|
+
const paths = resolveProjectPaths(rootDir, loadConfig(rootDir));
|
|
3188
|
+
if (paths?.dbConfig.kind === "prisma" && paths.dbConfig.schemaPath && (0, import_node_fs10.existsSync)(paths.dbConfig.schemaPath)) {
|
|
3189
|
+
return true;
|
|
3190
|
+
}
|
|
3191
|
+
if (paths?.dbDir) {
|
|
3192
|
+
if ((0, import_node_fs10.existsSync)((0, import_node_path9.join)(paths.dbDir, "seed.ts")) || (0, import_node_fs10.existsSync)((0, import_node_path9.join)(paths.dbDir, "seed.js"))) return true;
|
|
3193
|
+
}
|
|
2957
3194
|
return (0, import_node_fs10.existsSync)((0, import_node_path9.join)(rootDir, "prisma", "schema.prisma")) || (0, import_node_fs10.existsSync)((0, import_node_path9.join)(rootDir, "prisma", "seed.ts"));
|
|
2958
3195
|
}
|
|
2959
3196
|
function generate4(rootDir) {
|
|
@@ -3173,13 +3410,22 @@ var staticRefScannerParser = {
|
|
|
3173
3410
|
const paths = resolveProjectPaths(rootDir, loadConfig(rootDir));
|
|
3174
3411
|
if (!paths) return { cross_refs: [], flagged_edges: [], warnings: [] };
|
|
3175
3412
|
const srcDir = paths.srcDir;
|
|
3176
|
-
const
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3413
|
+
const seenFile = /* @__PURE__ */ new Set();
|
|
3414
|
+
const files = [];
|
|
3415
|
+
for (const root of paths.srcRoots) {
|
|
3416
|
+
for (const f of walkWithIgnore(root, [".ts", ".tsx"])) {
|
|
3417
|
+
if (!seenFile.has(f)) {
|
|
3418
|
+
seenFile.add(f);
|
|
3419
|
+
files.push(f);
|
|
3420
|
+
}
|
|
3421
|
+
}
|
|
3422
|
+
}
|
|
3423
|
+
for (const conv of paths.conventionFiles) {
|
|
3424
|
+
if (!seenFile.has(conv)) {
|
|
3425
|
+
seenFile.add(conv);
|
|
3426
|
+
files.push(conv);
|
|
3427
|
+
}
|
|
3428
|
+
}
|
|
3183
3429
|
const uiOutput = layerOutputs.get("ui");
|
|
3184
3430
|
const apiOutput = layerOutputs.get("api");
|
|
3185
3431
|
const uiNodeIds = new Set(uiOutput?.nodes.map((n) => n.id) ?? []);
|
|
@@ -3196,7 +3442,8 @@ var staticRefScannerParser = {
|
|
|
3196
3442
|
const seen = /* @__PURE__ */ new Set();
|
|
3197
3443
|
let filesScanned = 0;
|
|
3198
3444
|
for (const absPath of files) {
|
|
3199
|
-
const
|
|
3445
|
+
const relFromSrc = (0, import_node_path10.relative)(srcDir, absPath).replace(/\\/g, "/");
|
|
3446
|
+
const sourceId = relFromSrc.startsWith("..") ? (0, import_node_path10.relative)(rootDir, absPath).replace(/\\/g, "/") : relFromSrc;
|
|
3200
3447
|
const sourceLayer = uiNodeIds.has(sourceId) ? "ui" : apiNodeIds.has(sourceId) ? "api" : null;
|
|
3201
3448
|
if (!sourceLayer) continue;
|
|
3202
3449
|
const content = (0, import_node_fs11.readFileSync)(absPath, "utf-8");
|
|
@@ -3657,6 +3904,9 @@ var GENERIC_ROLE_NAMES_BUILTIN = /* @__PURE__ */ new Set([
|
|
|
3657
3904
|
"__tests__",
|
|
3658
3905
|
"spec",
|
|
3659
3906
|
"specs",
|
|
3907
|
+
"scripts",
|
|
3908
|
+
"bin",
|
|
3909
|
+
"tools",
|
|
3660
3910
|
// Go
|
|
3661
3911
|
"cmd",
|
|
3662
3912
|
"pkg",
|
|
@@ -3744,13 +3994,17 @@ function isTrivialGroup(name, extraTrivial) {
|
|
|
3744
3994
|
function normalizeGroupName(name) {
|
|
3745
3995
|
return name.toLowerCase().replace(/-pages?$/, "").replace(/-layout$/, "").replace(/-wrapper$/, "");
|
|
3746
3996
|
}
|
|
3747
|
-
function extractModuleFromPath(id, extraTrivial, extraSkipSegments) {
|
|
3997
|
+
function extractModuleFromPath(id, extraTrivial, extraSkipSegments, extraGenericRoles) {
|
|
3748
3998
|
const segments = id.split("/");
|
|
3749
3999
|
const routeGroups = extractRouteGroups(id);
|
|
3750
4000
|
const skipSegments = new Set(SKIP_SEGMENTS_BUILTIN);
|
|
4001
|
+
for (const r of GENERIC_ROLE_NAMES_BUILTIN) skipSegments.add(r);
|
|
3751
4002
|
if (extraSkipSegments) {
|
|
3752
4003
|
for (const s of extraSkipSegments) skipSegments.add(s);
|
|
3753
4004
|
}
|
|
4005
|
+
if (extraGenericRoles) {
|
|
4006
|
+
for (const r of extraGenericRoles) skipSegments.add(r);
|
|
4007
|
+
}
|
|
3754
4008
|
const moduleGroups = routeGroups.filter((g) => !isTrivialGroup(g, extraTrivial)).map(normalizeGroupName);
|
|
3755
4009
|
if (moduleGroups.length > 0) {
|
|
3756
4010
|
return moduleGroups[moduleGroups.length - 1];
|
|
@@ -4280,7 +4534,7 @@ function checkDeadScreens(rootDir) {
|
|
|
4280
4534
|
const pages = ui.nodes.filter((n) => n.type === "page" || n.type === "layout");
|
|
4281
4535
|
const navTargets = /* @__PURE__ */ new Set();
|
|
4282
4536
|
for (const e of ui.edges) {
|
|
4283
|
-
if (e.type === "navigates" || e.type === "renders" || e.type === "imports") {
|
|
4537
|
+
if (e.type === "navigates" || e.type === "renders" || e.type === "imports" || e.type === "wraps") {
|
|
4284
4538
|
navTargets.add(e.target);
|
|
4285
4539
|
}
|
|
4286
4540
|
}
|
|
@@ -4290,13 +4544,15 @@ function checkDeadScreens(rootDir) {
|
|
|
4290
4544
|
for (const page of pages) {
|
|
4291
4545
|
if (page.id.endsWith("layout.tsx") && page.id.split("/").length <= 2) continue;
|
|
4292
4546
|
if (["error.tsx", "loading.tsx", "not-found.tsx", "template.tsx"].some((s) => page.id.endsWith(s))) continue;
|
|
4547
|
+
const route = page.route;
|
|
4548
|
+
if (route === "/" || route === "") continue;
|
|
4293
4549
|
if (!navTargets.has(page.id)) {
|
|
4294
4550
|
findings.push({
|
|
4295
4551
|
id: `dead:${page.id}`,
|
|
4296
4552
|
severity: "info",
|
|
4297
4553
|
category: "dead_screens",
|
|
4298
4554
|
title: page.name,
|
|
4299
|
-
detail: `Page "${page.id}" has no incoming navigation, render, or
|
|
4555
|
+
detail: `Page "${page.id}" has no incoming navigation, render, import, or layout-wrap edges.`,
|
|
4300
4556
|
file: page.id
|
|
4301
4557
|
});
|
|
4302
4558
|
}
|