@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
package/dist/server/cli.js
CHANGED
|
@@ -3960,6 +3960,12 @@ function parseFileTS(absPath) {
|
|
|
3960
3960
|
if (linkTemplate) {
|
|
3961
3961
|
navigations.push({ kind: "link-href", target: linkTemplate, isTemplate: true });
|
|
3962
3962
|
}
|
|
3963
|
+
if (caps["nav.redirect.literal"]) {
|
|
3964
|
+
navigations.push({ kind: "router-replace", target: caps["nav.redirect.literal"], isTemplate: false });
|
|
3965
|
+
}
|
|
3966
|
+
if (caps["nav.redirect.template"]) {
|
|
3967
|
+
navigations.push({ kind: "router-replace", target: caps["nav.redirect.template"], isTemplate: true });
|
|
3968
|
+
}
|
|
3963
3969
|
if (caps["nav.window.literal"]) {
|
|
3964
3970
|
navigations.push({ kind: "window-location", target: caps["nav.window.literal"], isTemplate: false });
|
|
3965
3971
|
}
|
|
@@ -4038,15 +4044,25 @@ function extractDbCallsTS(absPath) {
|
|
|
4038
4044
|
const seen = /* @__PURE__ */ new Set();
|
|
4039
4045
|
for (const m of matches) {
|
|
4040
4046
|
const caps = captureMap(m);
|
|
4047
|
+
const sbTable = caps["sb.table"];
|
|
4048
|
+
const sbMethod = caps["sb.method"];
|
|
4049
|
+
if (sbTable && sbMethod) {
|
|
4050
|
+
const key2 = `sql:${sbTable}.${sbMethod}`;
|
|
4051
|
+
if (seen.has(key2)) continue;
|
|
4052
|
+
seen.add(key2);
|
|
4053
|
+
const isMutation = SUPABASE_MUTATION_METHODS_BUILTIN.has(sbMethod) || extraMutationMethods.includes(sbMethod);
|
|
4054
|
+
calls.push({ model: sbTable, method: sbMethod, isMutation, kind: "sql" });
|
|
4055
|
+
continue;
|
|
4056
|
+
}
|
|
4041
4057
|
const identifier = caps["db.identifier"];
|
|
4042
4058
|
const model = caps["db.model"];
|
|
4043
4059
|
const method = caps["db.method"];
|
|
4044
4060
|
if (!identifier || !model || !method) continue;
|
|
4045
4061
|
if (!dbIdentifiers.has(identifier)) continue;
|
|
4046
|
-
const key =
|
|
4062
|
+
const key = `orm:${model}.${method}`;
|
|
4047
4063
|
if (seen.has(key)) continue;
|
|
4048
4064
|
seen.add(key);
|
|
4049
|
-
calls.push({ model, method, isMutation: getMutationMethods().has(method) });
|
|
4065
|
+
calls.push({ model, method, isMutation: getMutationMethods().has(method), kind: "orm" });
|
|
4050
4066
|
}
|
|
4051
4067
|
return calls;
|
|
4052
4068
|
}
|
|
@@ -4060,6 +4076,9 @@ function classifyFile(absPath) {
|
|
|
4060
4076
|
const captures = classifyQuery.captures(root);
|
|
4061
4077
|
const capNames = new Set(captures.map((c) => c.name));
|
|
4062
4078
|
if (capNames.has("http_export") || capNames.has("http_export_fn")) return "endpoint";
|
|
4079
|
+
if (capNames.has("use_server_directive") && fileName !== "page.tsx" && fileName !== "layout.tsx") {
|
|
4080
|
+
return "server-action";
|
|
4081
|
+
}
|
|
4063
4082
|
if (fileName === "page.tsx" && capNames.has("has_jsx")) return "page";
|
|
4064
4083
|
if (fileName === "layout.tsx" && capNames.has("has_jsx")) return "layout";
|
|
4065
4084
|
if (capNames.has("has_create_context") || capNames.has("has_create_context_bare")) return "context";
|
|
@@ -4080,6 +4099,36 @@ function extractAuthWrappersTS(absPath) {
|
|
|
4080
4099
|
wrappers.add(caps["wrapper.fn_name"]);
|
|
4081
4100
|
}
|
|
4082
4101
|
}
|
|
4102
|
+
const inlineHelpers = /* @__PURE__ */ new Set();
|
|
4103
|
+
for (const stmt of childrenOfType(root, "import_statement")) {
|
|
4104
|
+
const sourceNode = childOfType(stmt, "string");
|
|
4105
|
+
const frag = sourceNode ? childOfType(sourceNode, "string_fragment") : void 0;
|
|
4106
|
+
if (!frag) continue;
|
|
4107
|
+
const provider = INLINE_AUTH_IMPORTS.find((p) => p.module.test(frag.text));
|
|
4108
|
+
if (!provider) continue;
|
|
4109
|
+
const clause = childOfType(stmt, "import_clause");
|
|
4110
|
+
if (!clause) continue;
|
|
4111
|
+
const named = childOfType(clause, "named_imports");
|
|
4112
|
+
if (!named) continue;
|
|
4113
|
+
for (const specNode of childrenOfType(named, "import_specifier")) {
|
|
4114
|
+
const ids = childrenOfType(specNode, "identifier");
|
|
4115
|
+
const importedName = ids[0]?.text;
|
|
4116
|
+
const localName = ids[ids.length - 1]?.text;
|
|
4117
|
+
if (importedName && provider.helpers.includes(importedName) && localName) {
|
|
4118
|
+
inlineHelpers.add(localName);
|
|
4119
|
+
}
|
|
4120
|
+
}
|
|
4121
|
+
}
|
|
4122
|
+
if (inlineHelpers.size > 0) {
|
|
4123
|
+
const text = root.text;
|
|
4124
|
+
for (const name of inlineHelpers) {
|
|
4125
|
+
const re = new RegExp(`\\b${name}\\s*\\(`);
|
|
4126
|
+
if (re.test(text)) {
|
|
4127
|
+
wrappers.add("inline");
|
|
4128
|
+
break;
|
|
4129
|
+
}
|
|
4130
|
+
}
|
|
4131
|
+
}
|
|
4083
4132
|
return wrappers;
|
|
4084
4133
|
}
|
|
4085
4134
|
function trunc(s, max = 120) {
|
|
@@ -4238,7 +4287,7 @@ function extractDeep(absPath) {
|
|
|
4238
4287
|
}
|
|
4239
4288
|
return { elements, stateVars, conditions, variables, responses, params };
|
|
4240
4289
|
}
|
|
4241
|
-
var import_node_fs4, import_node_path4, tsxLanguage, parserInstance, initPromise, initialized, queriesDir, queryCache, PRISMA_MUTATION_METHODS_BUILTIN, DB_IDENTIFIERS_FALLBACK, extraDbIdentifiers, extraMutationMethods;
|
|
4290
|
+
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;
|
|
4242
4291
|
var init_ts_extractor = __esm({
|
|
4243
4292
|
"src/server/graph/core/ts-extractor.ts"() {
|
|
4244
4293
|
"use strict";
|
|
@@ -4262,9 +4311,21 @@ var init_ts_extractor = __esm({
|
|
|
4262
4311
|
"delete",
|
|
4263
4312
|
"deleteMany"
|
|
4264
4313
|
];
|
|
4314
|
+
SUPABASE_MUTATION_METHODS_BUILTIN = /* @__PURE__ */ new Set([
|
|
4315
|
+
"insert",
|
|
4316
|
+
"update",
|
|
4317
|
+
"delete",
|
|
4318
|
+
"upsert"
|
|
4319
|
+
]);
|
|
4265
4320
|
DB_IDENTIFIERS_FALLBACK = ["db", "prisma"];
|
|
4266
4321
|
extraDbIdentifiers = [];
|
|
4267
4322
|
extraMutationMethods = [];
|
|
4323
|
+
INLINE_AUTH_IMPORTS = [
|
|
4324
|
+
{ module: /^@clerk\/nextjs(\/server)?$/, helpers: ["auth", "currentUser"] },
|
|
4325
|
+
{ module: /^next-auth(\/.+)?$/, helpers: ["auth", "getServerSession"] },
|
|
4326
|
+
{ module: /^@auth\//, helpers: ["auth"] },
|
|
4327
|
+
{ module: /^@supabase\/auth-helpers/, helpers: ["createServerClient"] }
|
|
4328
|
+
];
|
|
4268
4329
|
}
|
|
4269
4330
|
});
|
|
4270
4331
|
|
|
@@ -7217,6 +7278,7 @@ init_ts_extractor();
|
|
|
7217
7278
|
var HTTP_METHODS = /* @__PURE__ */ new Set(["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"]);
|
|
7218
7279
|
var CLASSIFICATION_TO_LAYER = {
|
|
7219
7280
|
endpoint: "api",
|
|
7281
|
+
"server-action": "api",
|
|
7220
7282
|
page: "ui",
|
|
7221
7283
|
layout: "ui",
|
|
7222
7284
|
component: "ui",
|
|
@@ -7315,6 +7377,8 @@ function extractRoute(id) {
|
|
|
7315
7377
|
if (!id.endsWith("/page.tsx")) return null;
|
|
7316
7378
|
let route = id.replace(/^app\//, "/").replace(/\/page\.tsx$/, "");
|
|
7317
7379
|
route = route.replace(/\/\([^)]+\)/g, "");
|
|
7380
|
+
route = route.replace(/\[\[\.\.\.([^\]]+)\]\]/g, "*$1?");
|
|
7381
|
+
route = route.replace(/\[\.\.\.([^\]]+)\]/g, "*$1");
|
|
7318
7382
|
route = route.replace(/\[([^\]]+)\]/g, ":$1");
|
|
7319
7383
|
route = route.replace(/\/+/g, "/");
|
|
7320
7384
|
if (!route.startsWith("/")) route = "/" + route;
|
|
@@ -7326,6 +7390,7 @@ function nameFromFilename(absPath) {
|
|
|
7326
7390
|
function filePathToAppRoute(appDir, absPath) {
|
|
7327
7391
|
let route = ("/" + (0, import_node_path5.relative)(appDir, absPath).replace(/\\/g, "/")).replace(/\/route\.tsx?$/, "");
|
|
7328
7392
|
route = route.replace(/\/\([^)]+\)/g, "");
|
|
7393
|
+
route = route.replace(/\[\[\.\.\.([^\]]+)\]\]/g, "*$1?");
|
|
7329
7394
|
route = route.replace(/\[\.\.\.([^\]]+)\]/g, "*$1");
|
|
7330
7395
|
route = route.replace(/\[([^\]]+)\]/g, ":$1");
|
|
7331
7396
|
route = route.replace(/\/+/g, "/");
|
|
@@ -7374,25 +7439,52 @@ function resolveTemplateLiteralRoute(template, routeToNodeId) {
|
|
|
7374
7439
|
function routeMatchScore(candidate, known) {
|
|
7375
7440
|
const segsA = candidate.split("/");
|
|
7376
7441
|
const segsB = known.split("/");
|
|
7377
|
-
if (segsA.length !== segsB.length) return -1;
|
|
7378
7442
|
let score = 0;
|
|
7379
|
-
|
|
7380
|
-
|
|
7443
|
+
let i = 0, j = 0;
|
|
7444
|
+
while (i < segsA.length && j < segsB.length) {
|
|
7445
|
+
const a = segsA[i], b = segsB[j];
|
|
7446
|
+
if (b.startsWith("*") && b.endsWith("?")) {
|
|
7447
|
+
score += 1;
|
|
7448
|
+
return score;
|
|
7449
|
+
}
|
|
7450
|
+
if (b.startsWith("*")) {
|
|
7451
|
+
const remaining = segsA.length - i;
|
|
7452
|
+
if (remaining < 1) return -1;
|
|
7453
|
+
score += 1 + remaining;
|
|
7454
|
+
return score;
|
|
7455
|
+
}
|
|
7381
7456
|
if (a === b) {
|
|
7382
7457
|
score += 3;
|
|
7458
|
+
i++;
|
|
7459
|
+
j++;
|
|
7383
7460
|
continue;
|
|
7384
7461
|
}
|
|
7385
7462
|
if (a.startsWith(":") && b.startsWith(":")) {
|
|
7386
7463
|
score += 2;
|
|
7464
|
+
i++;
|
|
7465
|
+
j++;
|
|
7387
7466
|
continue;
|
|
7388
7467
|
}
|
|
7389
7468
|
if (a.startsWith(":") || b.startsWith(":")) {
|
|
7390
|
-
|
|
7469
|
+
i++;
|
|
7470
|
+
j++;
|
|
7391
7471
|
continue;
|
|
7392
7472
|
}
|
|
7393
7473
|
return -1;
|
|
7394
7474
|
}
|
|
7395
|
-
|
|
7475
|
+
if (i === segsA.length) {
|
|
7476
|
+
while (j < segsB.length) {
|
|
7477
|
+
const b = segsB[j];
|
|
7478
|
+
if (b.startsWith("*") && b.endsWith("?")) {
|
|
7479
|
+
score += 1;
|
|
7480
|
+
j++;
|
|
7481
|
+
continue;
|
|
7482
|
+
}
|
|
7483
|
+
return -1;
|
|
7484
|
+
}
|
|
7485
|
+
return score;
|
|
7486
|
+
}
|
|
7487
|
+
return -1;
|
|
7396
7488
|
}
|
|
7397
7489
|
function templateToRoute(template) {
|
|
7398
7490
|
return template.replace(/\$\{([^}]+)\}/g, (_, expr) => {
|
|
@@ -7427,7 +7519,7 @@ function extractEdges(srcDir, rootDir, absPath, sourceId, parsed, nodeIdSet, bar
|
|
|
7427
7519
|
edges.push(edge);
|
|
7428
7520
|
}
|
|
7429
7521
|
function edgeTypeFor(isTypeOnlyImport, importedNames) {
|
|
7430
|
-
if (isTypeOnlyImport) return "
|
|
7522
|
+
if (isTypeOnlyImport) return "imports_type";
|
|
7431
7523
|
const anyRendered = importedNames.some((n) => parsed.jsxElements.has(n));
|
|
7432
7524
|
if (anyRendered) return "renders";
|
|
7433
7525
|
return "imports";
|
|
@@ -7458,7 +7550,8 @@ function extractEdges(srcDir, rootDir, absPath, sourceId, parsed, nodeIdSet, bar
|
|
|
7458
7550
|
if (resolved) {
|
|
7459
7551
|
const targetId = toNodeId(srcDir, rootDir, resolved);
|
|
7460
7552
|
if (nodeIdSet.has(targetId) && !targetId.endsWith("/index.ts") && !targetId.endsWith("/index.tsx")) {
|
|
7461
|
-
|
|
7553
|
+
const allType = isTypeOnly || names.length > 0 && names.every((n) => typeNames.has(n));
|
|
7554
|
+
addEdge(targetId, edgeTypeFor(allType, names));
|
|
7462
7555
|
}
|
|
7463
7556
|
}
|
|
7464
7557
|
}
|
|
@@ -7467,7 +7560,8 @@ function extractEdges(srcDir, rootDir, absPath, sourceId, parsed, nodeIdSet, bar
|
|
|
7467
7560
|
if (resolved) {
|
|
7468
7561
|
const targetId = toNodeId(srcDir, rootDir, resolved);
|
|
7469
7562
|
if (nodeIdSet.has(targetId) && !targetId.endsWith("/index.ts") && !targetId.endsWith("/index.tsx")) {
|
|
7470
|
-
|
|
7563
|
+
const allType = isTypeOnly || names.length > 0 && names.every((n) => typeNames.has(n));
|
|
7564
|
+
addEdge(targetId, edgeTypeFor(allType, names));
|
|
7471
7565
|
}
|
|
7472
7566
|
}
|
|
7473
7567
|
}
|
|
@@ -7552,26 +7646,34 @@ function generate(rootDir) {
|
|
|
7552
7646
|
const layer = CLASSIFICATION_TO_LAYER[type] ?? "ui";
|
|
7553
7647
|
nodeIdSet.add(id);
|
|
7554
7648
|
if (layer === "api") {
|
|
7555
|
-
const methods = [];
|
|
7556
|
-
for (const exp of parsed.exports) {
|
|
7557
|
-
if (HTTP_METHODS.has(exp)) methods.push(exp);
|
|
7558
|
-
}
|
|
7559
7649
|
const dbCalls = extractDbCallsTS(absPath);
|
|
7560
7650
|
const authWrappers = extractAuthWrappersTS(absPath);
|
|
7561
7651
|
const deep = extractDeep(absPath);
|
|
7562
|
-
const routePath = filePathToAppRoute(paths.appDir, absPath);
|
|
7563
7652
|
const mutations = dbCalls.filter((c) => c.isMutation);
|
|
7564
7653
|
const mutates = mutations.length > 0;
|
|
7565
7654
|
const authStrategy = [...authWrappers];
|
|
7655
|
+
const isServerAction = type === "server-action";
|
|
7656
|
+
const methods = [];
|
|
7657
|
+
if (!isServerAction) {
|
|
7658
|
+
for (const exp of parsed.exports) {
|
|
7659
|
+
if (HTTP_METHODS.has(exp)) methods.push(exp);
|
|
7660
|
+
}
|
|
7661
|
+
}
|
|
7662
|
+
const routePath = isServerAction ? null : filePathToAppRoute(paths.appDir, absPath);
|
|
7663
|
+
const actions = isServerAction ? parsed.exports.filter((e) => !HTTP_METHODS.has(e)) : [];
|
|
7566
7664
|
apiNodes.push({
|
|
7567
7665
|
id,
|
|
7568
|
-
type: "endpoint",
|
|
7569
|
-
name:
|
|
7666
|
+
type: isServerAction ? "server-action" : "endpoint",
|
|
7667
|
+
// For HTTP routes: name = URL path. For Server Actions: name = file id +
|
|
7668
|
+
// exported action names — the callable surface, not a URL.
|
|
7669
|
+
name: isServerAction ? actions.length > 0 ? `${id} (${actions.join(", ")})` : id : routePath,
|
|
7570
7670
|
layer: "api",
|
|
7571
7671
|
path: routePath,
|
|
7672
|
+
// null for Server Actions
|
|
7572
7673
|
methods,
|
|
7573
7674
|
handler: id,
|
|
7574
7675
|
mutates,
|
|
7676
|
+
...isServerAction ? { actions } : {},
|
|
7575
7677
|
auth: authStrategy.length > 0 ? authStrategy : ["public"],
|
|
7576
7678
|
db_models: [...new Set(dbCalls.map((c) => c.model))],
|
|
7577
7679
|
db_operations: [...new Set(dbCalls.map((c) => `${c.model}.${c.method}`))],
|
|
@@ -7585,6 +7687,8 @@ function generate(rootDir) {
|
|
|
7585
7687
|
} else {
|
|
7586
7688
|
const route = extractRoute(id);
|
|
7587
7689
|
const deep = extractDeep(absPath);
|
|
7690
|
+
const dbCalls = extractDbCallsTS(absPath);
|
|
7691
|
+
const authWrappers = type === "page" || type === "layout" ? [...extractAuthWrappersTS(absPath)] : [];
|
|
7588
7692
|
uiNodes.push({
|
|
7589
7693
|
id,
|
|
7590
7694
|
type,
|
|
@@ -7595,9 +7699,17 @@ function generate(rootDir) {
|
|
|
7595
7699
|
elements: deep.elements,
|
|
7596
7700
|
stateVars: deep.stateVars,
|
|
7597
7701
|
conditions: deep.conditions,
|
|
7598
|
-
variables: deep.variables
|
|
7702
|
+
variables: deep.variables,
|
|
7703
|
+
...authWrappers.length > 0 ? { auth: authWrappers } : {},
|
|
7704
|
+
...dbCalls.length > 0 ? { _dbCalls: dbCalls } : {}
|
|
7599
7705
|
});
|
|
7600
|
-
if (route)
|
|
7706
|
+
if (route) {
|
|
7707
|
+
routeToNodeId.set(route, id);
|
|
7708
|
+
const trimmed = route.replace(/\/\*[^/]+\?$/, "");
|
|
7709
|
+
if (trimmed && trimmed !== route && !routeToNodeId.has(trimmed)) {
|
|
7710
|
+
routeToNodeId.set(trimmed, id);
|
|
7711
|
+
}
|
|
7712
|
+
}
|
|
7601
7713
|
}
|
|
7602
7714
|
}
|
|
7603
7715
|
const uiEdges = [];
|
|
@@ -7619,6 +7731,29 @@ function generate(rootDir) {
|
|
|
7619
7731
|
uiEdges.push(...edges);
|
|
7620
7732
|
uiFlagged.push(...flagged);
|
|
7621
7733
|
}
|
|
7734
|
+
const layoutsById = /* @__PURE__ */ new Set();
|
|
7735
|
+
for (const n of uiNodes) {
|
|
7736
|
+
if (n.type === "layout") layoutsById.add(n.id);
|
|
7737
|
+
}
|
|
7738
|
+
function findClosestLayout(pageId) {
|
|
7739
|
+
let dir = pageId.replace(/\/page\.tsx$/, "");
|
|
7740
|
+
while (dir.length > 0) {
|
|
7741
|
+
const candidate = `${dir}/layout.tsx`;
|
|
7742
|
+
if (layoutsById.has(candidate)) return candidate;
|
|
7743
|
+
const slash = dir.lastIndexOf("/");
|
|
7744
|
+
if (slash < 0) break;
|
|
7745
|
+
dir = dir.slice(0, slash);
|
|
7746
|
+
}
|
|
7747
|
+
if (layoutsById.has("app/layout.tsx")) return "app/layout.tsx";
|
|
7748
|
+
return null;
|
|
7749
|
+
}
|
|
7750
|
+
for (const n of uiNodes) {
|
|
7751
|
+
if (n.type !== "page") continue;
|
|
7752
|
+
const layoutId = findClosestLayout(n.id);
|
|
7753
|
+
if (layoutId && layoutId !== n.id) {
|
|
7754
|
+
uiEdges.push({ source: layoutId, target: n.id, type: "wraps" });
|
|
7755
|
+
}
|
|
7756
|
+
}
|
|
7622
7757
|
const fetchCallEntries = [];
|
|
7623
7758
|
for (const absPath of fileSet) {
|
|
7624
7759
|
const sourceId = toNodeId(srcDir, rootDir, absPath);
|
|
@@ -7700,17 +7835,37 @@ function generate(rootDir) {
|
|
|
7700
7835
|
if (!dbCalls) continue;
|
|
7701
7836
|
const seenModels = /* @__PURE__ */ new Set();
|
|
7702
7837
|
for (const call of dbCalls) {
|
|
7703
|
-
|
|
7704
|
-
seenModels.
|
|
7838
|
+
const target = call.kind === "sql" ? call.model : camelToPascal(call.model);
|
|
7839
|
+
if (seenModels.has(target)) continue;
|
|
7840
|
+
seenModels.add(target);
|
|
7705
7841
|
apiCrossRefs.push({
|
|
7706
7842
|
source: node.id,
|
|
7707
|
-
target
|
|
7843
|
+
target,
|
|
7844
|
+
type: call.isMutation ? "mutates" : "reads",
|
|
7845
|
+
layer: "db"
|
|
7846
|
+
});
|
|
7847
|
+
}
|
|
7848
|
+
delete node._dbCalls;
|
|
7849
|
+
}
|
|
7850
|
+
const uiCrossRefs = [];
|
|
7851
|
+
for (const node of uiNodes) {
|
|
7852
|
+
const dbCalls = node._dbCalls;
|
|
7853
|
+
if (!dbCalls) continue;
|
|
7854
|
+
const seenModels = /* @__PURE__ */ new Set();
|
|
7855
|
+
for (const call of dbCalls) {
|
|
7856
|
+
const target = call.kind === "sql" ? call.model : camelToPascal(call.model);
|
|
7857
|
+
if (seenModels.has(target)) continue;
|
|
7858
|
+
seenModels.add(target);
|
|
7859
|
+
uiCrossRefs.push({
|
|
7860
|
+
source: node.id,
|
|
7861
|
+
target,
|
|
7708
7862
|
type: call.isMutation ? "mutates" : "reads",
|
|
7709
7863
|
layer: "db"
|
|
7710
7864
|
});
|
|
7711
7865
|
}
|
|
7712
7866
|
delete node._dbCalls;
|
|
7713
7867
|
}
|
|
7868
|
+
uiCrossRefs.sort((a, b) => a.source.localeCompare(b.source) || a.target.localeCompare(b.target));
|
|
7714
7869
|
const apiNodeIds = new Set(apiNodes.map((n) => n.id));
|
|
7715
7870
|
const apiEdges = [];
|
|
7716
7871
|
const uiOnlyEdges = [];
|
|
@@ -7772,7 +7927,7 @@ function generate(rootDir) {
|
|
|
7772
7927
|
},
|
|
7773
7928
|
nodes: stripLayer(uiNodes),
|
|
7774
7929
|
edges: uiOnlyEdges,
|
|
7775
|
-
cross_refs:
|
|
7930
|
+
cross_refs: uiCrossRefs,
|
|
7776
7931
|
contradictions: [],
|
|
7777
7932
|
warnings: [],
|
|
7778
7933
|
flagged_edges: dedupedFlagged,
|
|
@@ -8596,6 +8751,10 @@ function buildApiPathMap(routes) {
|
|
|
8596
8751
|
const map = /* @__PURE__ */ new Map();
|
|
8597
8752
|
for (const r of routes) {
|
|
8598
8753
|
if (!map.has(r.path)) map.set(r.path, r.nodeId);
|
|
8754
|
+
const trimmed = r.path.replace(/\/\*[^/]+\?$/, "");
|
|
8755
|
+
if (trimmed && trimmed !== r.path && !map.has(trimmed)) {
|
|
8756
|
+
map.set(trimmed, r.nodeId);
|
|
8757
|
+
}
|
|
8599
8758
|
}
|
|
8600
8759
|
return map;
|
|
8601
8760
|
}
|
|
@@ -8618,26 +8777,54 @@ function normalizeFetchUrl(raw) {
|
|
|
8618
8777
|
return { path: s || "/", hadInterpolation };
|
|
8619
8778
|
}
|
|
8620
8779
|
function scoreApiRouteMatch(candidate, known) {
|
|
8621
|
-
if (candidate.length !== known.length) return -1;
|
|
8622
8780
|
let score = 0;
|
|
8623
|
-
|
|
8781
|
+
let i = 0, j = 0;
|
|
8782
|
+
while (i < candidate.length && j < known.length) {
|
|
8624
8783
|
const a = candidate[i];
|
|
8625
|
-
const b = known[
|
|
8784
|
+
const b = known[j];
|
|
8785
|
+
if (b.startsWith("*") && b.endsWith("?")) {
|
|
8786
|
+
score += 1;
|
|
8787
|
+
return score;
|
|
8788
|
+
}
|
|
8789
|
+
if (b.startsWith("*")) {
|
|
8790
|
+
const remaining = candidate.length - i;
|
|
8791
|
+
if (remaining < 1) return -1;
|
|
8792
|
+
score += 1 + remaining;
|
|
8793
|
+
return score;
|
|
8794
|
+
}
|
|
8626
8795
|
if (a === b) {
|
|
8627
8796
|
score += 3;
|
|
8797
|
+
i++;
|
|
8798
|
+
j++;
|
|
8628
8799
|
continue;
|
|
8629
8800
|
}
|
|
8630
8801
|
if (a.startsWith(":") && b.startsWith(":")) {
|
|
8631
8802
|
score += 2;
|
|
8803
|
+
i++;
|
|
8804
|
+
j++;
|
|
8632
8805
|
continue;
|
|
8633
8806
|
}
|
|
8634
8807
|
if (a.startsWith(":") || b.startsWith(":")) {
|
|
8635
8808
|
score += 1;
|
|
8809
|
+
i++;
|
|
8810
|
+
j++;
|
|
8636
8811
|
continue;
|
|
8637
8812
|
}
|
|
8638
8813
|
return -1;
|
|
8639
8814
|
}
|
|
8640
|
-
|
|
8815
|
+
if (i === candidate.length) {
|
|
8816
|
+
while (j < known.length) {
|
|
8817
|
+
const b = known[j];
|
|
8818
|
+
if (b.startsWith("*") && b.endsWith("?")) {
|
|
8819
|
+
score += 1;
|
|
8820
|
+
j++;
|
|
8821
|
+
continue;
|
|
8822
|
+
}
|
|
8823
|
+
return -1;
|
|
8824
|
+
}
|
|
8825
|
+
return score;
|
|
8826
|
+
}
|
|
8827
|
+
return -1;
|
|
8641
8828
|
}
|
|
8642
8829
|
function resolveFetchCall(call, apiPathMap, apiRoutes) {
|
|
8643
8830
|
const raw = call.url;
|
|
@@ -8794,48 +8981,58 @@ var fetchResolverParser = {
|
|
|
8794
8981
|
// src/server/graph/parsers/crosslayer/api-annotations.ts
|
|
8795
8982
|
var import_node_fs8 = require("node:fs");
|
|
8796
8983
|
var import_node_path7 = require("node:path");
|
|
8984
|
+
init_config();
|
|
8797
8985
|
var API_ANNOTATION_RE = /@api\s+(GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS)\s+(\/\S+)/g;
|
|
8798
|
-
function
|
|
8799
|
-
|
|
8800
|
-
|
|
8801
|
-
|
|
8802
|
-
if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
|
|
8803
|
-
const full = (0, import_node_path7.join)(dir, entry.name);
|
|
8804
|
-
if (entry.isDirectory()) {
|
|
8805
|
-
results.push(...walk2(full, exts));
|
|
8806
|
-
} else if (exts.includes((0, import_node_path7.extname)(entry.name))) {
|
|
8807
|
-
results.push(full);
|
|
8808
|
-
}
|
|
8986
|
+
function toNodeId2(srcDir, rootDir, absPath) {
|
|
8987
|
+
const relFromSrc = (0, import_node_path7.relative)(srcDir, absPath).replace(/\\/g, "/");
|
|
8988
|
+
if (relFromSrc.startsWith("..")) {
|
|
8989
|
+
return (0, import_node_path7.relative)(rootDir, absPath).replace(/\\/g, "/");
|
|
8809
8990
|
}
|
|
8810
|
-
return
|
|
8811
|
-
}
|
|
8812
|
-
function toNodeId2(srcDir, absPath) {
|
|
8813
|
-
return (0, import_node_path7.relative)(srcDir, absPath).replace(/\\/g, "/");
|
|
8991
|
+
return relFromSrc;
|
|
8814
8992
|
}
|
|
8815
8993
|
var apiAnnotationsParser = {
|
|
8816
8994
|
id: "api-annotations",
|
|
8817
8995
|
layer: "crosslayer",
|
|
8818
8996
|
concern: "api-binding",
|
|
8819
8997
|
detect(rootDir) {
|
|
8820
|
-
return (
|
|
8998
|
+
return resolveProjectPaths(rootDir, loadConfig(rootDir)) !== null;
|
|
8821
8999
|
},
|
|
8822
9000
|
generate(rootDir, layerOutputs) {
|
|
8823
9001
|
const apiOutput = layerOutputs.get("api");
|
|
8824
9002
|
if (!apiOutput) {
|
|
8825
9003
|
return { cross_refs: [], flagged_edges: [], warnings: [] };
|
|
8826
9004
|
}
|
|
9005
|
+
const paths = resolveProjectPaths(rootDir, loadConfig(rootDir));
|
|
9006
|
+
if (!paths) {
|
|
9007
|
+
return { cross_refs: [], flagged_edges: [], warnings: [] };
|
|
9008
|
+
}
|
|
8827
9009
|
const uiOutput = layerOutputs.get("ui");
|
|
8828
9010
|
const uiNodeIds = new Set(uiOutput?.nodes.map((n) => n.id) ?? []);
|
|
8829
9011
|
const apiRoutes = loadApiRoutesFromOutput(apiOutput);
|
|
8830
9012
|
const apiPathMap = buildApiPathMap(apiRoutes);
|
|
8831
|
-
const srcDir =
|
|
8832
|
-
const
|
|
9013
|
+
const srcDir = paths.srcDir;
|
|
9014
|
+
const seen = /* @__PURE__ */ new Set();
|
|
9015
|
+
const files = [];
|
|
9016
|
+
for (const root of paths.srcRoots) {
|
|
9017
|
+
for (const f of walk(root, [".ts", ".tsx"])) {
|
|
9018
|
+
if (!seen.has(f)) {
|
|
9019
|
+
seen.add(f);
|
|
9020
|
+
files.push(f);
|
|
9021
|
+
}
|
|
9022
|
+
}
|
|
9023
|
+
}
|
|
9024
|
+
for (const conv of paths.conventionFiles) {
|
|
9025
|
+
if (!seen.has(conv)) {
|
|
9026
|
+
seen.add(conv);
|
|
9027
|
+
files.push(conv);
|
|
9028
|
+
}
|
|
9029
|
+
}
|
|
8833
9030
|
const crossRefs = [];
|
|
8834
9031
|
const flaggedEdges = [];
|
|
8835
|
-
const
|
|
9032
|
+
const seenEdge = /* @__PURE__ */ new Set();
|
|
8836
9033
|
for (const absPath of files) {
|
|
8837
9034
|
const content = (0, import_node_fs8.readFileSync)(absPath, "utf-8");
|
|
8838
|
-
const sourceId = toNodeId2(srcDir, absPath);
|
|
9035
|
+
const sourceId = toNodeId2(srcDir, rootDir, absPath);
|
|
8839
9036
|
if (!uiNodeIds.has(sourceId)) continue;
|
|
8840
9037
|
let match;
|
|
8841
9038
|
API_ANNOTATION_RE.lastIndex = 0;
|
|
@@ -8845,8 +9042,8 @@ var apiAnnotationsParser = {
|
|
|
8845
9042
|
const result = resolveUrlPath(urlPath, apiPathMap, apiRoutes);
|
|
8846
9043
|
if (result.kind === "resolved" && result.nodeId) {
|
|
8847
9044
|
const key = `${sourceId}|${result.nodeId}|calls_api`;
|
|
8848
|
-
if (
|
|
8849
|
-
|
|
9045
|
+
if (seenEdge.has(key)) continue;
|
|
9046
|
+
seenEdge.add(key);
|
|
8850
9047
|
crossRefs.push({
|
|
8851
9048
|
source: sourceId,
|
|
8852
9049
|
target: result.nodeId,
|
|
@@ -8881,23 +9078,13 @@ var apiAnnotationsParser = {
|
|
|
8881
9078
|
var import_node_fs9 = require("node:fs");
|
|
8882
9079
|
var import_node_path8 = require("node:path");
|
|
8883
9080
|
init_config();
|
|
8884
|
-
var URL_LITERAL_RE = /['"`](\/
|
|
8885
|
-
function
|
|
8886
|
-
|
|
8887
|
-
|
|
8888
|
-
|
|
8889
|
-
if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
|
|
8890
|
-
const full = (0, import_node_path8.join)(dir, entry.name);
|
|
8891
|
-
if (entry.isDirectory()) {
|
|
8892
|
-
results.push(...walk3(full, exts));
|
|
8893
|
-
} else if (exts.includes((0, import_node_path8.extname)(entry.name))) {
|
|
8894
|
-
results.push(full);
|
|
8895
|
-
}
|
|
9081
|
+
var URL_LITERAL_RE = /['"`](\/[a-zA-Z][^'"`\s]*?)['"`]/g;
|
|
9082
|
+
function toNodeId3(srcDir, rootDir, absPath) {
|
|
9083
|
+
const relFromSrc = (0, import_node_path8.relative)(srcDir, absPath).replace(/\\/g, "/");
|
|
9084
|
+
if (relFromSrc.startsWith("..")) {
|
|
9085
|
+
return (0, import_node_path8.relative)(rootDir, absPath).replace(/\\/g, "/");
|
|
8896
9086
|
}
|
|
8897
|
-
return
|
|
8898
|
-
}
|
|
8899
|
-
function toNodeId3(srcDir, absPath) {
|
|
8900
|
-
return (0, import_node_path8.relative)(srcDir, absPath).replace(/\\/g, "/");
|
|
9087
|
+
return relFromSrc;
|
|
8901
9088
|
}
|
|
8902
9089
|
var urlLiteralScannerParser = {
|
|
8903
9090
|
id: "url-literal-scanner",
|
|
@@ -8918,15 +9105,26 @@ var urlLiteralScannerParser = {
|
|
|
8918
9105
|
const apiPathMap = buildApiPathMap(apiRoutes);
|
|
8919
9106
|
const paths = resolveProjectPaths(rootDir, loadConfig(rootDir));
|
|
8920
9107
|
const srcDir = paths.srcDir;
|
|
8921
|
-
const
|
|
8922
|
-
const files = [
|
|
8923
|
-
|
|
8924
|
-
|
|
8925
|
-
|
|
9108
|
+
const seenFile = /* @__PURE__ */ new Set();
|
|
9109
|
+
const files = [];
|
|
9110
|
+
for (const root of paths.srcRoots) {
|
|
9111
|
+
for (const f of walk(root, [".ts", ".tsx"])) {
|
|
9112
|
+
if (!seenFile.has(f)) {
|
|
9113
|
+
seenFile.add(f);
|
|
9114
|
+
files.push(f);
|
|
9115
|
+
}
|
|
9116
|
+
}
|
|
9117
|
+
}
|
|
9118
|
+
for (const conv of paths.conventionFiles) {
|
|
9119
|
+
if (!seenFile.has(conv)) {
|
|
9120
|
+
seenFile.add(conv);
|
|
9121
|
+
files.push(conv);
|
|
9122
|
+
}
|
|
9123
|
+
}
|
|
8926
9124
|
const crossRefs = [];
|
|
8927
9125
|
const seen = /* @__PURE__ */ new Set();
|
|
8928
9126
|
for (const absPath of files) {
|
|
8929
|
-
const sourceId = toNodeId3(srcDir, absPath);
|
|
9127
|
+
const sourceId = toNodeId3(srcDir, rootDir, absPath);
|
|
8930
9128
|
if (!uiNodeIds.has(sourceId)) continue;
|
|
8931
9129
|
const content = (0, import_node_fs9.readFileSync)(absPath, "utf-8");
|
|
8932
9130
|
let match;
|
|
@@ -8961,6 +9159,7 @@ var urlLiteralScannerParser = {
|
|
|
8961
9159
|
// src/server/graph/parsers/static/static-values.ts
|
|
8962
9160
|
var import_node_fs10 = require("node:fs");
|
|
8963
9161
|
var import_node_path9 = require("node:path");
|
|
9162
|
+
init_config();
|
|
8964
9163
|
var parseCode = null;
|
|
8965
9164
|
function tryLoadTreeSitter() {
|
|
8966
9165
|
if (parseCode) return true;
|
|
@@ -8992,10 +9191,15 @@ function classifyScope(source, model) {
|
|
|
8992
9191
|
function extractEnumValues(rootDir) {
|
|
8993
9192
|
const nodes = [];
|
|
8994
9193
|
const edges = [];
|
|
8995
|
-
const
|
|
8996
|
-
|
|
8997
|
-
|
|
8998
|
-
|
|
9194
|
+
const paths = resolveProjectPaths(rootDir, loadConfig(rootDir));
|
|
9195
|
+
const schemaPaths = [];
|
|
9196
|
+
if (paths?.dbConfig.kind === "prisma" && paths.dbConfig.schemaPath) {
|
|
9197
|
+
schemaPaths.push(paths.dbConfig.schemaPath);
|
|
9198
|
+
schemaPaths.push((0, import_node_path9.join)((0, import_node_path9.dirname)(paths.dbConfig.schemaPath), "schema"));
|
|
9199
|
+
} else {
|
|
9200
|
+
schemaPaths.push((0, import_node_path9.join)(rootDir, "prisma", "schema.prisma"));
|
|
9201
|
+
schemaPaths.push((0, import_node_path9.join)(rootDir, "prisma", "schema"));
|
|
9202
|
+
}
|
|
8999
9203
|
let content = "";
|
|
9000
9204
|
for (const p of schemaPaths) {
|
|
9001
9205
|
if ((0, import_node_fs10.existsSync)(p)) {
|
|
@@ -9079,7 +9283,7 @@ function extractStringArrayFromNode(node) {
|
|
|
9079
9283
|
return values;
|
|
9080
9284
|
}
|
|
9081
9285
|
function findArrayDecl(root, varName) {
|
|
9082
|
-
function
|
|
9286
|
+
function walk2(node) {
|
|
9083
9287
|
if (node.type === "variable_declarator") {
|
|
9084
9288
|
const nameNode = node.childForFieldName("name");
|
|
9085
9289
|
const valueNode = node.childForFieldName("value");
|
|
@@ -9092,12 +9296,12 @@ function findArrayDecl(root, varName) {
|
|
|
9092
9296
|
}
|
|
9093
9297
|
}
|
|
9094
9298
|
for (const child of node.namedChildren) {
|
|
9095
|
-
const found =
|
|
9299
|
+
const found = walk2(child);
|
|
9096
9300
|
if (found) return found;
|
|
9097
9301
|
}
|
|
9098
9302
|
return null;
|
|
9099
9303
|
}
|
|
9100
|
-
return
|
|
9304
|
+
return walk2(root);
|
|
9101
9305
|
}
|
|
9102
9306
|
function extractObjectPropsRegex(objStr) {
|
|
9103
9307
|
const props = {};
|
|
@@ -9160,11 +9364,26 @@ function modelToNodeType(model) {
|
|
|
9160
9364
|
function extractSeedData(rootDir) {
|
|
9161
9365
|
const nodes = [];
|
|
9162
9366
|
const edges = [];
|
|
9163
|
-
const
|
|
9164
|
-
|
|
9165
|
-
|
|
9166
|
-
(0, import_node_path9.join)(
|
|
9167
|
-
|
|
9367
|
+
const paths = resolveProjectPaths(rootDir, loadConfig(rootDir));
|
|
9368
|
+
const candidates = [];
|
|
9369
|
+
if (paths?.dbDir) {
|
|
9370
|
+
candidates.push((0, import_node_path9.join)(paths.dbDir, "seed.ts"));
|
|
9371
|
+
candidates.push((0, import_node_path9.join)(paths.dbDir, "seed.js"));
|
|
9372
|
+
} else {
|
|
9373
|
+
candidates.push((0, import_node_path9.join)(rootDir, "prisma", "seed.ts"));
|
|
9374
|
+
candidates.push((0, import_node_path9.join)(rootDir, "prisma", "seed.js"));
|
|
9375
|
+
}
|
|
9376
|
+
const baseRoots = paths?.srcRoots ?? [(0, import_node_path9.join)(rootDir, "src")];
|
|
9377
|
+
for (const root of baseRoots) {
|
|
9378
|
+
candidates.push((0, import_node_path9.join)(root, "server", "lib", "system-tags.ts"));
|
|
9379
|
+
}
|
|
9380
|
+
const seedFiles = candidates.filter((p) => {
|
|
9381
|
+
try {
|
|
9382
|
+
return (0, import_node_fs10.existsSync)(p) && (0, import_node_fs10.statSync)(p).isFile();
|
|
9383
|
+
} catch {
|
|
9384
|
+
return false;
|
|
9385
|
+
}
|
|
9386
|
+
});
|
|
9168
9387
|
const useTreeSitter = tryLoadTreeSitter();
|
|
9169
9388
|
for (const filePath of seedFiles) {
|
|
9170
9389
|
const content = (0, import_node_fs10.readFileSync)(filePath, "utf-8");
|
|
@@ -9274,9 +9493,20 @@ function walkDir(dir, exts) {
|
|
|
9274
9493
|
}
|
|
9275
9494
|
function extractConstants(rootDir) {
|
|
9276
9495
|
const nodes = [];
|
|
9277
|
-
const
|
|
9278
|
-
|
|
9279
|
-
|
|
9496
|
+
const paths = resolveProjectPaths(rootDir, loadConfig(rootDir));
|
|
9497
|
+
const roots = paths?.srcRoots ?? [(0, import_node_path9.join)(rootDir, "src")];
|
|
9498
|
+
const seenFile = /* @__PURE__ */ new Set();
|
|
9499
|
+
const allFiles = [];
|
|
9500
|
+
for (const root of roots) {
|
|
9501
|
+
for (const f of walkDir(root, [".ts", ".tsx"])) {
|
|
9502
|
+
if (!seenFile.has(f)) {
|
|
9503
|
+
seenFile.add(f);
|
|
9504
|
+
allFiles.push(f);
|
|
9505
|
+
}
|
|
9506
|
+
}
|
|
9507
|
+
}
|
|
9508
|
+
if (allFiles.length === 0) return { nodes };
|
|
9509
|
+
for (const filePath of allFiles) {
|
|
9280
9510
|
const content = (0, import_node_fs10.readFileSync)(filePath, "utf-8");
|
|
9281
9511
|
const relPath = (0, import_node_path9.relative)(rootDir, filePath);
|
|
9282
9512
|
const constArrayRe = /export\s+const\s+([A-Z][A-Z_0-9]+)\s*(?::[^=]+)?\s*=\s*\[/g;
|
|
@@ -9311,6 +9541,13 @@ function extractConstants(rootDir) {
|
|
|
9311
9541
|
return { nodes };
|
|
9312
9542
|
}
|
|
9313
9543
|
function detect4(rootDir) {
|
|
9544
|
+
const paths = resolveProjectPaths(rootDir, loadConfig(rootDir));
|
|
9545
|
+
if (paths?.dbConfig.kind === "prisma" && paths.dbConfig.schemaPath && (0, import_node_fs10.existsSync)(paths.dbConfig.schemaPath)) {
|
|
9546
|
+
return true;
|
|
9547
|
+
}
|
|
9548
|
+
if (paths?.dbDir) {
|
|
9549
|
+
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;
|
|
9550
|
+
}
|
|
9314
9551
|
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"));
|
|
9315
9552
|
}
|
|
9316
9553
|
function generate4(rootDir) {
|
|
@@ -9530,13 +9767,22 @@ var staticRefScannerParser = {
|
|
|
9530
9767
|
const paths = resolveProjectPaths(rootDir, loadConfig(rootDir));
|
|
9531
9768
|
if (!paths) return { cross_refs: [], flagged_edges: [], warnings: [] };
|
|
9532
9769
|
const srcDir = paths.srcDir;
|
|
9533
|
-
const
|
|
9534
|
-
|
|
9535
|
-
|
|
9536
|
-
|
|
9537
|
-
|
|
9538
|
-
|
|
9539
|
-
|
|
9770
|
+
const seenFile = /* @__PURE__ */ new Set();
|
|
9771
|
+
const files = [];
|
|
9772
|
+
for (const root of paths.srcRoots) {
|
|
9773
|
+
for (const f of walkWithIgnore(root, [".ts", ".tsx"])) {
|
|
9774
|
+
if (!seenFile.has(f)) {
|
|
9775
|
+
seenFile.add(f);
|
|
9776
|
+
files.push(f);
|
|
9777
|
+
}
|
|
9778
|
+
}
|
|
9779
|
+
}
|
|
9780
|
+
for (const conv of paths.conventionFiles) {
|
|
9781
|
+
if (!seenFile.has(conv)) {
|
|
9782
|
+
seenFile.add(conv);
|
|
9783
|
+
files.push(conv);
|
|
9784
|
+
}
|
|
9785
|
+
}
|
|
9540
9786
|
const uiOutput = layerOutputs.get("ui");
|
|
9541
9787
|
const apiOutput = layerOutputs.get("api");
|
|
9542
9788
|
const uiNodeIds = new Set(uiOutput?.nodes.map((n) => n.id) ?? []);
|
|
@@ -9553,7 +9799,8 @@ var staticRefScannerParser = {
|
|
|
9553
9799
|
const seen = /* @__PURE__ */ new Set();
|
|
9554
9800
|
let filesScanned = 0;
|
|
9555
9801
|
for (const absPath of files) {
|
|
9556
|
-
const
|
|
9802
|
+
const relFromSrc = (0, import_node_path10.relative)(srcDir, absPath).replace(/\\/g, "/");
|
|
9803
|
+
const sourceId = relFromSrc.startsWith("..") ? (0, import_node_path10.relative)(rootDir, absPath).replace(/\\/g, "/") : relFromSrc;
|
|
9557
9804
|
const sourceLayer = uiNodeIds.has(sourceId) ? "ui" : apiNodeIds.has(sourceId) ? "api" : null;
|
|
9558
9805
|
if (!sourceLayer) continue;
|
|
9559
9806
|
const content = (0, import_node_fs11.readFileSync)(absPath, "utf-8");
|
|
@@ -10014,6 +10261,9 @@ var GENERIC_ROLE_NAMES_BUILTIN = /* @__PURE__ */ new Set([
|
|
|
10014
10261
|
"__tests__",
|
|
10015
10262
|
"spec",
|
|
10016
10263
|
"specs",
|
|
10264
|
+
"scripts",
|
|
10265
|
+
"bin",
|
|
10266
|
+
"tools",
|
|
10017
10267
|
// Go
|
|
10018
10268
|
"cmd",
|
|
10019
10269
|
"pkg",
|
|
@@ -10101,13 +10351,17 @@ function isTrivialGroup(name, extraTrivial) {
|
|
|
10101
10351
|
function normalizeGroupName(name) {
|
|
10102
10352
|
return name.toLowerCase().replace(/-pages?$/, "").replace(/-layout$/, "").replace(/-wrapper$/, "");
|
|
10103
10353
|
}
|
|
10104
|
-
function extractModuleFromPath(id, extraTrivial, extraSkipSegments) {
|
|
10354
|
+
function extractModuleFromPath(id, extraTrivial, extraSkipSegments, extraGenericRoles) {
|
|
10105
10355
|
const segments = id.split("/");
|
|
10106
10356
|
const routeGroups = extractRouteGroups(id);
|
|
10107
10357
|
const skipSegments = new Set(SKIP_SEGMENTS_BUILTIN);
|
|
10358
|
+
for (const r of GENERIC_ROLE_NAMES_BUILTIN) skipSegments.add(r);
|
|
10108
10359
|
if (extraSkipSegments) {
|
|
10109
10360
|
for (const s of extraSkipSegments) skipSegments.add(s);
|
|
10110
10361
|
}
|
|
10362
|
+
if (extraGenericRoles) {
|
|
10363
|
+
for (const r of extraGenericRoles) skipSegments.add(r);
|
|
10364
|
+
}
|
|
10111
10365
|
const moduleGroups = routeGroups.filter((g) => !isTrivialGroup(g, extraTrivial)).map(normalizeGroupName);
|
|
10112
10366
|
if (moduleGroups.length > 0) {
|
|
10113
10367
|
return moduleGroups[moduleGroups.length - 1];
|
|
@@ -10858,7 +11112,7 @@ function checkDeadScreens(rootDir) {
|
|
|
10858
11112
|
const pages = ui.nodes.filter((n) => n.type === "page" || n.type === "layout");
|
|
10859
11113
|
const navTargets = /* @__PURE__ */ new Set();
|
|
10860
11114
|
for (const e of ui.edges) {
|
|
10861
|
-
if (e.type === "navigates" || e.type === "renders" || e.type === "imports") {
|
|
11115
|
+
if (e.type === "navigates" || e.type === "renders" || e.type === "imports" || e.type === "wraps") {
|
|
10862
11116
|
navTargets.add(e.target);
|
|
10863
11117
|
}
|
|
10864
11118
|
}
|
|
@@ -10868,13 +11122,15 @@ function checkDeadScreens(rootDir) {
|
|
|
10868
11122
|
for (const page of pages) {
|
|
10869
11123
|
if (page.id.endsWith("layout.tsx") && page.id.split("/").length <= 2) continue;
|
|
10870
11124
|
if (["error.tsx", "loading.tsx", "not-found.tsx", "template.tsx"].some((s) => page.id.endsWith(s))) continue;
|
|
11125
|
+
const route = page.route;
|
|
11126
|
+
if (route === "/" || route === "") continue;
|
|
10871
11127
|
if (!navTargets.has(page.id)) {
|
|
10872
11128
|
findings.push({
|
|
10873
11129
|
id: `dead:${page.id}`,
|
|
10874
11130
|
severity: "info",
|
|
10875
11131
|
category: "dead_screens",
|
|
10876
11132
|
title: page.name,
|
|
10877
|
-
detail: `Page "${page.id}" has no incoming navigation, render, or
|
|
11133
|
+
detail: `Page "${page.id}" has no incoming navigation, render, import, or layout-wrap edges.`,
|
|
10878
11134
|
file: page.id
|
|
10879
11135
|
});
|
|
10880
11136
|
}
|
|
@@ -12201,7 +12457,7 @@ function handleDetectProjectStack() {
|
|
|
12201
12457
|
const descriptions = {
|
|
12202
12458
|
"fetch-resolver": "Detects direct fetch()/api.get() calls with inline URLs",
|
|
12203
12459
|
"api-annotations": "Scans for @api METHOD /path annotations in JSDoc/comments",
|
|
12204
|
-
"url-literal-scanner": "Finds
|
|
12460
|
+
"url-literal-scanner": "Finds path-like string literals (any /\u2026) and resolves them to API routes",
|
|
12205
12461
|
"static-ref-scanner": "Finds references to static values (enums, permissions, roles)"
|
|
12206
12462
|
};
|
|
12207
12463
|
const grouped = {};
|