@launchsecure/launch-kit 0.0.9 → 0.0.11
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/chart-client/assets/index-DIPKWwEJ.js +404 -0
- package/dist/chart-client/assets/index-DjnSR-Hf.css +1 -0
- package/dist/chart-client/index.html +2 -2
- package/dist/client/assets/index-BIpeUMYR.css +32 -0
- package/dist/client/index.html +2 -2
- package/dist/server/chart-serve.js +595 -324
- package/dist/server/cli.js +701 -326
- package/dist/server/graph/queries/db-calls.scm +8 -0
- package/dist/server/graph/queries/deep/conditions.scm +10 -0
- package/dist/server/graph/queries/deep/jsx-semantic.scm +21 -0
- package/dist/server/graph/queries/deep/request-params.scm +24 -0
- package/dist/server/graph/queries/deep/responses.scm +26 -0
- package/dist/server/graph/queries/deep/state-hooks.scm +23 -0
- package/dist/server/graph/queries/deep/variables.scm +17 -0
- package/dist/server/graph/queries/exports.scm +66 -0
- package/dist/server/graph/queries/fetch-calls.scm +57 -0
- package/dist/server/graph/queries/imports.scm +14 -0
- package/dist/server/graph/queries/jsx-elements.scm +14 -0
- package/dist/server/graph/queries/navigations.scm +85 -0
- package/dist/server/graph/queries/wrappers.scm +8 -0
- package/dist/server/graph-mcp-entry.js +710 -331
- package/package.json +5 -2
- package/dist/chart-client/assets/index-8p8Otm3A.js +0 -379
- package/dist/chart-client/assets/index-CuRWRjsg.css +0 -1
- package/dist/client/assets/index-DlwXprgf.css +0 -32
- /package/dist/client/assets/{index-B4wZOxci.js → index-qzK1AD2G.js} +0 -0
package/dist/server/cli.js
CHANGED
|
@@ -6363,41 +6363,160 @@ var import_node_path8 = require("node:path");
|
|
|
6363
6363
|
var import_node_fs3 = require("node:fs");
|
|
6364
6364
|
var import_node_path3 = require("node:path");
|
|
6365
6365
|
|
|
6366
|
-
// src/server/graph/core/
|
|
6366
|
+
// src/server/graph/core/ts-extractor.ts
|
|
6367
6367
|
var import_node_fs2 = require("node:fs");
|
|
6368
6368
|
var import_node_path2 = require("node:path");
|
|
6369
|
-
var
|
|
6370
|
-
|
|
6371
|
-
|
|
6372
|
-
|
|
6369
|
+
var tsxLanguage;
|
|
6370
|
+
var parserInstance;
|
|
6371
|
+
var initPromise;
|
|
6372
|
+
var initialized = false;
|
|
6373
|
+
var queriesDir = (() => {
|
|
6374
|
+
const srcPath = (0, import_node_path2.join)((0, import_node_path2.dirname)(__filename), "..", "queries");
|
|
6375
|
+
if (require("fs").existsSync(srcPath)) return srcPath;
|
|
6376
|
+
return (0, import_node_path2.join)((0, import_node_path2.dirname)(__filename), "graph", "queries");
|
|
6377
|
+
})();
|
|
6378
|
+
var queryCache = /* @__PURE__ */ new Map();
|
|
6379
|
+
async function initTreeSitter() {
|
|
6380
|
+
if (initialized) return;
|
|
6381
|
+
if (initPromise) return initPromise;
|
|
6382
|
+
initPromise = (async () => {
|
|
6383
|
+
const TreeSitter = require("web-tree-sitter");
|
|
6384
|
+
await TreeSitter.init();
|
|
6385
|
+
const wasmPath = require.resolve("tree-sitter-typescript/tree-sitter-tsx.wasm");
|
|
6386
|
+
tsxLanguage = await TreeSitter.Language.load(wasmPath);
|
|
6387
|
+
parserInstance = new TreeSitter();
|
|
6388
|
+
parserInstance.setLanguage(tsxLanguage);
|
|
6389
|
+
initialized = true;
|
|
6390
|
+
})();
|
|
6391
|
+
return initPromise;
|
|
6392
|
+
}
|
|
6393
|
+
function ensureInit() {
|
|
6394
|
+
if (!initialized || !tsxLanguage || !parserInstance) {
|
|
6395
|
+
throw new Error("Tree-sitter not initialized. Call initTreeSitter() first.");
|
|
6396
|
+
}
|
|
6397
|
+
}
|
|
6398
|
+
function getQuery(name) {
|
|
6399
|
+
ensureInit();
|
|
6400
|
+
const cached = queryCache.get(name);
|
|
6401
|
+
if (cached) return cached;
|
|
6402
|
+
const scmPath = (0, import_node_path2.join)(queriesDir, `${name}.scm`);
|
|
6403
|
+
const scm = (0, import_node_fs2.readFileSync)(scmPath, "utf-8");
|
|
6404
|
+
const query = tsxLanguage.query(scm);
|
|
6405
|
+
queryCache.set(name, query);
|
|
6406
|
+
return query;
|
|
6407
|
+
}
|
|
6408
|
+
function parseSource(absPath) {
|
|
6409
|
+
ensureInit();
|
|
6410
|
+
const content = (0, import_node_fs2.readFileSync)(absPath, "utf-8");
|
|
6411
|
+
return parserInstance.parse(content);
|
|
6412
|
+
}
|
|
6413
|
+
var PRISMA_MUTATION_METHODS_BUILTIN = [
|
|
6414
|
+
"create",
|
|
6415
|
+
"createMany",
|
|
6416
|
+
"createManyAndReturn",
|
|
6417
|
+
"update",
|
|
6418
|
+
"updateMany",
|
|
6419
|
+
"updateManyAndReturn",
|
|
6420
|
+
"upsert",
|
|
6421
|
+
"delete",
|
|
6422
|
+
"deleteMany"
|
|
6423
|
+
];
|
|
6424
|
+
var DB_IDENTIFIERS_FALLBACK = ["db", "prisma"];
|
|
6425
|
+
var extraDbIdentifiers = [];
|
|
6426
|
+
var extraMutationMethods = [];
|
|
6427
|
+
function setExtractorConfig(config) {
|
|
6428
|
+
extraDbIdentifiers = config.dbIdentifiers ?? [];
|
|
6429
|
+
extraMutationMethods = config.mutationMethods ?? [];
|
|
6430
|
+
}
|
|
6431
|
+
function getMutationMethods() {
|
|
6432
|
+
return /* @__PURE__ */ new Set([...PRISMA_MUTATION_METHODS_BUILTIN, ...extraMutationMethods]);
|
|
6433
|
+
}
|
|
6434
|
+
function getFallbackDbIdentifiers() {
|
|
6435
|
+
return /* @__PURE__ */ new Set([...DB_IDENTIFIERS_FALLBACK, ...extraDbIdentifiers]);
|
|
6436
|
+
}
|
|
6437
|
+
function looksLikeUrl(s) {
|
|
6438
|
+
return s.startsWith("/") || /^(https?:)?\/\//i.test(s);
|
|
6439
|
+
}
|
|
6440
|
+
function templateStartsWithSlash(text) {
|
|
6441
|
+
const content = text.slice(1);
|
|
6442
|
+
return content.startsWith("/");
|
|
6443
|
+
}
|
|
6444
|
+
function captureMap(match) {
|
|
6445
|
+
const map = {};
|
|
6446
|
+
for (const c of match.captures) {
|
|
6447
|
+
map[c.name] = c.node.text;
|
|
6373
6448
|
}
|
|
6374
|
-
return
|
|
6449
|
+
return map;
|
|
6375
6450
|
}
|
|
6376
|
-
|
|
6377
|
-
|
|
6378
|
-
|
|
6379
|
-
|
|
6380
|
-
|
|
6381
|
-
|
|
6382
|
-
|
|
6383
|
-
|
|
6384
|
-
|
|
6385
|
-
|
|
6386
|
-
|
|
6387
|
-
|
|
6388
|
-
|
|
6389
|
-
|
|
6451
|
+
function childrenOfType(node, type) {
|
|
6452
|
+
return node.children.filter((n) => n.type === type);
|
|
6453
|
+
}
|
|
6454
|
+
function childOfType(node, type) {
|
|
6455
|
+
return node.children.find((n) => n.type === type);
|
|
6456
|
+
}
|
|
6457
|
+
function parseFileTS(absPath) {
|
|
6458
|
+
const tree = parseSource(absPath);
|
|
6459
|
+
const root = tree.rootNode;
|
|
6460
|
+
const imports = [];
|
|
6461
|
+
const importStatements = childrenOfType(root, "import_statement");
|
|
6462
|
+
for (const stmt of importStatements) {
|
|
6463
|
+
const sourceNode = childOfType(stmt, "string");
|
|
6464
|
+
const frag = sourceNode ? childOfType(sourceNode, "string_fragment") : void 0;
|
|
6465
|
+
if (!frag) continue;
|
|
6466
|
+
const specifier = frag.text;
|
|
6467
|
+
const hasTypeKeyword = stmt.children.some(
|
|
6468
|
+
(n) => n.type === "type" && n.text === "type"
|
|
6469
|
+
);
|
|
6470
|
+
const clause = childOfType(stmt, "import_clause");
|
|
6471
|
+
if (!clause) {
|
|
6472
|
+
imports.push({ names: [], specifier, isTypeOnly: false, typeNames: /* @__PURE__ */ new Set() });
|
|
6473
|
+
continue;
|
|
6474
|
+
}
|
|
6475
|
+
const names = [];
|
|
6476
|
+
const typeNames = /* @__PURE__ */ new Set();
|
|
6477
|
+
const defaultId = childOfType(clause, "identifier");
|
|
6478
|
+
if (defaultId) names.push(defaultId.text);
|
|
6479
|
+
const nsImport = childOfType(clause, "namespace_import");
|
|
6480
|
+
if (nsImport) {
|
|
6481
|
+
const nsId = childOfType(nsImport, "identifier");
|
|
6482
|
+
if (nsId) names.push(nsId.text);
|
|
6483
|
+
}
|
|
6484
|
+
const namedImports = childOfType(clause, "named_imports");
|
|
6485
|
+
if (namedImports) {
|
|
6486
|
+
for (const spec of childrenOfType(namedImports, "import_specifier")) {
|
|
6487
|
+
const identifiers = childrenOfType(spec, "identifier");
|
|
6488
|
+
const importedName = identifiers.length > 1 ? identifiers[identifiers.length - 1].text : identifiers[0]?.text;
|
|
6489
|
+
if (importedName) {
|
|
6490
|
+
names.push(importedName);
|
|
6491
|
+
const specIsType = spec.children.some(
|
|
6492
|
+
(n) => n.type === "type" && n.text === "type"
|
|
6493
|
+
);
|
|
6494
|
+
if (specIsType) typeNames.add(importedName);
|
|
6495
|
+
}
|
|
6496
|
+
}
|
|
6497
|
+
}
|
|
6498
|
+
if (names.length > 0 || hasTypeKeyword) {
|
|
6499
|
+
imports.push({ names, specifier, isTypeOnly: hasTypeKeyword, typeNames });
|
|
6500
|
+
}
|
|
6501
|
+
}
|
|
6502
|
+
const importQuery = getQuery("imports");
|
|
6503
|
+
const importMatches = importQuery.matches(root);
|
|
6504
|
+
for (const m of importMatches) {
|
|
6505
|
+
const caps = captureMap(m);
|
|
6506
|
+
if (caps["import.dynamic"]) {
|
|
6507
|
+
imports.push({
|
|
6508
|
+
names: [],
|
|
6509
|
+
specifier: caps["import.dynamic"],
|
|
6510
|
+
isTypeOnly: false,
|
|
6511
|
+
typeNames: /* @__PURE__ */ new Set()
|
|
6512
|
+
});
|
|
6513
|
+
}
|
|
6514
|
+
}
|
|
6390
6515
|
const exportsSet = /* @__PURE__ */ new Set();
|
|
6391
6516
|
const exportsOrdered = [];
|
|
6392
6517
|
let defaultName = null;
|
|
6393
6518
|
let firstValueExport = null;
|
|
6394
6519
|
let firstTypeExport = null;
|
|
6395
|
-
const imports = [];
|
|
6396
|
-
const reExports = [];
|
|
6397
|
-
const jsxElements = /* @__PURE__ */ new Set();
|
|
6398
|
-
const navigations = [];
|
|
6399
|
-
const fetchCalls = [];
|
|
6400
|
-
const includeConcat = process.env.LAUNCH_CHART_INCLUDE_CONCAT_FETCHES === "1";
|
|
6401
6520
|
function addExport(name2, kind) {
|
|
6402
6521
|
if (!exportsSet.has(name2)) {
|
|
6403
6522
|
exportsSet.add(name2);
|
|
@@ -6407,300 +6526,339 @@ function parseFile(absPath) {
|
|
|
6407
6526
|
else if (kind === "value" && !firstValueExport) firstValueExport = name2;
|
|
6408
6527
|
else if (kind === "type" && !firstTypeExport) firstTypeExport = name2;
|
|
6409
6528
|
}
|
|
6410
|
-
|
|
6411
|
-
|
|
6412
|
-
|
|
6413
|
-
|
|
6414
|
-
|
|
6415
|
-
if (
|
|
6416
|
-
|
|
6417
|
-
|
|
6418
|
-
if (
|
|
6419
|
-
|
|
6529
|
+
const exportQuery = getQuery("exports");
|
|
6530
|
+
const exportMatches = exportQuery.matches(root);
|
|
6531
|
+
for (const m of exportMatches) {
|
|
6532
|
+
const caps = captureMap(m);
|
|
6533
|
+
if (caps["export.default.func"]) addExport(caps["export.default.func"], "default");
|
|
6534
|
+
else if (caps["export.default.class"]) addExport(caps["export.default.class"], "default");
|
|
6535
|
+
else if (caps["export.default.value"]) addExport(caps["export.default.value"], "default");
|
|
6536
|
+
else if (caps["export.named.func"]) addExport(caps["export.named.func"], "value");
|
|
6537
|
+
else if (caps["export.named.class"]) addExport(caps["export.named.class"], "value");
|
|
6538
|
+
else if (caps["export.named.const"]) addExport(caps["export.named.const"], "value");
|
|
6539
|
+
else if (caps["export.named.enum"]) addExport(caps["export.named.enum"], "value");
|
|
6540
|
+
else if (caps["export.named.type"]) addExport(caps["export.named.type"], "type");
|
|
6541
|
+
else if (caps["export.named.interface"]) addExport(caps["export.named.interface"], "type");
|
|
6542
|
+
else if (caps["export.bare.name"]) addExport(caps["export.bare.name"], "value");
|
|
6543
|
+
if (caps["reexport.name"]) addExport(caps["reexport.name"], "value");
|
|
6544
|
+
}
|
|
6545
|
+
for (const stmt of childrenOfType(root, "export_statement")) {
|
|
6546
|
+
const hasDefault = stmt.children.some((n) => n.text === "default");
|
|
6547
|
+
if (hasDefault && !defaultName) {
|
|
6548
|
+
defaultName = "default";
|
|
6420
6549
|
}
|
|
6421
|
-
return null;
|
|
6422
|
-
}
|
|
6423
|
-
function looksLikeUrl(s) {
|
|
6424
|
-
return s.startsWith("/") || /^(https?:)?\/\//i.test(s);
|
|
6425
|
-
}
|
|
6426
|
-
function templateStartsWithSlash(expr) {
|
|
6427
|
-
const head = expr.head.text;
|
|
6428
|
-
return head.startsWith("/");
|
|
6429
6550
|
}
|
|
6430
|
-
|
|
6431
|
-
|
|
6432
|
-
|
|
6433
|
-
|
|
6551
|
+
const reExports = [];
|
|
6552
|
+
for (const m of exportMatches) {
|
|
6553
|
+
const caps = captureMap(m);
|
|
6554
|
+
if (caps["reexport.name"] && caps["reexport.source"]) {
|
|
6555
|
+
reExports.push({ name: caps["reexport.name"], from: caps["reexport.source"] });
|
|
6434
6556
|
}
|
|
6435
|
-
if (
|
|
6436
|
-
|
|
6437
|
-
return { url: arg.getText(sourceFile), isTemplate: true };
|
|
6557
|
+
if (caps["reexport.wildcard.source"]) {
|
|
6558
|
+
reExports.push({ name: "*", from: caps["reexport.wildcard.source"], isWildcard: true });
|
|
6438
6559
|
}
|
|
6439
|
-
|
|
6440
|
-
|
|
6441
|
-
|
|
6442
|
-
|
|
6560
|
+
}
|
|
6561
|
+
const jsxElements = /* @__PURE__ */ new Set();
|
|
6562
|
+
const jsxQuery = getQuery("jsx-elements");
|
|
6563
|
+
const jsxCaptures = jsxQuery.captures(root);
|
|
6564
|
+
for (const c of jsxCaptures) {
|
|
6565
|
+
if (c.name === "jsx.tag") {
|
|
6566
|
+
if (/^[A-Z]/.test(c.node.text)) {
|
|
6567
|
+
jsxElements.add(c.node.text);
|
|
6443
6568
|
}
|
|
6444
|
-
|
|
6445
|
-
|
|
6569
|
+
} else if (c.name === "jsx.member_tag") {
|
|
6570
|
+
const rootName = c.node.text.split(".")[0];
|
|
6571
|
+
if (rootName && /^[A-Z]/.test(rootName)) {
|
|
6572
|
+
jsxElements.add(rootName);
|
|
6446
6573
|
}
|
|
6447
6574
|
}
|
|
6448
|
-
return null;
|
|
6449
6575
|
}
|
|
6450
|
-
|
|
6451
|
-
|
|
6452
|
-
|
|
6453
|
-
|
|
6454
|
-
|
|
6455
|
-
|
|
6456
|
-
|
|
6457
|
-
|
|
6458
|
-
|
|
6459
|
-
|
|
6460
|
-
|
|
6461
|
-
const nb = clause.namedBindings;
|
|
6462
|
-
if (nb && ts.isNamedImports(nb)) {
|
|
6463
|
-
for (const el of nb.elements) {
|
|
6464
|
-
names.push(el.name.text);
|
|
6465
|
-
if (el.isTypeOnly) typeNames.add(el.name.text);
|
|
6466
|
-
}
|
|
6467
|
-
} else if (nb && ts.isNamespaceImport(nb)) {
|
|
6468
|
-
names.push(nb.name.text);
|
|
6469
|
-
}
|
|
6470
|
-
}
|
|
6471
|
-
if (names.length > 0 || isTypeOnly) {
|
|
6472
|
-
imports.push({ names, specifier, isTypeOnly, typeNames });
|
|
6473
|
-
} else if (!clause) {
|
|
6474
|
-
imports.push({ names: [], specifier, isTypeOnly: false, typeNames: /* @__PURE__ */ new Set() });
|
|
6475
|
-
}
|
|
6476
|
-
}
|
|
6576
|
+
const navigations = [];
|
|
6577
|
+
const navQuery = getQuery("navigations");
|
|
6578
|
+
const navMatches = navQuery.matches(root);
|
|
6579
|
+
for (const m of navMatches) {
|
|
6580
|
+
const caps = captureMap(m);
|
|
6581
|
+
if (caps["nav.target.literal"] && caps["nav.method"]) {
|
|
6582
|
+
navigations.push({
|
|
6583
|
+
kind: caps["nav.method"] === "push" ? "router-push" : "router-replace",
|
|
6584
|
+
target: caps["nav.target.literal"],
|
|
6585
|
+
isTemplate: false
|
|
6586
|
+
});
|
|
6477
6587
|
}
|
|
6478
|
-
if (
|
|
6479
|
-
|
|
6480
|
-
|
|
6481
|
-
|
|
6482
|
-
|
|
6483
|
-
|
|
6484
|
-
if (fromSpec) {
|
|
6485
|
-
reExports.push({ name: exportedName, from: fromSpec });
|
|
6486
|
-
}
|
|
6487
|
-
}
|
|
6488
|
-
} else if (!node.exportClause && fromSpec) {
|
|
6489
|
-
reExports.push({ name: "*", from: fromSpec, isWildcard: true });
|
|
6490
|
-
}
|
|
6588
|
+
if (caps["nav.target.template"] && caps["nav.method.template"]) {
|
|
6589
|
+
navigations.push({
|
|
6590
|
+
kind: caps["nav.method.template"] === "push" ? "router-push" : "router-replace",
|
|
6591
|
+
target: caps["nav.target.template"],
|
|
6592
|
+
isTemplate: true
|
|
6593
|
+
});
|
|
6491
6594
|
}
|
|
6492
|
-
|
|
6493
|
-
|
|
6494
|
-
|
|
6495
|
-
imports.push({
|
|
6496
|
-
names: [],
|
|
6497
|
-
specifier: arg.text,
|
|
6498
|
-
isTypeOnly: false,
|
|
6499
|
-
typeNames: /* @__PURE__ */ new Set()
|
|
6500
|
-
});
|
|
6501
|
-
}
|
|
6595
|
+
const linkLiteral = caps["nav.link.literal"] || caps["nav.link.literal.self"];
|
|
6596
|
+
if (linkLiteral) {
|
|
6597
|
+
navigations.push({ kind: "link-href", target: linkLiteral, isTemplate: false });
|
|
6502
6598
|
}
|
|
6503
|
-
|
|
6504
|
-
|
|
6505
|
-
|
|
6506
|
-
|
|
6507
|
-
|
|
6508
|
-
}
|
|
6599
|
+
const linkTemplate = caps["nav.link.template"] || caps["nav.link.template.self"];
|
|
6600
|
+
if (linkTemplate) {
|
|
6601
|
+
navigations.push({ kind: "link-href", target: linkTemplate, isTemplate: true });
|
|
6602
|
+
}
|
|
6603
|
+
if (caps["nav.window.literal"]) {
|
|
6604
|
+
navigations.push({ kind: "window-location", target: caps["nav.window.literal"], isTemplate: false });
|
|
6509
6605
|
}
|
|
6510
|
-
if (
|
|
6511
|
-
|
|
6512
|
-
if (node.name) addExport(node.name.text, isDefault ? "default" : "value");
|
|
6606
|
+
if (caps["nav.window.assign.target"]) {
|
|
6607
|
+
navigations.push({ kind: "window-location", target: caps["nav.window.assign.target"], isTemplate: false });
|
|
6513
6608
|
}
|
|
6514
|
-
|
|
6515
|
-
|
|
6516
|
-
|
|
6517
|
-
|
|
6609
|
+
}
|
|
6610
|
+
const fetchCalls = [];
|
|
6611
|
+
const fetchQuery = getQuery("fetch-calls");
|
|
6612
|
+
const fetchMatches = fetchQuery.matches(root);
|
|
6613
|
+
for (const m of fetchMatches) {
|
|
6614
|
+
const caps = captureMap(m);
|
|
6615
|
+
if (caps["fetch.url.literal"] && looksLikeUrl(caps["fetch.url.literal"])) {
|
|
6616
|
+
fetchCalls.push({ url: caps["fetch.url.literal"], isTemplate: false, kind: "fetch" });
|
|
6617
|
+
}
|
|
6618
|
+
if (caps["fetch.url.template"] && templateStartsWithSlash(caps["fetch.url.template"])) {
|
|
6619
|
+
fetchCalls.push({ url: caps["fetch.url.template"], isTemplate: true, kind: "fetch" });
|
|
6620
|
+
}
|
|
6621
|
+
const clientUrl = caps["fetch.client.url.literal"] || caps["fetch.await.url.literal"];
|
|
6622
|
+
const clientMethod = caps["fetch.method"] || caps["fetch.await.method"];
|
|
6623
|
+
if (clientUrl && clientMethod && looksLikeUrl(clientUrl)) {
|
|
6624
|
+
fetchCalls.push({
|
|
6625
|
+
method: clientMethod.toUpperCase(),
|
|
6626
|
+
url: clientUrl,
|
|
6627
|
+
isTemplate: false,
|
|
6628
|
+
kind: "client-method"
|
|
6629
|
+
});
|
|
6630
|
+
}
|
|
6631
|
+
const clientUrlTpl = caps["fetch.client.url.template"] || caps["fetch.await.url.template"];
|
|
6632
|
+
const clientMethodTpl = caps["fetch.method.template"] || caps["fetch.await.method.template"];
|
|
6633
|
+
if (clientUrlTpl && clientMethodTpl && templateStartsWithSlash(clientUrlTpl)) {
|
|
6634
|
+
fetchCalls.push({
|
|
6635
|
+
method: clientMethodTpl.toUpperCase(),
|
|
6636
|
+
url: clientUrlTpl,
|
|
6637
|
+
isTemplate: true,
|
|
6638
|
+
kind: "client-method"
|
|
6639
|
+
});
|
|
6640
|
+
}
|
|
6641
|
+
}
|
|
6642
|
+
const name = defaultName ?? firstValueExport ?? firstTypeExport ?? "";
|
|
6643
|
+
return { name, exports: exportsOrdered, imports, reExports, jsxElements, navigations, fetchCalls };
|
|
6644
|
+
}
|
|
6645
|
+
function extractDbCallsTS(absPath) {
|
|
6646
|
+
const tree = parseSource(absPath);
|
|
6647
|
+
const root = tree.rootNode;
|
|
6648
|
+
const dbQuery = getQuery("db-calls");
|
|
6649
|
+
const matches = dbQuery.matches(root);
|
|
6650
|
+
const dbIdentifiers = /* @__PURE__ */ new Set();
|
|
6651
|
+
for (const stmt of childrenOfType(root, "import_statement")) {
|
|
6652
|
+
const sourceNode = childOfType(stmt, "string");
|
|
6653
|
+
const frag = sourceNode ? childOfType(sourceNode, "string_fragment") : void 0;
|
|
6654
|
+
if (!frag) continue;
|
|
6655
|
+
const spec = frag.text;
|
|
6656
|
+
if (spec.includes("prisma") || spec.includes("/db") || spec === "@prisma/client") {
|
|
6657
|
+
const clause = childOfType(stmt, "import_clause");
|
|
6658
|
+
if (clause) {
|
|
6659
|
+
const defaultId = childOfType(clause, "identifier");
|
|
6660
|
+
if (defaultId) dbIdentifiers.add(defaultId.text);
|
|
6661
|
+
const named = childOfType(clause, "named_imports");
|
|
6662
|
+
if (named) {
|
|
6663
|
+
for (const specNode of childrenOfType(named, "import_specifier")) {
|
|
6664
|
+
const ids = childrenOfType(specNode, "identifier");
|
|
6665
|
+
const importedName = ids[ids.length - 1];
|
|
6666
|
+
if (importedName) dbIdentifiers.add(importedName.text);
|
|
6667
|
+
}
|
|
6518
6668
|
}
|
|
6519
6669
|
}
|
|
6520
6670
|
}
|
|
6521
|
-
|
|
6522
|
-
|
|
6523
|
-
|
|
6524
|
-
|
|
6525
|
-
|
|
6526
|
-
|
|
6671
|
+
}
|
|
6672
|
+
if (dbIdentifiers.size === 0) {
|
|
6673
|
+
for (const id of getFallbackDbIdentifiers()) dbIdentifiers.add(id);
|
|
6674
|
+
} else {
|
|
6675
|
+
for (const id of extraDbIdentifiers) dbIdentifiers.add(id);
|
|
6676
|
+
}
|
|
6677
|
+
const calls = [];
|
|
6678
|
+
const seen = /* @__PURE__ */ new Set();
|
|
6679
|
+
for (const m of matches) {
|
|
6680
|
+
const caps = captureMap(m);
|
|
6681
|
+
const identifier = caps["db.identifier"];
|
|
6682
|
+
const model = caps["db.model"];
|
|
6683
|
+
const method = caps["db.method"];
|
|
6684
|
+
if (!identifier || !model || !method) continue;
|
|
6685
|
+
if (!dbIdentifiers.has(identifier)) continue;
|
|
6686
|
+
const key = `${model}.${method}`;
|
|
6687
|
+
if (seen.has(key)) continue;
|
|
6688
|
+
seen.add(key);
|
|
6689
|
+
calls.push({ model, method, isMutation: getMutationMethods().has(method) });
|
|
6690
|
+
}
|
|
6691
|
+
return calls;
|
|
6692
|
+
}
|
|
6693
|
+
function extractAuthWrappersTS(absPath) {
|
|
6694
|
+
const tree = parseSource(absPath);
|
|
6695
|
+
const root = tree.rootNode;
|
|
6696
|
+
const wrapperQuery = getQuery("wrappers");
|
|
6697
|
+
const matches = wrapperQuery.matches(root);
|
|
6698
|
+
const wrappers = /* @__PURE__ */ new Set();
|
|
6699
|
+
for (const m of matches) {
|
|
6700
|
+
const caps = captureMap(m);
|
|
6701
|
+
if (caps["wrapper.fn_name"]) {
|
|
6702
|
+
wrappers.add(caps["wrapper.fn_name"]);
|
|
6527
6703
|
}
|
|
6528
|
-
|
|
6529
|
-
|
|
6704
|
+
}
|
|
6705
|
+
return wrappers;
|
|
6706
|
+
}
|
|
6707
|
+
function trunc(s, max = 120) {
|
|
6708
|
+
return s.length > max ? s.slice(0, max) + "..." : s;
|
|
6709
|
+
}
|
|
6710
|
+
function extractDeep(absPath) {
|
|
6711
|
+
const tree = parseSource(absPath);
|
|
6712
|
+
const root = tree.rootNode;
|
|
6713
|
+
const elements = [];
|
|
6714
|
+
const elQuery = getQuery("deep/jsx-semantic");
|
|
6715
|
+
const elMatches = elQuery.matches(root);
|
|
6716
|
+
const elementMap = /* @__PURE__ */ new Map();
|
|
6717
|
+
for (const m of elMatches) {
|
|
6718
|
+
const tag = m.captures.find((c) => c.name === "el.tag")?.node;
|
|
6719
|
+
if (!tag || !/^[A-Z]/.test(tag.text)) continue;
|
|
6720
|
+
const elNode = m.captures.find((c) => c.name === "el.self" || c.name === "el.open")?.node;
|
|
6721
|
+
const key = elNode ? `${elNode.startPosition.row}:${elNode.startPosition.column}` : `${tag.startPosition.row}:${tag.startPosition.column}`;
|
|
6722
|
+
if (!elementMap.has(key)) {
|
|
6723
|
+
elementMap.set(key, { tag: tag.text, props: {}, nodeKey: key });
|
|
6724
|
+
}
|
|
6725
|
+
const entry = elementMap.get(key);
|
|
6726
|
+
const propName = m.captures.find((c) => c.name === "el.prop.name")?.node.text;
|
|
6727
|
+
const propVal = m.captures.find((c) => c.name === "el.prop.value.str")?.node.text;
|
|
6728
|
+
const propExpr = m.captures.find((c) => c.name === "el.prop.value.expr")?.node.text;
|
|
6729
|
+
if (propName) {
|
|
6730
|
+
entry.props[propName] = propVal ?? (propExpr ? trunc(propExpr, 60) : "true");
|
|
6731
|
+
}
|
|
6732
|
+
}
|
|
6733
|
+
for (const m of elMatches) {
|
|
6734
|
+
const textTag = m.captures.find((c) => c.name === "el.text.tag")?.node;
|
|
6735
|
+
const textContent = m.captures.find((c) => c.name === "el.text.content")?.node;
|
|
6736
|
+
if (textTag && textContent && /^[A-Z]/.test(textTag.text)) {
|
|
6737
|
+
const key = `${textTag.startPosition.row}:${textTag.startPosition.column}`;
|
|
6738
|
+
if (!elementMap.has(key)) {
|
|
6739
|
+
elementMap.set(key, { tag: textTag.text, props: {}, nodeKey: key });
|
|
6740
|
+
}
|
|
6741
|
+
const entry = elementMap.get(key);
|
|
6742
|
+
const text = textContent.text.trim();
|
|
6743
|
+
if (text) entry.props["_text"] = text;
|
|
6744
|
+
}
|
|
6745
|
+
}
|
|
6746
|
+
for (const entry of elementMap.values()) {
|
|
6747
|
+
const el = { tag: entry.tag, props: entry.props };
|
|
6748
|
+
const hasExpr = Object.values(entry.props).some((v) => v.includes("{") || v.includes("("));
|
|
6749
|
+
if (hasExpr) el.dynamic = true;
|
|
6750
|
+
if (entry.props["_text"]) {
|
|
6751
|
+
el.text = entry.props["_text"];
|
|
6752
|
+
delete el.props["_text"];
|
|
6753
|
+
}
|
|
6754
|
+
elements.push(el);
|
|
6755
|
+
}
|
|
6756
|
+
const stateVars = [];
|
|
6757
|
+
const hookQuery = getQuery("deep/state-hooks");
|
|
6758
|
+
const hookMatches = hookQuery.matches(root);
|
|
6759
|
+
for (const m of hookMatches) {
|
|
6760
|
+
const caps = captureMap(m);
|
|
6761
|
+
if (caps["hook.var"] && caps["hook.setter"] && caps["hook.fn"]) {
|
|
6762
|
+
stateVars.push({
|
|
6763
|
+
name: caps["hook.var"],
|
|
6764
|
+
setter: caps["hook.setter"],
|
|
6765
|
+
hook: caps["hook.fn"],
|
|
6766
|
+
init: trunc(caps["hook.init"] ?? "", 80)
|
|
6767
|
+
});
|
|
6530
6768
|
}
|
|
6531
|
-
if (
|
|
6532
|
-
|
|
6769
|
+
if (caps["reducer.var"] && caps["reducer.dispatch"]) {
|
|
6770
|
+
stateVars.push({
|
|
6771
|
+
name: caps["reducer.var"],
|
|
6772
|
+
setter: caps["reducer.dispatch"],
|
|
6773
|
+
hook: "useReducer",
|
|
6774
|
+
init: trunc(caps["reducer.init"] ?? "", 80)
|
|
6775
|
+
});
|
|
6533
6776
|
}
|
|
6534
|
-
|
|
6535
|
-
|
|
6536
|
-
|
|
6537
|
-
|
|
6538
|
-
|
|
6539
|
-
|
|
6540
|
-
|
|
6541
|
-
|
|
6542
|
-
|
|
6543
|
-
|
|
6544
|
-
|
|
6777
|
+
}
|
|
6778
|
+
const conditions = [];
|
|
6779
|
+
const condQuery = getQuery("deep/conditions");
|
|
6780
|
+
const condMatches = condQuery.matches(root);
|
|
6781
|
+
for (const m of condMatches) {
|
|
6782
|
+
const caps = captureMap(m);
|
|
6783
|
+
if (caps["cond.test"]) {
|
|
6784
|
+
conditions.push({
|
|
6785
|
+
kind: "if",
|
|
6786
|
+
test: trunc(caps["cond.test"], 100),
|
|
6787
|
+
consequence: caps["cond.consequence"] ? trunc(caps["cond.consequence"], 80) : void 0
|
|
6788
|
+
});
|
|
6545
6789
|
}
|
|
6546
|
-
if (
|
|
6547
|
-
|
|
6548
|
-
|
|
6549
|
-
|
|
6550
|
-
|
|
6551
|
-
const parsed = extractTargetFromExpression(arg);
|
|
6552
|
-
if (parsed) {
|
|
6553
|
-
navigations.push({
|
|
6554
|
-
kind: expr.name.text === "push" ? "router-push" : "router-replace",
|
|
6555
|
-
target: parsed.target,
|
|
6556
|
-
isTemplate: parsed.isTemplate
|
|
6557
|
-
});
|
|
6558
|
-
}
|
|
6559
|
-
}
|
|
6560
|
-
}
|
|
6790
|
+
if (caps["ternary.test"]) {
|
|
6791
|
+
conditions.push({
|
|
6792
|
+
kind: "ternary",
|
|
6793
|
+
test: trunc(caps["ternary.test"], 100)
|
|
6794
|
+
});
|
|
6561
6795
|
}
|
|
6562
|
-
|
|
6563
|
-
|
|
6564
|
-
|
|
6565
|
-
|
|
6566
|
-
|
|
6567
|
-
|
|
6568
|
-
|
|
6569
|
-
|
|
6570
|
-
|
|
6571
|
-
|
|
6572
|
-
|
|
6573
|
-
|
|
6574
|
-
|
|
6575
|
-
}
|
|
6576
|
-
if (ts.isPropertyAccessExpression(expr) && ts.isIdentifier(expr.name)) {
|
|
6577
|
-
const methodName = expr.name.text;
|
|
6578
|
-
if (HTTP_METHODS.has(methodName)) {
|
|
6579
|
-
const extracted = extractUrlFromFetchArg(firstArg);
|
|
6580
|
-
if (extracted) {
|
|
6581
|
-
fetchCalls.push({
|
|
6582
|
-
method: methodName.toUpperCase(),
|
|
6583
|
-
url: extracted.url,
|
|
6584
|
-
isTemplate: extracted.isTemplate,
|
|
6585
|
-
...extracted.isConcat ? { isConcat: true } : {},
|
|
6586
|
-
kind: "client-method"
|
|
6587
|
-
});
|
|
6588
|
-
}
|
|
6589
|
-
}
|
|
6590
|
-
}
|
|
6796
|
+
}
|
|
6797
|
+
const variables = [];
|
|
6798
|
+
const varQuery = getQuery("deep/variables");
|
|
6799
|
+
const varMatches = varQuery.matches(root);
|
|
6800
|
+
for (const m of varMatches) {
|
|
6801
|
+
const caps = captureMap(m);
|
|
6802
|
+
const declNode = m.captures.find((c) => c.name === "var.decl" || c.name === "var.destructured" || c.name === "var.array")?.node;
|
|
6803
|
+
const kind = declNode?.children.find((n) => n.type === "const" || n.type === "let" || n.type === "var")?.type ?? "const";
|
|
6804
|
+
if (caps["var.name"] && caps["var.init"]) {
|
|
6805
|
+
variables.push({
|
|
6806
|
+
name: caps["var.name"],
|
|
6807
|
+
kind,
|
|
6808
|
+
init: trunc(caps["var.init"], 100)
|
|
6809
|
+
});
|
|
6591
6810
|
}
|
|
6592
|
-
if (
|
|
6593
|
-
|
|
6594
|
-
|
|
6595
|
-
|
|
6596
|
-
|
|
6597
|
-
|
|
6598
|
-
if (ts.isStringLiteral(init)) {
|
|
6599
|
-
navigations.push({ kind: "link-href", target: init.text, isTemplate: false });
|
|
6600
|
-
} else if (ts.isJsxExpression(init) && init.expression) {
|
|
6601
|
-
const parsed = extractTargetFromExpression(init.expression);
|
|
6602
|
-
if (parsed) {
|
|
6603
|
-
navigations.push({ kind: "link-href", target: parsed.target, isTemplate: parsed.isTemplate });
|
|
6604
|
-
}
|
|
6605
|
-
}
|
|
6606
|
-
}
|
|
6607
|
-
}
|
|
6608
|
-
}
|
|
6811
|
+
if (caps["var.destructured.obj"]) {
|
|
6812
|
+
variables.push({
|
|
6813
|
+
name: trunc(caps["var.destructured.obj"], 60),
|
|
6814
|
+
kind,
|
|
6815
|
+
init: trunc(caps["var.destructured.init"], 100)
|
|
6816
|
+
});
|
|
6609
6817
|
}
|
|
6610
|
-
if (
|
|
6611
|
-
|
|
6612
|
-
|
|
6613
|
-
|
|
6614
|
-
|
|
6615
|
-
|
|
6616
|
-
}
|
|
6617
|
-
}
|
|
6818
|
+
if (caps["var.array.pattern"]) {
|
|
6819
|
+
variables.push({
|
|
6820
|
+
name: trunc(caps["var.array.pattern"], 60),
|
|
6821
|
+
kind,
|
|
6822
|
+
init: trunc(caps["var.array.init"], 100)
|
|
6823
|
+
});
|
|
6618
6824
|
}
|
|
6619
|
-
|
|
6620
|
-
|
|
6621
|
-
|
|
6622
|
-
|
|
6623
|
-
|
|
6624
|
-
|
|
6625
|
-
|
|
6626
|
-
|
|
6627
|
-
|
|
6628
|
-
|
|
6629
|
-
|
|
6825
|
+
}
|
|
6826
|
+
const responses = [];
|
|
6827
|
+
const respQuery = getQuery("deep/responses");
|
|
6828
|
+
const respMatches = respQuery.matches(root);
|
|
6829
|
+
const explicitBodies = /* @__PURE__ */ new Set();
|
|
6830
|
+
for (const m of respMatches) {
|
|
6831
|
+
const caps = captureMap(m);
|
|
6832
|
+
if (caps["resp.status"] && caps["resp.body"]) {
|
|
6833
|
+
explicitBodies.add(trunc(caps["resp.body"], 80));
|
|
6834
|
+
responses.push({
|
|
6835
|
+
status: caps["resp.status"],
|
|
6836
|
+
body: trunc(caps["resp.body"], 80)
|
|
6837
|
+
});
|
|
6630
6838
|
}
|
|
6631
|
-
ts.forEachChild(node, visit);
|
|
6632
6839
|
}
|
|
6633
|
-
|
|
6634
|
-
|
|
6635
|
-
|
|
6636
|
-
|
|
6637
|
-
|
|
6638
|
-
|
|
6639
|
-
reExports,
|
|
6640
|
-
jsxElements,
|
|
6641
|
-
navigations,
|
|
6642
|
-
fetchCalls
|
|
6643
|
-
};
|
|
6644
|
-
}
|
|
6645
|
-
var MUTATION_METHODS = /* @__PURE__ */ new Set([
|
|
6646
|
-
"create",
|
|
6647
|
-
"createMany",
|
|
6648
|
-
"createManyAndReturn",
|
|
6649
|
-
"update",
|
|
6650
|
-
"updateMany",
|
|
6651
|
-
"updateManyAndReturn",
|
|
6652
|
-
"upsert",
|
|
6653
|
-
"delete",
|
|
6654
|
-
"deleteMany"
|
|
6655
|
-
]);
|
|
6656
|
-
function extractDbCalls(absPath) {
|
|
6657
|
-
const ts = getTs();
|
|
6658
|
-
const content = (0, import_node_fs2.readFileSync)(absPath, "utf-8");
|
|
6659
|
-
const ext = (0, import_node_path2.extname)(absPath);
|
|
6660
|
-
const scriptKind = ext === ".tsx" ? ts.ScriptKind.TSX : ts.ScriptKind.TS;
|
|
6661
|
-
const sourceFile = ts.createSourceFile(absPath, content, ts.ScriptTarget.Latest, true, scriptKind);
|
|
6662
|
-
const calls = [];
|
|
6663
|
-
const seen = /* @__PURE__ */ new Set();
|
|
6664
|
-
function visit(node) {
|
|
6665
|
-
if (ts.isCallExpression(node)) {
|
|
6666
|
-
const expr = node.expression;
|
|
6667
|
-
if (ts.isPropertyAccessExpression(expr) && ts.isPropertyAccessExpression(expr.expression) && ts.isIdentifier(expr.expression.expression) && expr.expression.expression.text === "db") {
|
|
6668
|
-
const model = expr.expression.name.text;
|
|
6669
|
-
const method = expr.name.text;
|
|
6670
|
-
const key = `${model}.${method}`;
|
|
6671
|
-
if (!seen.has(key)) {
|
|
6672
|
-
seen.add(key);
|
|
6673
|
-
calls.push({
|
|
6674
|
-
model,
|
|
6675
|
-
method,
|
|
6676
|
-
isMutation: MUTATION_METHODS.has(method)
|
|
6677
|
-
});
|
|
6678
|
-
}
|
|
6840
|
+
for (const m of respMatches) {
|
|
6841
|
+
const caps = captureMap(m);
|
|
6842
|
+
if (caps["resp.body.default"] && !caps["resp.status"]) {
|
|
6843
|
+
const bodyText = trunc(caps["resp.body.default"], 80);
|
|
6844
|
+
if (!explicitBodies.has(bodyText)) {
|
|
6845
|
+
responses.push({ status: "200", body: bodyText });
|
|
6679
6846
|
}
|
|
6680
6847
|
}
|
|
6681
|
-
ts.forEachChild(node, visit);
|
|
6682
6848
|
}
|
|
6683
|
-
|
|
6684
|
-
|
|
6685
|
-
|
|
6686
|
-
|
|
6687
|
-
|
|
6688
|
-
|
|
6689
|
-
|
|
6690
|
-
|
|
6691
|
-
|
|
6692
|
-
|
|
6693
|
-
const AUTH_WRAPPERS = /* @__PURE__ */ new Set(["withAuth", "withPermission", "withRole", "requireAuth"]);
|
|
6694
|
-
function visit(node) {
|
|
6695
|
-
if (ts.isCallExpression(node) && ts.isIdentifier(node.expression)) {
|
|
6696
|
-
if (AUTH_WRAPPERS.has(node.expression.text)) {
|
|
6697
|
-
wrappers.add(node.expression.text);
|
|
6698
|
-
}
|
|
6849
|
+
const params = [];
|
|
6850
|
+
const paramQuery = getQuery("deep/request-params");
|
|
6851
|
+
const paramMatches = paramQuery.matches(root);
|
|
6852
|
+
for (const m of paramMatches) {
|
|
6853
|
+
const caps = captureMap(m);
|
|
6854
|
+
if (caps["param.name"]) {
|
|
6855
|
+
params.push({ name: caps["param.name"], source: "body-field" });
|
|
6856
|
+
}
|
|
6857
|
+
if (caps["param.body"]) {
|
|
6858
|
+
params.push({ name: caps["param.body"], source: "body" });
|
|
6699
6859
|
}
|
|
6700
|
-
ts.forEachChild(node, visit);
|
|
6701
6860
|
}
|
|
6702
|
-
|
|
6703
|
-
return wrappers;
|
|
6861
|
+
return { elements, stateVars, conditions, variables, responses, params };
|
|
6704
6862
|
}
|
|
6705
6863
|
|
|
6706
6864
|
// src/server/graph/parsers/ui/react-nextjs.ts
|
|
@@ -7026,7 +7184,7 @@ function generate(rootDir) {
|
|
|
7026
7184
|
const allDiscovered = [...appFiles, ...clientFiles, ...serverFiles, ...libFiles, ...configFiles];
|
|
7027
7185
|
const parsedByPath = /* @__PURE__ */ new Map();
|
|
7028
7186
|
for (const absPath of allDiscovered) {
|
|
7029
|
-
parsedByPath.set(absPath,
|
|
7187
|
+
parsedByPath.set(absPath, parseFileTS(absPath));
|
|
7030
7188
|
}
|
|
7031
7189
|
const barrelMaps = buildAllBarrelMaps(srcDir, parsedByPath);
|
|
7032
7190
|
const fileSet = allDiscovered.filter((f) => !(0, import_node_path3.basename)(f).startsWith("index."));
|
|
@@ -7040,7 +7198,18 @@ function generate(rootDir) {
|
|
|
7040
7198
|
const parsed = parsedByPath.get(absPath);
|
|
7041
7199
|
const name = parsed.name || nameFromFilename(absPath);
|
|
7042
7200
|
const route = extractRoute(id);
|
|
7043
|
-
|
|
7201
|
+
const deep = extractDeep(absPath);
|
|
7202
|
+
nodes.push({
|
|
7203
|
+
id,
|
|
7204
|
+
type,
|
|
7205
|
+
name,
|
|
7206
|
+
route,
|
|
7207
|
+
exports: parsed.exports,
|
|
7208
|
+
elements: deep.elements,
|
|
7209
|
+
stateVars: deep.stateVars,
|
|
7210
|
+
conditions: deep.conditions,
|
|
7211
|
+
variables: deep.variables
|
|
7212
|
+
});
|
|
7044
7213
|
nodeIdSet.add(id);
|
|
7045
7214
|
nodeTypeMap.set(id, type);
|
|
7046
7215
|
if (route) routeToNodeId.set(route, id);
|
|
@@ -7099,7 +7268,7 @@ function generate(rootDir) {
|
|
|
7099
7268
|
if (externalScanned.has(normalized)) continue;
|
|
7100
7269
|
let parsed;
|
|
7101
7270
|
try {
|
|
7102
|
-
parsed =
|
|
7271
|
+
parsed = parseFileTS(absPath);
|
|
7103
7272
|
} catch {
|
|
7104
7273
|
continue;
|
|
7105
7274
|
}
|
|
@@ -7223,7 +7392,7 @@ var reactNextjsParser = {
|
|
|
7223
7392
|
// src/server/graph/parsers/api/nextjs-routes.ts
|
|
7224
7393
|
var import_node_fs4 = require("node:fs");
|
|
7225
7394
|
var import_node_path4 = require("node:path");
|
|
7226
|
-
var
|
|
7395
|
+
var HTTP_METHODS = /* @__PURE__ */ new Set(["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"]);
|
|
7227
7396
|
function walk2(dir) {
|
|
7228
7397
|
const results = [];
|
|
7229
7398
|
if (!(0, import_node_fs4.existsSync)(dir)) return results;
|
|
@@ -7262,12 +7431,12 @@ function generate2(rootDir) {
|
|
|
7262
7431
|
let endpointsWithAuth = 0;
|
|
7263
7432
|
let endpointsWithDbAccess = 0;
|
|
7264
7433
|
for (const absPath of routeFiles) {
|
|
7265
|
-
const parsed =
|
|
7266
|
-
const dbCalls =
|
|
7267
|
-
const authWrappers =
|
|
7434
|
+
const parsed = parseFileTS(absPath);
|
|
7435
|
+
const dbCalls = extractDbCallsTS(absPath);
|
|
7436
|
+
const authWrappers = extractAuthWrappersTS(absPath);
|
|
7268
7437
|
const methods = [];
|
|
7269
7438
|
for (const exp of parsed.exports) {
|
|
7270
|
-
if (
|
|
7439
|
+
if (HTTP_METHODS.has(exp)) methods.push(exp);
|
|
7271
7440
|
}
|
|
7272
7441
|
const routePath = filePathToRoute(apiDir, absPath);
|
|
7273
7442
|
const relPath = (0, import_node_path4.relative)(rootDir, absPath).replace(/\\/g, "/");
|
|
@@ -7286,6 +7455,7 @@ function generate2(rootDir) {
|
|
|
7286
7455
|
authUsage[w] = (authUsage[w] ?? 0) + 1;
|
|
7287
7456
|
}
|
|
7288
7457
|
if (authStrategy.length > 0) endpointsWithAuth++;
|
|
7458
|
+
const deep = extractDeep(absPath);
|
|
7289
7459
|
nodes.push({
|
|
7290
7460
|
id: relPath,
|
|
7291
7461
|
type: "endpoint",
|
|
@@ -7297,7 +7467,12 @@ function generate2(rootDir) {
|
|
|
7297
7467
|
mutates,
|
|
7298
7468
|
auth: authStrategy.length > 0 ? authStrategy : ["public"],
|
|
7299
7469
|
db_models: [...new Set(dbCalls.map((c) => c.model))],
|
|
7300
|
-
db_operations: [...new Set(dbCalls.map((c) => `${c.model}.${c.method}`))]
|
|
7470
|
+
db_operations: [...new Set(dbCalls.map((c) => `${c.model}.${c.method}`))],
|
|
7471
|
+
// Deep extraction fields
|
|
7472
|
+
conditions: deep.conditions,
|
|
7473
|
+
variables: deep.variables,
|
|
7474
|
+
responses: deep.responses,
|
|
7475
|
+
params: deep.params
|
|
7301
7476
|
});
|
|
7302
7477
|
const seenModels = /* @__PURE__ */ new Set();
|
|
7303
7478
|
for (const call of dbCalls) {
|
|
@@ -7337,7 +7512,7 @@ function generate2(rootDir) {
|
|
|
7337
7512
|
flagged_edges: [],
|
|
7338
7513
|
patterns: {
|
|
7339
7514
|
total_endpoints: nodes.length,
|
|
7340
|
-
methods_breakdown: [...
|
|
7515
|
+
methods_breakdown: [...HTTP_METHODS].reduce((acc, m) => {
|
|
7341
7516
|
acc[m] = nodes.filter((n) => n.methods.includes(m)).length;
|
|
7342
7517
|
return acc;
|
|
7343
7518
|
}, {}),
|
|
@@ -8192,9 +8367,10 @@ function matchParts(pat, pi, id, ii) {
|
|
|
8192
8367
|
while (pi < pat.length && pat[pi] === "**") pi++;
|
|
8193
8368
|
return pi === pat.length && ii === id.length;
|
|
8194
8369
|
}
|
|
8195
|
-
var
|
|
8196
|
-
function detectConventionDirs(rootDir) {
|
|
8370
|
+
var CONVENTION_DIRS_BUILTIN = ["features", "modules", "domains", "areas"];
|
|
8371
|
+
function detectConventionDirs(rootDir, extraConventionDirs = []) {
|
|
8197
8372
|
const result = /* @__PURE__ */ new Map();
|
|
8373
|
+
const conventionDirs = [...CONVENTION_DIRS_BUILTIN, ...extraConventionDirs];
|
|
8198
8374
|
const searchDirs = [
|
|
8199
8375
|
rootDir,
|
|
8200
8376
|
(0, import_node_path10.join)(rootDir, "src"),
|
|
@@ -8202,7 +8378,7 @@ function detectConventionDirs(rootDir) {
|
|
|
8202
8378
|
(0, import_node_path10.join)(rootDir, "lib")
|
|
8203
8379
|
];
|
|
8204
8380
|
for (const base of searchDirs) {
|
|
8205
|
-
for (const convention of
|
|
8381
|
+
for (const convention of conventionDirs) {
|
|
8206
8382
|
const dir = (0, import_node_path10.join)(base, convention);
|
|
8207
8383
|
if (!(0, import_node_fs9.existsSync)(dir)) continue;
|
|
8208
8384
|
try {
|
|
@@ -8228,7 +8404,76 @@ function extractRouteGroups(id) {
|
|
|
8228
8404
|
}
|
|
8229
8405
|
return groups;
|
|
8230
8406
|
}
|
|
8231
|
-
var
|
|
8407
|
+
var GENERIC_ROLE_NAMES_BUILTIN = /* @__PURE__ */ new Set([
|
|
8408
|
+
// JS/TS
|
|
8409
|
+
"components",
|
|
8410
|
+
"hooks",
|
|
8411
|
+
"pages",
|
|
8412
|
+
"views",
|
|
8413
|
+
"screens",
|
|
8414
|
+
"layouts",
|
|
8415
|
+
"utils",
|
|
8416
|
+
"helpers",
|
|
8417
|
+
"lib",
|
|
8418
|
+
"libs",
|
|
8419
|
+
"services",
|
|
8420
|
+
"api",
|
|
8421
|
+
"apis",
|
|
8422
|
+
"stores",
|
|
8423
|
+
"state",
|
|
8424
|
+
"store",
|
|
8425
|
+
"context",
|
|
8426
|
+
"contexts",
|
|
8427
|
+
"providers",
|
|
8428
|
+
"types",
|
|
8429
|
+
"interfaces",
|
|
8430
|
+
"models",
|
|
8431
|
+
"schemas",
|
|
8432
|
+
"constants",
|
|
8433
|
+
"config",
|
|
8434
|
+
"configs",
|
|
8435
|
+
"assets",
|
|
8436
|
+
"styles",
|
|
8437
|
+
"public",
|
|
8438
|
+
"middleware",
|
|
8439
|
+
"middlewares",
|
|
8440
|
+
"routes",
|
|
8441
|
+
"router",
|
|
8442
|
+
"tests",
|
|
8443
|
+
"test",
|
|
8444
|
+
"__tests__",
|
|
8445
|
+
"spec",
|
|
8446
|
+
"specs",
|
|
8447
|
+
// Go
|
|
8448
|
+
"cmd",
|
|
8449
|
+
"pkg",
|
|
8450
|
+
"internal",
|
|
8451
|
+
// Python
|
|
8452
|
+
"management",
|
|
8453
|
+
"migrations",
|
|
8454
|
+
"templatetags",
|
|
8455
|
+
"templates",
|
|
8456
|
+
// Java
|
|
8457
|
+
"controller",
|
|
8458
|
+
"controllers",
|
|
8459
|
+
"service",
|
|
8460
|
+
"repository",
|
|
8461
|
+
"repositories",
|
|
8462
|
+
"entity",
|
|
8463
|
+
"entities",
|
|
8464
|
+
"dto",
|
|
8465
|
+
"dtos",
|
|
8466
|
+
// General
|
|
8467
|
+
"shared",
|
|
8468
|
+
"common",
|
|
8469
|
+
"core",
|
|
8470
|
+
"base",
|
|
8471
|
+
"app",
|
|
8472
|
+
// Next.js specific
|
|
8473
|
+
"client",
|
|
8474
|
+
"server"
|
|
8475
|
+
]);
|
|
8476
|
+
var SKIP_SEGMENTS_BUILTIN = /* @__PURE__ */ new Set([
|
|
8232
8477
|
"src",
|
|
8233
8478
|
"app",
|
|
8234
8479
|
"client",
|
|
@@ -8286,9 +8531,13 @@ function isTrivialGroup(name, extraTrivial) {
|
|
|
8286
8531
|
function normalizeGroupName(name) {
|
|
8287
8532
|
return name.toLowerCase().replace(/-pages?$/, "").replace(/-layout$/, "").replace(/-wrapper$/, "");
|
|
8288
8533
|
}
|
|
8289
|
-
function extractModuleFromPath(id, extraTrivial) {
|
|
8534
|
+
function extractModuleFromPath(id, extraTrivial, extraSkipSegments) {
|
|
8290
8535
|
const segments = id.split("/");
|
|
8291
8536
|
const routeGroups = extractRouteGroups(id);
|
|
8537
|
+
const skipSegments = new Set(SKIP_SEGMENTS_BUILTIN);
|
|
8538
|
+
if (extraSkipSegments) {
|
|
8539
|
+
for (const s of extraSkipSegments) skipSegments.add(s);
|
|
8540
|
+
}
|
|
8292
8541
|
const moduleGroups = routeGroups.filter((g) => !isTrivialGroup(g, extraTrivial)).map(normalizeGroupName);
|
|
8293
8542
|
if (moduleGroups.length > 0) {
|
|
8294
8543
|
return moduleGroups[moduleGroups.length - 1];
|
|
@@ -8299,7 +8548,7 @@ function extractModuleFromPath(id, extraTrivial) {
|
|
|
8299
8548
|
if (isRouteGroup(seg)) continue;
|
|
8300
8549
|
if (isDynamicSegment(seg)) continue;
|
|
8301
8550
|
if (isDomainDir(seg)) continue;
|
|
8302
|
-
if (
|
|
8551
|
+
if (skipSegments.has(seg)) continue;
|
|
8303
8552
|
meaningful.push(seg);
|
|
8304
8553
|
}
|
|
8305
8554
|
if (meaningful.length > 0) {
|
|
@@ -8316,12 +8565,10 @@ var moduleTagger = {
|
|
|
8316
8565
|
layers: null,
|
|
8317
8566
|
// applies to all layers
|
|
8318
8567
|
tag(nodes, layer, rootDir) {
|
|
8319
|
-
if (cachedRootDir !== rootDir) {
|
|
8320
|
-
cachedConventionDirs = detectConventionDirs(rootDir);
|
|
8321
|
-
cachedRootDir = rootDir;
|
|
8322
|
-
}
|
|
8323
8568
|
let configRules = [];
|
|
8324
8569
|
let extraTrivial;
|
|
8570
|
+
let extraSkipSegments;
|
|
8571
|
+
let extraConventionDirs = [];
|
|
8325
8572
|
try {
|
|
8326
8573
|
const { loadConfig: loadConfig2 } = (init_config(), __toCommonJS(config_exports));
|
|
8327
8574
|
const config = loadConfig2(rootDir);
|
|
@@ -8330,8 +8577,21 @@ var moduleTagger = {
|
|
|
8330
8577
|
if (trivialFromConfig?.length) {
|
|
8331
8578
|
extraTrivial = new Set(trivialFromConfig);
|
|
8332
8579
|
}
|
|
8580
|
+
const skipFromConfig = config.taggers?.module?.skipSegments;
|
|
8581
|
+
if (skipFromConfig?.length) {
|
|
8582
|
+
extraSkipSegments = new Set(skipFromConfig);
|
|
8583
|
+
}
|
|
8584
|
+
extraConventionDirs = config.taggers?.module?.conventionDirs ?? [];
|
|
8585
|
+
const roleNamesFromConfig = config.taggers?.module?.genericRoleNames;
|
|
8586
|
+
if (roleNamesFromConfig?.length) {
|
|
8587
|
+
for (const name of roleNamesFromConfig) GENERIC_ROLE_NAMES_BUILTIN.add(name);
|
|
8588
|
+
}
|
|
8333
8589
|
} catch {
|
|
8334
8590
|
}
|
|
8591
|
+
if (cachedRootDir !== rootDir) {
|
|
8592
|
+
cachedConventionDirs = detectConventionDirs(rootDir, extraConventionDirs);
|
|
8593
|
+
cachedRootDir = rootDir;
|
|
8594
|
+
}
|
|
8335
8595
|
const result = /* @__PURE__ */ new Map();
|
|
8336
8596
|
for (const node of nodes) {
|
|
8337
8597
|
const id = node.id;
|
|
@@ -8357,7 +8617,7 @@ var moduleTagger = {
|
|
|
8357
8617
|
}
|
|
8358
8618
|
}
|
|
8359
8619
|
if (matched) continue;
|
|
8360
|
-
const module2 = extractModuleFromPath(id, extraTrivial);
|
|
8620
|
+
const module2 = extractModuleFromPath(id, extraTrivial, extraSkipSegments);
|
|
8361
8621
|
result.set(id, module2);
|
|
8362
8622
|
}
|
|
8363
8623
|
return result;
|
|
@@ -8585,7 +8845,13 @@ function readAllGraphs(rootDir) {
|
|
|
8585
8845
|
}
|
|
8586
8846
|
return result;
|
|
8587
8847
|
}
|
|
8588
|
-
function generateGraph(rootDir, layer) {
|
|
8848
|
+
async function generateGraph(rootDir, layer) {
|
|
8849
|
+
await initTreeSitter();
|
|
8850
|
+
const config = loadConfig(rootDir);
|
|
8851
|
+
setExtractorConfig({
|
|
8852
|
+
dbIdentifiers: config.parsers?.patterns?.dbIdentifiers,
|
|
8853
|
+
mutationMethods: config.parsers?.patterns?.mutationMethods
|
|
8854
|
+
});
|
|
8589
8855
|
const dir = graphsDir(rootDir);
|
|
8590
8856
|
(0, import_node_fs11.mkdirSync)(dir, { recursive: true });
|
|
8591
8857
|
const results = layer ? [generateLayer(rootDir, layer)].filter((r) => r !== null) : generateAll(rootDir);
|
|
@@ -8610,11 +8876,11 @@ function parseLayerFlag(args) {
|
|
|
8610
8876
|
}
|
|
8611
8877
|
return value;
|
|
8612
8878
|
}
|
|
8613
|
-
function handleGraphCommand(subcommand, args) {
|
|
8879
|
+
async function handleGraphCommand(subcommand, args) {
|
|
8614
8880
|
const rootDir = process.cwd();
|
|
8615
8881
|
if (subcommand === "graph:generate") {
|
|
8616
8882
|
const layer = parseLayerFlag(args);
|
|
8617
|
-
const results = generateGraph(rootDir, layer);
|
|
8883
|
+
const results = await generateGraph(rootDir, layer);
|
|
8618
8884
|
if (results.length === 0) {
|
|
8619
8885
|
console.error(
|
|
8620
8886
|
layer ? `No parser detected for the "${layer}" layer in this project.` : "No parsers detected for this project. Check that the project has the expected structure."
|
|
@@ -8768,7 +9034,7 @@ var TOOLS = [
|
|
|
8768
9034
|
},
|
|
8769
9035
|
{
|
|
8770
9036
|
name: "read_graph",
|
|
8771
|
-
description: 'Query the structural project graph \u2014
|
|
9037
|
+
description: 'Query the structural project graph \u2014 use INSTEAD of Glob and Grep for locating files, understanding structure, and navigating the codebase. Faster and more accurate than file-system search because it returns typed nodes with metadata and relationships. \n\nUSE THIS FOR: "where is X", "what files are in module Y", "what pages exist under /admin", "what components does Z render", "what tables relate to User", "list all hooks in auth module", "which endpoints touch the User table", "what auth strategy does this endpoint use". \n\nDO NOT USE FOR: understanding what\'s INSIDE a component (use inspect_node for elements, conditions, state, variables, responses), reading actual source code (use Read). \n\nQUERY PARAMS (at least one required for node data \u2014 unfiltered calls return summary only to stay in context):\n- search: substring match on node id, name, or route\n- type: filter by node type (ui layer: page, layout, component, ui, hook, context, config, util; api layer: endpoint; db layer: table, enum)\n- module: filter by module tag (computed from directory structure, e.g. "auth", "admin", "settings")\n- node_id: return this node + its neighborhood (incoming+outgoing edges within `hops`)\n- hops: neighborhood radius when node_id is set (default 1)\n- minimal: return only id/type/name/module/route per node (skip heavy fields like columns, exports)\n- include_edges: return the actual edge list. Default: TRUE for neighborhood queries (node_id), FALSE for filter queries (search/type/module). Filter responses always include `edge_count`; only pass include_edges:true when you actually need to inspect individual edges (e.g. "which components render X"). This default cuts typical filter responses in half.\n\nBATCH MODE: pass `queries` (array of query objects) to run multiple independent queries in a single call. Each query object uses the same params (layer/search/type/module/node_id/hops/minimal). Returns { batch: true, count, results: [{index, query, result}, ...] }. Use this when you need multiple graph views up-front (e.g. scoping a feature across ui+api+db layers) to save round-trips. When batch mode is used, top-level params are ignored.\n\nReturns: filtered nodes + edges between them. If no filter given, returns per-layer counts and type breakdown only.\n\nWIRE FORMAT (compact): responses that include nodes/edges use short keys and edge-by-index refs to cut payload ~40-60%. Every such response carries a `_schema` legend. Quick reference:\n nodes[]: { i: id, t: type, n: name, m: module, r: route, mt: methods, x: exports, c: columns }\n edges[]: { s: source_node_index, d: target_node_index, t: type, l: label }\nedges.s / edges.d are 0-based indices into THIS response\'s nodes array. If a referenced node is not in the response (boundary case), s/d may instead contain the full node id string \u2014 always check the type.\n\nPAGINATION (filter queries):\n- Use `offset` and `limit` to paginate through large result sets.\n- Response includes: `total` (matched), `returned` (in this page), `has_more`, `next_offset`.\n- If `has_more: true`, call again with `offset: next_offset` to get the next page.\n\nBUDGET GUARDS:\n- Neighborhood queries stop expanding when the projected response exceeds budget. The response then contains `budget_exceeded: true` plus `hops_traversed < hops_requested`. When this happens, drill into a specific neighbor with another node_id call rather than retrying with larger hops \u2014 it will just truncate again.\n- Batch mode caps total response size. Once the budget is hit, later queries return `{skipped: true, reason: "batch_budget_exhausted"}` and you must re-run them individually.',
|
|
8772
9038
|
inputSchema: {
|
|
8773
9039
|
type: "object",
|
|
8774
9040
|
properties: {
|
|
@@ -8813,6 +9079,14 @@ var TOOLS = [
|
|
|
8813
9079
|
type: "boolean",
|
|
8814
9080
|
description: "Include the edge list in the response. Default TRUE for neighborhood queries (node_id), FALSE for filter queries. Filter responses always include edge_count. Only set true on filter queries when you actually need edge data."
|
|
8815
9081
|
},
|
|
9082
|
+
offset: {
|
|
9083
|
+
type: "number",
|
|
9084
|
+
description: "Skip first N matched nodes (pagination). Default 0. Use next_offset from a previous response to get the next page."
|
|
9085
|
+
},
|
|
9086
|
+
limit: {
|
|
9087
|
+
type: "number",
|
|
9088
|
+
description: "Max nodes to return. Default: all matched nodes. Use with offset for pagination."
|
|
9089
|
+
},
|
|
8816
9090
|
queries: {
|
|
8817
9091
|
type: "array",
|
|
8818
9092
|
description: "Batch mode \u2014 array of query objects to run in a single call. Each uses the same param schema. When set, top-level params are ignored. Subject to an aggregate size budget \u2014 later queries may return a skipped stub.",
|
|
@@ -8835,7 +9109,7 @@ var TOOLS = [
|
|
|
8835
9109
|
},
|
|
8836
9110
|
{
|
|
8837
9111
|
name: "grep_nodes",
|
|
8838
|
-
description: `Search for text patterns WITHIN files selected by the project graph.
|
|
9112
|
+
description: `Search for text patterns WITHIN files selected by the project graph. Use INSTEAD of Grep when searching within code \u2014 combines structural filtering (type/module/neighborhood) with regex content search. Narrower than plain Grep because it only scans files matching the graph filter, reducing noise from tests, docs, generated code, unrelated modules.
|
|
8839
9113
|
|
|
8840
9114
|
USE THIS FOR: "which auth hooks use JWT decoding", "find TODO comments in pages only", "which deployment writers call Sentry", "what validation schemas exist in form components". It's grep scoped to a structurally-selected file set.
|
|
8841
9115
|
|
|
@@ -8881,6 +9155,40 @@ Returns: { pattern, filter, files_searched, total_matches, matches: [{file, line
|
|
|
8881
9155
|
required: ["layer", "pattern"]
|
|
8882
9156
|
}
|
|
8883
9157
|
},
|
|
9158
|
+
{
|
|
9159
|
+
name: "inspect_node",
|
|
9160
|
+
description: `Get deep AST data for specific graph nodes \u2014 what's INSIDE a component or endpoint. Returns elements (JSX), state hooks, conditions, variables, responses, and request params. Use INSTEAD of Grep/Read when you need to understand component internals without reading source.
|
|
9161
|
+
|
|
9162
|
+
USE THIS FOR: "what elements does LoginPage have?", "what conditions does the login endpoint check?", "what state does SettingsPage manage?", "what responses can this endpoint return?", "what validation does this API do?", "what props does this component accept?"
|
|
9163
|
+
|
|
9164
|
+
DO NOT USE FOR: structural queries (use read_graph), content search (use grep_nodes).
|
|
9165
|
+
|
|
9166
|
+
Returns deep fields only \u2014 not structural metadata (use read_graph for that).`,
|
|
9167
|
+
inputSchema: {
|
|
9168
|
+
type: "object",
|
|
9169
|
+
properties: {
|
|
9170
|
+
layer: {
|
|
9171
|
+
type: "string",
|
|
9172
|
+
enum: ["ui", "api", "db"],
|
|
9173
|
+
description: "Graph layer (required)."
|
|
9174
|
+
},
|
|
9175
|
+
node_id: {
|
|
9176
|
+
type: "string",
|
|
9177
|
+
description: "Node ID to inspect. Use read_graph to find node IDs first."
|
|
9178
|
+
},
|
|
9179
|
+
search: {
|
|
9180
|
+
type: "string",
|
|
9181
|
+
description: "Substring match on node id/name/route. Returns deep data for all matching nodes (max 5)."
|
|
9182
|
+
},
|
|
9183
|
+
fields: {
|
|
9184
|
+
type: "array",
|
|
9185
|
+
items: { type: "string" },
|
|
9186
|
+
description: "Specific deep fields to return. Options: elements, stateVars, conditions, variables, responses, params. Omit for all."
|
|
9187
|
+
}
|
|
9188
|
+
},
|
|
9189
|
+
required: ["layer"]
|
|
9190
|
+
}
|
|
9191
|
+
},
|
|
8884
9192
|
{
|
|
8885
9193
|
name: "chart_server_status",
|
|
8886
9194
|
description: `Check whether the launch-chart UI server is running. Returns: { running: boolean, url?: string, port?: number, pid?: number, startedAt?: string, cwd?: string }.
|
|
@@ -9009,6 +9317,14 @@ var COMPACT_NODE_KNOWN_KEYS = /* @__PURE__ */ new Set([
|
|
|
9009
9317
|
"columns",
|
|
9010
9318
|
"tags"
|
|
9011
9319
|
]);
|
|
9320
|
+
var DEEP_FIELDS = /* @__PURE__ */ new Set([
|
|
9321
|
+
"elements",
|
|
9322
|
+
"stateVars",
|
|
9323
|
+
"conditions",
|
|
9324
|
+
"variables",
|
|
9325
|
+
"responses",
|
|
9326
|
+
"params"
|
|
9327
|
+
]);
|
|
9012
9328
|
var EST_CHARS_PER_NODE_FULL = {
|
|
9013
9329
|
ui: 300,
|
|
9014
9330
|
api: 300,
|
|
@@ -9036,7 +9352,7 @@ function toCompactNode(n) {
|
|
|
9036
9352
|
if (n.columns != null) out.c = n.columns;
|
|
9037
9353
|
if (tags != null) out.tg = tags;
|
|
9038
9354
|
for (const k of Object.keys(n)) {
|
|
9039
|
-
if (!COMPACT_NODE_KNOWN_KEYS.has(k) && n[k] != null) out[k] = n[k];
|
|
9355
|
+
if (!COMPACT_NODE_KNOWN_KEYS.has(k) && !DEEP_FIELDS.has(k) && n[k] != null) out[k] = n[k];
|
|
9040
9356
|
}
|
|
9041
9357
|
return out;
|
|
9042
9358
|
}
|
|
@@ -9136,13 +9452,13 @@ function okJson(data) {
|
|
|
9136
9452
|
function err(text) {
|
|
9137
9453
|
return { content: [{ type: "text", text }], isError: true };
|
|
9138
9454
|
}
|
|
9139
|
-
function handleGenerateGraph(args) {
|
|
9455
|
+
async function handleGenerateGraph(args) {
|
|
9140
9456
|
const rootDir = process.cwd();
|
|
9141
9457
|
const layer = args.layer;
|
|
9142
9458
|
if (layer && !["ui", "api", "db"].includes(layer)) {
|
|
9143
9459
|
return err(`Invalid layer "${layer}". Must be one of: ui, api, db`);
|
|
9144
9460
|
}
|
|
9145
|
-
const results = generateGraph(rootDir, layer);
|
|
9461
|
+
const results = await generateGraph(rootDir, layer);
|
|
9146
9462
|
if (results.length === 0) {
|
|
9147
9463
|
return err(
|
|
9148
9464
|
layer ? `No parser detected for the "${layer}" layer in this project.` : "No parsers detected for this project. Check that the project has the expected structure."
|
|
@@ -9175,6 +9491,8 @@ function runReadGraphQueryRaw(rootDir, args) {
|
|
|
9175
9491
|
const layerIsDb = args.layer === "db";
|
|
9176
9492
|
const minimal = args.minimal ?? layerIsDb;
|
|
9177
9493
|
const includeEdges = args.include_edges;
|
|
9494
|
+
const offset = args.offset ?? 0;
|
|
9495
|
+
const limit = args.limit;
|
|
9178
9496
|
const hasFilter = !!(search || type || module_ || nodeId || tagKey && tagValue);
|
|
9179
9497
|
if (layer && !["ui", "api", "db"].includes(layer)) {
|
|
9180
9498
|
return { error: `Invalid layer "${layer}". Must be one of: ui, api, db` };
|
|
@@ -9246,18 +9564,29 @@ function runReadGraphQueryRaw(rootDir, args) {
|
|
|
9246
9564
|
hint: "No nodes matched. Check spelling, or call read_graph without filter to see the summary and available types/modules."
|
|
9247
9565
|
};
|
|
9248
9566
|
}
|
|
9567
|
+
const totalMatched = matched.length;
|
|
9568
|
+
const paginatedNodes = limit != null ? matched.slice(offset, offset + limit) : matched.slice(offset);
|
|
9569
|
+
const hasMore = offset + paginatedNodes.length < totalMatched;
|
|
9249
9570
|
const wantEdges = includeEdges ?? false;
|
|
9571
|
+
const returnedIds = new Set(paginatedNodes.map((n) => n.id));
|
|
9572
|
+
const returnedEdges = graph.edges.filter((e) => returnedIds.has(e.source) && returnedIds.has(e.target));
|
|
9250
9573
|
const result = {
|
|
9251
9574
|
layer,
|
|
9252
9575
|
filter: { search, type, module: module_ },
|
|
9253
|
-
|
|
9254
|
-
|
|
9255
|
-
|
|
9576
|
+
total: totalMatched,
|
|
9577
|
+
returned: paginatedNodes.length,
|
|
9578
|
+
offset,
|
|
9579
|
+
has_more: hasMore,
|
|
9580
|
+
edge_count: returnedEdges.length,
|
|
9581
|
+
nodes: minimal ? toMinimal(paginatedNodes) : paginatedNodes
|
|
9256
9582
|
};
|
|
9583
|
+
if (hasMore) {
|
|
9584
|
+
result.next_offset = offset + paginatedNodes.length;
|
|
9585
|
+
}
|
|
9257
9586
|
if (wantEdges) {
|
|
9258
|
-
result.edges =
|
|
9259
|
-
} else {
|
|
9260
|
-
result.edges_hint = `${
|
|
9587
|
+
result.edges = returnedEdges;
|
|
9588
|
+
} else if (returnedEdges.length > 0) {
|
|
9589
|
+
result.edges_hint = `${returnedEdges.length} edges between matched nodes omitted. Pass include_edges:true to retrieve them (only do this when you actually need edge data).`;
|
|
9261
9590
|
}
|
|
9262
9591
|
return result;
|
|
9263
9592
|
}
|
|
@@ -9325,6 +9654,48 @@ function nodeToFilePath(rootDir, layer, nodeId) {
|
|
|
9325
9654
|
if (layer === "db") return (0, import_node_path15.join)(rootDir, "prisma", "schema.prisma");
|
|
9326
9655
|
return null;
|
|
9327
9656
|
}
|
|
9657
|
+
function handleInspectNode(args) {
|
|
9658
|
+
const rootDir = process.cwd();
|
|
9659
|
+
const layer = args.layer;
|
|
9660
|
+
const nodeId = args.node_id;
|
|
9661
|
+
const search = args.search;
|
|
9662
|
+
const fields = args.fields;
|
|
9663
|
+
if (!layer) return err("layer is required.");
|
|
9664
|
+
if (!nodeId && !search) return err("Either node_id or search is required.");
|
|
9665
|
+
const graph = readGraph(rootDir, layer);
|
|
9666
|
+
if (!graph) return err(`No graph found for layer "${layer}". Run generate_graph first.`);
|
|
9667
|
+
let matched;
|
|
9668
|
+
if (nodeId) {
|
|
9669
|
+
const node = graph.nodes.find((n) => n.id === nodeId);
|
|
9670
|
+
if (!node) return err(`Node "${nodeId}" not found in ${layer} layer.`);
|
|
9671
|
+
matched = [node];
|
|
9672
|
+
} else {
|
|
9673
|
+
const searchLower = search.toLowerCase();
|
|
9674
|
+
matched = graph.nodes.filter(
|
|
9675
|
+
(n) => n.id.toLowerCase().includes(searchLower) || n.name.toLowerCase().includes(searchLower) || n.route?.toLowerCase().includes(searchLower)
|
|
9676
|
+
);
|
|
9677
|
+
}
|
|
9678
|
+
if (matched.length === 0) return err(`No nodes matching "${search}" in ${layer} layer.`);
|
|
9679
|
+
if (matched.length > 5) {
|
|
9680
|
+
return err(`${matched.length} nodes match "${search}". Narrow your search (max 5 for inspect_node).`);
|
|
9681
|
+
}
|
|
9682
|
+
const allDeepFields = ["elements", "stateVars", "conditions", "variables", "responses", "params"];
|
|
9683
|
+
const requestedFields = fields ?? allDeepFields;
|
|
9684
|
+
const results = matched.map((node) => {
|
|
9685
|
+
const deep = { id: node.id, name: node.name, type: node.type };
|
|
9686
|
+
for (const field of requestedFields) {
|
|
9687
|
+
if (allDeepFields.includes(field) && node[field] != null) {
|
|
9688
|
+
deep[field] = node[field];
|
|
9689
|
+
}
|
|
9690
|
+
}
|
|
9691
|
+
return deep;
|
|
9692
|
+
});
|
|
9693
|
+
return okJson({
|
|
9694
|
+
layer,
|
|
9695
|
+
matched: results.length,
|
|
9696
|
+
nodes: results
|
|
9697
|
+
});
|
|
9698
|
+
}
|
|
9328
9699
|
function handleGrepNodes(args) {
|
|
9329
9700
|
const rootDir = process.cwd();
|
|
9330
9701
|
const pattern = args.pattern;
|
|
@@ -9587,7 +9958,7 @@ function respond(id, result) {
|
|
|
9587
9958
|
function respondError(id, code, message) {
|
|
9588
9959
|
send({ jsonrpc: "2.0", id, error: { code, message } });
|
|
9589
9960
|
}
|
|
9590
|
-
function handleMessage(msg) {
|
|
9961
|
+
async function handleMessage(msg) {
|
|
9591
9962
|
const method = msg.method;
|
|
9592
9963
|
const id = msg.id;
|
|
9593
9964
|
if (method === "initialize") {
|
|
@@ -9610,7 +9981,7 @@ function handleMessage(msg) {
|
|
|
9610
9981
|
const toolName = params.name;
|
|
9611
9982
|
const args = params.arguments ?? {};
|
|
9612
9983
|
if (toolName === "generate_graph") {
|
|
9613
|
-
respond(id ?? null, handleGenerateGraph(args));
|
|
9984
|
+
respond(id ?? null, await handleGenerateGraph(args));
|
|
9614
9985
|
return;
|
|
9615
9986
|
}
|
|
9616
9987
|
if (toolName === "read_graph") {
|
|
@@ -9621,6 +9992,10 @@ function handleMessage(msg) {
|
|
|
9621
9992
|
respond(id ?? null, handleGrepNodes(args));
|
|
9622
9993
|
return;
|
|
9623
9994
|
}
|
|
9995
|
+
if (toolName === "inspect_node") {
|
|
9996
|
+
respond(id ?? null, handleInspectNode(args));
|
|
9997
|
+
return;
|
|
9998
|
+
}
|
|
9624
9999
|
if (toolName === "chart_server_status") {
|
|
9625
10000
|
respond(id ?? null, handleChartServerStatus());
|
|
9626
10001
|
return;
|