@launchsecure/launch-kit 0.0.21 → 0.0.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/server/chart-serve.js +336 -95
- package/dist/server/cli.js +337 -96
- 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 +341 -96
- 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,7 +7699,9 @@ 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
7706
|
if (route) routeToNodeId.set(route, id);
|
|
7601
7707
|
}
|
|
@@ -7619,6 +7725,29 @@ function generate(rootDir) {
|
|
|
7619
7725
|
uiEdges.push(...edges);
|
|
7620
7726
|
uiFlagged.push(...flagged);
|
|
7621
7727
|
}
|
|
7728
|
+
const layoutsById = /* @__PURE__ */ new Set();
|
|
7729
|
+
for (const n of uiNodes) {
|
|
7730
|
+
if (n.type === "layout") layoutsById.add(n.id);
|
|
7731
|
+
}
|
|
7732
|
+
function findClosestLayout(pageId) {
|
|
7733
|
+
let dir = pageId.replace(/\/page\.tsx$/, "");
|
|
7734
|
+
while (dir.length > 0) {
|
|
7735
|
+
const candidate = `${dir}/layout.tsx`;
|
|
7736
|
+
if (layoutsById.has(candidate)) return candidate;
|
|
7737
|
+
const slash = dir.lastIndexOf("/");
|
|
7738
|
+
if (slash < 0) break;
|
|
7739
|
+
dir = dir.slice(0, slash);
|
|
7740
|
+
}
|
|
7741
|
+
if (layoutsById.has("app/layout.tsx")) return "app/layout.tsx";
|
|
7742
|
+
return null;
|
|
7743
|
+
}
|
|
7744
|
+
for (const n of uiNodes) {
|
|
7745
|
+
if (n.type !== "page") continue;
|
|
7746
|
+
const layoutId = findClosestLayout(n.id);
|
|
7747
|
+
if (layoutId && layoutId !== n.id) {
|
|
7748
|
+
uiEdges.push({ source: layoutId, target: n.id, type: "wraps" });
|
|
7749
|
+
}
|
|
7750
|
+
}
|
|
7622
7751
|
const fetchCallEntries = [];
|
|
7623
7752
|
for (const absPath of fileSet) {
|
|
7624
7753
|
const sourceId = toNodeId(srcDir, rootDir, absPath);
|
|
@@ -7700,17 +7829,37 @@ function generate(rootDir) {
|
|
|
7700
7829
|
if (!dbCalls) continue;
|
|
7701
7830
|
const seenModels = /* @__PURE__ */ new Set();
|
|
7702
7831
|
for (const call of dbCalls) {
|
|
7703
|
-
|
|
7704
|
-
seenModels.
|
|
7832
|
+
const target = call.kind === "sql" ? call.model : camelToPascal(call.model);
|
|
7833
|
+
if (seenModels.has(target)) continue;
|
|
7834
|
+
seenModels.add(target);
|
|
7705
7835
|
apiCrossRefs.push({
|
|
7706
7836
|
source: node.id,
|
|
7707
|
-
target
|
|
7837
|
+
target,
|
|
7708
7838
|
type: call.isMutation ? "mutates" : "reads",
|
|
7709
7839
|
layer: "db"
|
|
7710
7840
|
});
|
|
7711
7841
|
}
|
|
7712
7842
|
delete node._dbCalls;
|
|
7713
7843
|
}
|
|
7844
|
+
const uiCrossRefs = [];
|
|
7845
|
+
for (const node of uiNodes) {
|
|
7846
|
+
const dbCalls = node._dbCalls;
|
|
7847
|
+
if (!dbCalls) continue;
|
|
7848
|
+
const seenModels = /* @__PURE__ */ new Set();
|
|
7849
|
+
for (const call of dbCalls) {
|
|
7850
|
+
const target = call.kind === "sql" ? call.model : camelToPascal(call.model);
|
|
7851
|
+
if (seenModels.has(target)) continue;
|
|
7852
|
+
seenModels.add(target);
|
|
7853
|
+
uiCrossRefs.push({
|
|
7854
|
+
source: node.id,
|
|
7855
|
+
target,
|
|
7856
|
+
type: call.isMutation ? "mutates" : "reads",
|
|
7857
|
+
layer: "db"
|
|
7858
|
+
});
|
|
7859
|
+
}
|
|
7860
|
+
delete node._dbCalls;
|
|
7861
|
+
}
|
|
7862
|
+
uiCrossRefs.sort((a, b) => a.source.localeCompare(b.source) || a.target.localeCompare(b.target));
|
|
7714
7863
|
const apiNodeIds = new Set(apiNodes.map((n) => n.id));
|
|
7715
7864
|
const apiEdges = [];
|
|
7716
7865
|
const uiOnlyEdges = [];
|
|
@@ -7772,7 +7921,7 @@ function generate(rootDir) {
|
|
|
7772
7921
|
},
|
|
7773
7922
|
nodes: stripLayer(uiNodes),
|
|
7774
7923
|
edges: uiOnlyEdges,
|
|
7775
|
-
cross_refs:
|
|
7924
|
+
cross_refs: uiCrossRefs,
|
|
7776
7925
|
contradictions: [],
|
|
7777
7926
|
warnings: [],
|
|
7778
7927
|
flagged_edges: dedupedFlagged,
|
|
@@ -8618,26 +8767,54 @@ function normalizeFetchUrl(raw) {
|
|
|
8618
8767
|
return { path: s || "/", hadInterpolation };
|
|
8619
8768
|
}
|
|
8620
8769
|
function scoreApiRouteMatch(candidate, known) {
|
|
8621
|
-
if (candidate.length !== known.length) return -1;
|
|
8622
8770
|
let score = 0;
|
|
8623
|
-
|
|
8771
|
+
let i = 0, j = 0;
|
|
8772
|
+
while (i < candidate.length && j < known.length) {
|
|
8624
8773
|
const a = candidate[i];
|
|
8625
|
-
const b = known[
|
|
8774
|
+
const b = known[j];
|
|
8775
|
+
if (b.startsWith("*") && b.endsWith("?")) {
|
|
8776
|
+
score += 1;
|
|
8777
|
+
return score;
|
|
8778
|
+
}
|
|
8779
|
+
if (b.startsWith("*")) {
|
|
8780
|
+
const remaining = candidate.length - i;
|
|
8781
|
+
if (remaining < 1) return -1;
|
|
8782
|
+
score += 1 + remaining;
|
|
8783
|
+
return score;
|
|
8784
|
+
}
|
|
8626
8785
|
if (a === b) {
|
|
8627
8786
|
score += 3;
|
|
8787
|
+
i++;
|
|
8788
|
+
j++;
|
|
8628
8789
|
continue;
|
|
8629
8790
|
}
|
|
8630
8791
|
if (a.startsWith(":") && b.startsWith(":")) {
|
|
8631
8792
|
score += 2;
|
|
8793
|
+
i++;
|
|
8794
|
+
j++;
|
|
8632
8795
|
continue;
|
|
8633
8796
|
}
|
|
8634
8797
|
if (a.startsWith(":") || b.startsWith(":")) {
|
|
8635
8798
|
score += 1;
|
|
8799
|
+
i++;
|
|
8800
|
+
j++;
|
|
8636
8801
|
continue;
|
|
8637
8802
|
}
|
|
8638
8803
|
return -1;
|
|
8639
8804
|
}
|
|
8640
|
-
|
|
8805
|
+
if (i === candidate.length) {
|
|
8806
|
+
while (j < known.length) {
|
|
8807
|
+
const b = known[j];
|
|
8808
|
+
if (b.startsWith("*") && b.endsWith("?")) {
|
|
8809
|
+
score += 1;
|
|
8810
|
+
j++;
|
|
8811
|
+
continue;
|
|
8812
|
+
}
|
|
8813
|
+
return -1;
|
|
8814
|
+
}
|
|
8815
|
+
return score;
|
|
8816
|
+
}
|
|
8817
|
+
return -1;
|
|
8641
8818
|
}
|
|
8642
8819
|
function resolveFetchCall(call, apiPathMap, apiRoutes) {
|
|
8643
8820
|
const raw = call.url;
|
|
@@ -8794,48 +8971,58 @@ var fetchResolverParser = {
|
|
|
8794
8971
|
// src/server/graph/parsers/crosslayer/api-annotations.ts
|
|
8795
8972
|
var import_node_fs8 = require("node:fs");
|
|
8796
8973
|
var import_node_path7 = require("node:path");
|
|
8974
|
+
init_config();
|
|
8797
8975
|
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
|
-
}
|
|
8976
|
+
function toNodeId2(srcDir, rootDir, absPath) {
|
|
8977
|
+
const relFromSrc = (0, import_node_path7.relative)(srcDir, absPath).replace(/\\/g, "/");
|
|
8978
|
+
if (relFromSrc.startsWith("..")) {
|
|
8979
|
+
return (0, import_node_path7.relative)(rootDir, absPath).replace(/\\/g, "/");
|
|
8809
8980
|
}
|
|
8810
|
-
return
|
|
8811
|
-
}
|
|
8812
|
-
function toNodeId2(srcDir, absPath) {
|
|
8813
|
-
return (0, import_node_path7.relative)(srcDir, absPath).replace(/\\/g, "/");
|
|
8981
|
+
return relFromSrc;
|
|
8814
8982
|
}
|
|
8815
8983
|
var apiAnnotationsParser = {
|
|
8816
8984
|
id: "api-annotations",
|
|
8817
8985
|
layer: "crosslayer",
|
|
8818
8986
|
concern: "api-binding",
|
|
8819
8987
|
detect(rootDir) {
|
|
8820
|
-
return (
|
|
8988
|
+
return resolveProjectPaths(rootDir, loadConfig(rootDir)) !== null;
|
|
8821
8989
|
},
|
|
8822
8990
|
generate(rootDir, layerOutputs) {
|
|
8823
8991
|
const apiOutput = layerOutputs.get("api");
|
|
8824
8992
|
if (!apiOutput) {
|
|
8825
8993
|
return { cross_refs: [], flagged_edges: [], warnings: [] };
|
|
8826
8994
|
}
|
|
8995
|
+
const paths = resolveProjectPaths(rootDir, loadConfig(rootDir));
|
|
8996
|
+
if (!paths) {
|
|
8997
|
+
return { cross_refs: [], flagged_edges: [], warnings: [] };
|
|
8998
|
+
}
|
|
8827
8999
|
const uiOutput = layerOutputs.get("ui");
|
|
8828
9000
|
const uiNodeIds = new Set(uiOutput?.nodes.map((n) => n.id) ?? []);
|
|
8829
9001
|
const apiRoutes = loadApiRoutesFromOutput(apiOutput);
|
|
8830
9002
|
const apiPathMap = buildApiPathMap(apiRoutes);
|
|
8831
|
-
const srcDir =
|
|
8832
|
-
const
|
|
9003
|
+
const srcDir = paths.srcDir;
|
|
9004
|
+
const seen = /* @__PURE__ */ new Set();
|
|
9005
|
+
const files = [];
|
|
9006
|
+
for (const root of paths.srcRoots) {
|
|
9007
|
+
for (const f of walk(root, [".ts", ".tsx"])) {
|
|
9008
|
+
if (!seen.has(f)) {
|
|
9009
|
+
seen.add(f);
|
|
9010
|
+
files.push(f);
|
|
9011
|
+
}
|
|
9012
|
+
}
|
|
9013
|
+
}
|
|
9014
|
+
for (const conv of paths.conventionFiles) {
|
|
9015
|
+
if (!seen.has(conv)) {
|
|
9016
|
+
seen.add(conv);
|
|
9017
|
+
files.push(conv);
|
|
9018
|
+
}
|
|
9019
|
+
}
|
|
8833
9020
|
const crossRefs = [];
|
|
8834
9021
|
const flaggedEdges = [];
|
|
8835
|
-
const
|
|
9022
|
+
const seenEdge = /* @__PURE__ */ new Set();
|
|
8836
9023
|
for (const absPath of files) {
|
|
8837
9024
|
const content = (0, import_node_fs8.readFileSync)(absPath, "utf-8");
|
|
8838
|
-
const sourceId = toNodeId2(srcDir, absPath);
|
|
9025
|
+
const sourceId = toNodeId2(srcDir, rootDir, absPath);
|
|
8839
9026
|
if (!uiNodeIds.has(sourceId)) continue;
|
|
8840
9027
|
let match;
|
|
8841
9028
|
API_ANNOTATION_RE.lastIndex = 0;
|
|
@@ -8845,8 +9032,8 @@ var apiAnnotationsParser = {
|
|
|
8845
9032
|
const result = resolveUrlPath(urlPath, apiPathMap, apiRoutes);
|
|
8846
9033
|
if (result.kind === "resolved" && result.nodeId) {
|
|
8847
9034
|
const key = `${sourceId}|${result.nodeId}|calls_api`;
|
|
8848
|
-
if (
|
|
8849
|
-
|
|
9035
|
+
if (seenEdge.has(key)) continue;
|
|
9036
|
+
seenEdge.add(key);
|
|
8850
9037
|
crossRefs.push({
|
|
8851
9038
|
source: sourceId,
|
|
8852
9039
|
target: result.nodeId,
|
|
@@ -8881,23 +9068,13 @@ var apiAnnotationsParser = {
|
|
|
8881
9068
|
var import_node_fs9 = require("node:fs");
|
|
8882
9069
|
var import_node_path8 = require("node:path");
|
|
8883
9070
|
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
|
-
}
|
|
9071
|
+
var URL_LITERAL_RE = /['"`](\/[a-zA-Z][^'"`\s]*?)['"`]/g;
|
|
9072
|
+
function toNodeId3(srcDir, rootDir, absPath) {
|
|
9073
|
+
const relFromSrc = (0, import_node_path8.relative)(srcDir, absPath).replace(/\\/g, "/");
|
|
9074
|
+
if (relFromSrc.startsWith("..")) {
|
|
9075
|
+
return (0, import_node_path8.relative)(rootDir, absPath).replace(/\\/g, "/");
|
|
8896
9076
|
}
|
|
8897
|
-
return
|
|
8898
|
-
}
|
|
8899
|
-
function toNodeId3(srcDir, absPath) {
|
|
8900
|
-
return (0, import_node_path8.relative)(srcDir, absPath).replace(/\\/g, "/");
|
|
9077
|
+
return relFromSrc;
|
|
8901
9078
|
}
|
|
8902
9079
|
var urlLiteralScannerParser = {
|
|
8903
9080
|
id: "url-literal-scanner",
|
|
@@ -8918,15 +9095,26 @@ var urlLiteralScannerParser = {
|
|
|
8918
9095
|
const apiPathMap = buildApiPathMap(apiRoutes);
|
|
8919
9096
|
const paths = resolveProjectPaths(rootDir, loadConfig(rootDir));
|
|
8920
9097
|
const srcDir = paths.srcDir;
|
|
8921
|
-
const
|
|
8922
|
-
const files = [
|
|
8923
|
-
|
|
8924
|
-
|
|
8925
|
-
|
|
9098
|
+
const seenFile = /* @__PURE__ */ new Set();
|
|
9099
|
+
const files = [];
|
|
9100
|
+
for (const root of paths.srcRoots) {
|
|
9101
|
+
for (const f of walk(root, [".ts", ".tsx"])) {
|
|
9102
|
+
if (!seenFile.has(f)) {
|
|
9103
|
+
seenFile.add(f);
|
|
9104
|
+
files.push(f);
|
|
9105
|
+
}
|
|
9106
|
+
}
|
|
9107
|
+
}
|
|
9108
|
+
for (const conv of paths.conventionFiles) {
|
|
9109
|
+
if (!seenFile.has(conv)) {
|
|
9110
|
+
seenFile.add(conv);
|
|
9111
|
+
files.push(conv);
|
|
9112
|
+
}
|
|
9113
|
+
}
|
|
8926
9114
|
const crossRefs = [];
|
|
8927
9115
|
const seen = /* @__PURE__ */ new Set();
|
|
8928
9116
|
for (const absPath of files) {
|
|
8929
|
-
const sourceId = toNodeId3(srcDir, absPath);
|
|
9117
|
+
const sourceId = toNodeId3(srcDir, rootDir, absPath);
|
|
8930
9118
|
if (!uiNodeIds.has(sourceId)) continue;
|
|
8931
9119
|
const content = (0, import_node_fs9.readFileSync)(absPath, "utf-8");
|
|
8932
9120
|
let match;
|
|
@@ -8961,6 +9149,7 @@ var urlLiteralScannerParser = {
|
|
|
8961
9149
|
// src/server/graph/parsers/static/static-values.ts
|
|
8962
9150
|
var import_node_fs10 = require("node:fs");
|
|
8963
9151
|
var import_node_path9 = require("node:path");
|
|
9152
|
+
init_config();
|
|
8964
9153
|
var parseCode = null;
|
|
8965
9154
|
function tryLoadTreeSitter() {
|
|
8966
9155
|
if (parseCode) return true;
|
|
@@ -8992,10 +9181,15 @@ function classifyScope(source, model) {
|
|
|
8992
9181
|
function extractEnumValues(rootDir) {
|
|
8993
9182
|
const nodes = [];
|
|
8994
9183
|
const edges = [];
|
|
8995
|
-
const
|
|
8996
|
-
|
|
8997
|
-
|
|
8998
|
-
|
|
9184
|
+
const paths = resolveProjectPaths(rootDir, loadConfig(rootDir));
|
|
9185
|
+
const schemaPaths = [];
|
|
9186
|
+
if (paths?.dbConfig.kind === "prisma" && paths.dbConfig.schemaPath) {
|
|
9187
|
+
schemaPaths.push(paths.dbConfig.schemaPath);
|
|
9188
|
+
schemaPaths.push((0, import_node_path9.join)((0, import_node_path9.dirname)(paths.dbConfig.schemaPath), "schema"));
|
|
9189
|
+
} else {
|
|
9190
|
+
schemaPaths.push((0, import_node_path9.join)(rootDir, "prisma", "schema.prisma"));
|
|
9191
|
+
schemaPaths.push((0, import_node_path9.join)(rootDir, "prisma", "schema"));
|
|
9192
|
+
}
|
|
8999
9193
|
let content = "";
|
|
9000
9194
|
for (const p of schemaPaths) {
|
|
9001
9195
|
if ((0, import_node_fs10.existsSync)(p)) {
|
|
@@ -9079,7 +9273,7 @@ function extractStringArrayFromNode(node) {
|
|
|
9079
9273
|
return values;
|
|
9080
9274
|
}
|
|
9081
9275
|
function findArrayDecl(root, varName) {
|
|
9082
|
-
function
|
|
9276
|
+
function walk2(node) {
|
|
9083
9277
|
if (node.type === "variable_declarator") {
|
|
9084
9278
|
const nameNode = node.childForFieldName("name");
|
|
9085
9279
|
const valueNode = node.childForFieldName("value");
|
|
@@ -9092,12 +9286,12 @@ function findArrayDecl(root, varName) {
|
|
|
9092
9286
|
}
|
|
9093
9287
|
}
|
|
9094
9288
|
for (const child of node.namedChildren) {
|
|
9095
|
-
const found =
|
|
9289
|
+
const found = walk2(child);
|
|
9096
9290
|
if (found) return found;
|
|
9097
9291
|
}
|
|
9098
9292
|
return null;
|
|
9099
9293
|
}
|
|
9100
|
-
return
|
|
9294
|
+
return walk2(root);
|
|
9101
9295
|
}
|
|
9102
9296
|
function extractObjectPropsRegex(objStr) {
|
|
9103
9297
|
const props = {};
|
|
@@ -9160,11 +9354,26 @@ function modelToNodeType(model) {
|
|
|
9160
9354
|
function extractSeedData(rootDir) {
|
|
9161
9355
|
const nodes = [];
|
|
9162
9356
|
const edges = [];
|
|
9163
|
-
const
|
|
9164
|
-
|
|
9165
|
-
|
|
9166
|
-
(0, import_node_path9.join)(
|
|
9167
|
-
|
|
9357
|
+
const paths = resolveProjectPaths(rootDir, loadConfig(rootDir));
|
|
9358
|
+
const candidates = [];
|
|
9359
|
+
if (paths?.dbDir) {
|
|
9360
|
+
candidates.push((0, import_node_path9.join)(paths.dbDir, "seed.ts"));
|
|
9361
|
+
candidates.push((0, import_node_path9.join)(paths.dbDir, "seed.js"));
|
|
9362
|
+
} else {
|
|
9363
|
+
candidates.push((0, import_node_path9.join)(rootDir, "prisma", "seed.ts"));
|
|
9364
|
+
candidates.push((0, import_node_path9.join)(rootDir, "prisma", "seed.js"));
|
|
9365
|
+
}
|
|
9366
|
+
const baseRoots = paths?.srcRoots ?? [(0, import_node_path9.join)(rootDir, "src")];
|
|
9367
|
+
for (const root of baseRoots) {
|
|
9368
|
+
candidates.push((0, import_node_path9.join)(root, "server", "lib", "system-tags.ts"));
|
|
9369
|
+
}
|
|
9370
|
+
const seedFiles = candidates.filter((p) => {
|
|
9371
|
+
try {
|
|
9372
|
+
return (0, import_node_fs10.existsSync)(p) && (0, import_node_fs10.statSync)(p).isFile();
|
|
9373
|
+
} catch {
|
|
9374
|
+
return false;
|
|
9375
|
+
}
|
|
9376
|
+
});
|
|
9168
9377
|
const useTreeSitter = tryLoadTreeSitter();
|
|
9169
9378
|
for (const filePath of seedFiles) {
|
|
9170
9379
|
const content = (0, import_node_fs10.readFileSync)(filePath, "utf-8");
|
|
@@ -9274,9 +9483,20 @@ function walkDir(dir, exts) {
|
|
|
9274
9483
|
}
|
|
9275
9484
|
function extractConstants(rootDir) {
|
|
9276
9485
|
const nodes = [];
|
|
9277
|
-
const
|
|
9278
|
-
|
|
9279
|
-
|
|
9486
|
+
const paths = resolveProjectPaths(rootDir, loadConfig(rootDir));
|
|
9487
|
+
const roots = paths?.srcRoots ?? [(0, import_node_path9.join)(rootDir, "src")];
|
|
9488
|
+
const seenFile = /* @__PURE__ */ new Set();
|
|
9489
|
+
const allFiles = [];
|
|
9490
|
+
for (const root of roots) {
|
|
9491
|
+
for (const f of walkDir(root, [".ts", ".tsx"])) {
|
|
9492
|
+
if (!seenFile.has(f)) {
|
|
9493
|
+
seenFile.add(f);
|
|
9494
|
+
allFiles.push(f);
|
|
9495
|
+
}
|
|
9496
|
+
}
|
|
9497
|
+
}
|
|
9498
|
+
if (allFiles.length === 0) return { nodes };
|
|
9499
|
+
for (const filePath of allFiles) {
|
|
9280
9500
|
const content = (0, import_node_fs10.readFileSync)(filePath, "utf-8");
|
|
9281
9501
|
const relPath = (0, import_node_path9.relative)(rootDir, filePath);
|
|
9282
9502
|
const constArrayRe = /export\s+const\s+([A-Z][A-Z_0-9]+)\s*(?::[^=]+)?\s*=\s*\[/g;
|
|
@@ -9311,6 +9531,13 @@ function extractConstants(rootDir) {
|
|
|
9311
9531
|
return { nodes };
|
|
9312
9532
|
}
|
|
9313
9533
|
function detect4(rootDir) {
|
|
9534
|
+
const paths = resolveProjectPaths(rootDir, loadConfig(rootDir));
|
|
9535
|
+
if (paths?.dbConfig.kind === "prisma" && paths.dbConfig.schemaPath && (0, import_node_fs10.existsSync)(paths.dbConfig.schemaPath)) {
|
|
9536
|
+
return true;
|
|
9537
|
+
}
|
|
9538
|
+
if (paths?.dbDir) {
|
|
9539
|
+
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;
|
|
9540
|
+
}
|
|
9314
9541
|
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
9542
|
}
|
|
9316
9543
|
function generate4(rootDir) {
|
|
@@ -9530,13 +9757,22 @@ var staticRefScannerParser = {
|
|
|
9530
9757
|
const paths = resolveProjectPaths(rootDir, loadConfig(rootDir));
|
|
9531
9758
|
if (!paths) return { cross_refs: [], flagged_edges: [], warnings: [] };
|
|
9532
9759
|
const srcDir = paths.srcDir;
|
|
9533
|
-
const
|
|
9534
|
-
|
|
9535
|
-
|
|
9536
|
-
|
|
9537
|
-
|
|
9538
|
-
|
|
9539
|
-
|
|
9760
|
+
const seenFile = /* @__PURE__ */ new Set();
|
|
9761
|
+
const files = [];
|
|
9762
|
+
for (const root of paths.srcRoots) {
|
|
9763
|
+
for (const f of walkWithIgnore(root, [".ts", ".tsx"])) {
|
|
9764
|
+
if (!seenFile.has(f)) {
|
|
9765
|
+
seenFile.add(f);
|
|
9766
|
+
files.push(f);
|
|
9767
|
+
}
|
|
9768
|
+
}
|
|
9769
|
+
}
|
|
9770
|
+
for (const conv of paths.conventionFiles) {
|
|
9771
|
+
if (!seenFile.has(conv)) {
|
|
9772
|
+
seenFile.add(conv);
|
|
9773
|
+
files.push(conv);
|
|
9774
|
+
}
|
|
9775
|
+
}
|
|
9540
9776
|
const uiOutput = layerOutputs.get("ui");
|
|
9541
9777
|
const apiOutput = layerOutputs.get("api");
|
|
9542
9778
|
const uiNodeIds = new Set(uiOutput?.nodes.map((n) => n.id) ?? []);
|
|
@@ -9553,7 +9789,8 @@ var staticRefScannerParser = {
|
|
|
9553
9789
|
const seen = /* @__PURE__ */ new Set();
|
|
9554
9790
|
let filesScanned = 0;
|
|
9555
9791
|
for (const absPath of files) {
|
|
9556
|
-
const
|
|
9792
|
+
const relFromSrc = (0, import_node_path10.relative)(srcDir, absPath).replace(/\\/g, "/");
|
|
9793
|
+
const sourceId = relFromSrc.startsWith("..") ? (0, import_node_path10.relative)(rootDir, absPath).replace(/\\/g, "/") : relFromSrc;
|
|
9557
9794
|
const sourceLayer = uiNodeIds.has(sourceId) ? "ui" : apiNodeIds.has(sourceId) ? "api" : null;
|
|
9558
9795
|
if (!sourceLayer) continue;
|
|
9559
9796
|
const content = (0, import_node_fs11.readFileSync)(absPath, "utf-8");
|
|
@@ -10101,13 +10338,17 @@ function isTrivialGroup(name, extraTrivial) {
|
|
|
10101
10338
|
function normalizeGroupName(name) {
|
|
10102
10339
|
return name.toLowerCase().replace(/-pages?$/, "").replace(/-layout$/, "").replace(/-wrapper$/, "");
|
|
10103
10340
|
}
|
|
10104
|
-
function extractModuleFromPath(id, extraTrivial, extraSkipSegments) {
|
|
10341
|
+
function extractModuleFromPath(id, extraTrivial, extraSkipSegments, extraGenericRoles) {
|
|
10105
10342
|
const segments = id.split("/");
|
|
10106
10343
|
const routeGroups = extractRouteGroups(id);
|
|
10107
10344
|
const skipSegments = new Set(SKIP_SEGMENTS_BUILTIN);
|
|
10345
|
+
for (const r of GENERIC_ROLE_NAMES_BUILTIN) skipSegments.add(r);
|
|
10108
10346
|
if (extraSkipSegments) {
|
|
10109
10347
|
for (const s of extraSkipSegments) skipSegments.add(s);
|
|
10110
10348
|
}
|
|
10349
|
+
if (extraGenericRoles) {
|
|
10350
|
+
for (const r of extraGenericRoles) skipSegments.add(r);
|
|
10351
|
+
}
|
|
10111
10352
|
const moduleGroups = routeGroups.filter((g) => !isTrivialGroup(g, extraTrivial)).map(normalizeGroupName);
|
|
10112
10353
|
if (moduleGroups.length > 0) {
|
|
10113
10354
|
return moduleGroups[moduleGroups.length - 1];
|
|
@@ -12201,7 +12442,7 @@ function handleDetectProjectStack() {
|
|
|
12201
12442
|
const descriptions = {
|
|
12202
12443
|
"fetch-resolver": "Detects direct fetch()/api.get() calls with inline URLs",
|
|
12203
12444
|
"api-annotations": "Scans for @api METHOD /path annotations in JSDoc/comments",
|
|
12204
|
-
"url-literal-scanner": "Finds
|
|
12445
|
+
"url-literal-scanner": "Finds path-like string literals (any /\u2026) and resolves them to API routes",
|
|
12205
12446
|
"static-ref-scanner": "Finds references to static values (enums, permissions, roles)"
|
|
12206
12447
|
};
|
|
12207
12448
|
const grouped = {};
|