@neat.is/core 0.7.3 → 0.7.4
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/{chunk-3SBEY67S.js → chunk-KN6O7KMK.js} +2 -2
- package/dist/{chunk-BY6ZP5WA.js → chunk-UU4XKSTO.js} +3734 -3418
- package/dist/chunk-UU4XKSTO.js.map +1 -0
- package/dist/cli.cjs +1861 -1533
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +18 -2
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +1192 -878
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +2 -2
- package/dist/neatd.cjs +1192 -878
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +2 -2
- package/dist/server.cjs +1256 -942
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +1 -1
- package/package.json +2 -2
- package/dist/chunk-BY6ZP5WA.js.map +0 -1
- /package/dist/{chunk-3SBEY67S.js.map → chunk-KN6O7KMK.js.map} +0 -0
package/dist/neatd.cjs
CHANGED
|
@@ -780,8 +780,8 @@ init_cjs_shims();
|
|
|
780
780
|
|
|
781
781
|
// src/ingest.ts
|
|
782
782
|
init_cjs_shims();
|
|
783
|
-
var
|
|
784
|
-
var
|
|
783
|
+
var import_node_fs7 = require("fs");
|
|
784
|
+
var import_node_path8 = __toESM(require("path"), 1);
|
|
785
785
|
var sourceMapJs = __toESM(require("source-map-js"), 1);
|
|
786
786
|
|
|
787
787
|
// src/policy.ts
|
|
@@ -2130,16 +2130,16 @@ var PolicyViolationsLog = class {
|
|
|
2130
2130
|
};
|
|
2131
2131
|
|
|
2132
2132
|
// src/ingest.ts
|
|
2133
|
-
var
|
|
2133
|
+
var import_types8 = require("@neat.is/types");
|
|
2134
2134
|
|
|
2135
2135
|
// src/extract/routes.ts
|
|
2136
2136
|
init_cjs_shims();
|
|
2137
|
-
var
|
|
2138
|
-
var
|
|
2139
|
-
var
|
|
2140
|
-
var
|
|
2141
|
-
var
|
|
2142
|
-
var
|
|
2137
|
+
var import_node_path7 = __toESM(require("path"), 1);
|
|
2138
|
+
var import_tree_sitter2 = __toESM(require("tree-sitter"), 1);
|
|
2139
|
+
var import_tree_sitter_javascript2 = __toESM(require("tree-sitter-javascript"), 1);
|
|
2140
|
+
var import_tree_sitter_python2 = __toESM(require("tree-sitter-python"), 1);
|
|
2141
|
+
var import_tree_sitter_go2 = __toESM(require("tree-sitter-go"), 1);
|
|
2142
|
+
var import_types6 = require("@neat.is/types");
|
|
2143
2143
|
|
|
2144
2144
|
// src/extract/shared.ts
|
|
2145
2145
|
init_cjs_shims();
|
|
@@ -2510,7 +2510,15 @@ function ensureFileNode(graph, serviceName, serviceNodeId, relPath) {
|
|
|
2510
2510
|
return { fileNodeId, nodesAdded, edgesAdded };
|
|
2511
2511
|
}
|
|
2512
2512
|
|
|
2513
|
-
// src/extract/
|
|
2513
|
+
// src/extract/imports.ts
|
|
2514
|
+
init_cjs_shims();
|
|
2515
|
+
var import_node_path6 = __toESM(require("path"), 1);
|
|
2516
|
+
var import_node_fs6 = require("fs");
|
|
2517
|
+
var import_tree_sitter = __toESM(require("tree-sitter"), 1);
|
|
2518
|
+
var import_tree_sitter_javascript = __toESM(require("tree-sitter-javascript"), 1);
|
|
2519
|
+
var import_tree_sitter_python = __toESM(require("tree-sitter-python"), 1);
|
|
2520
|
+
var import_tree_sitter_go = __toESM(require("tree-sitter-go"), 1);
|
|
2521
|
+
var import_types5 = require("@neat.is/types");
|
|
2514
2522
|
var PARSE_CHUNK = 16384;
|
|
2515
2523
|
function parseSource(parser, source) {
|
|
2516
2524
|
return parser.parse(
|
|
@@ -2532,155 +2540,532 @@ function makeGoParser() {
|
|
|
2532
2540
|
p.setLanguage(import_tree_sitter_go.default);
|
|
2533
2541
|
return p;
|
|
2534
2542
|
}
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
"
|
|
2543
|
-
"all"
|
|
2544
|
-
]);
|
|
2545
|
-
var NEXT_APP_METHODS = /* @__PURE__ */ new Set(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"]);
|
|
2546
|
-
var JS_ROUTE_EXTENSIONS = /* @__PURE__ */ new Set([".js", ".jsx", ".mjs", ".cjs", ".ts", ".tsx"]);
|
|
2547
|
-
function ginRoutesFromSource(source, parser) {
|
|
2548
|
-
const tree = parseSource(parser, source);
|
|
2549
|
-
const prefixes = /* @__PURE__ */ new Map();
|
|
2550
|
-
const out = [];
|
|
2551
|
-
walk(tree.rootNode, (node) => {
|
|
2552
|
-
if (node.type === "short_var_declaration" || node.type === "var_spec") {
|
|
2553
|
-
const name = node.childForFieldName("left")?.namedChild(0)?.text ?? node.childForFieldName("name")?.text;
|
|
2554
|
-
const value = node.childForFieldName("right")?.namedChild(0) ?? node.childForFieldName("value");
|
|
2555
|
-
if (name && value?.type === "call_expression") {
|
|
2556
|
-
const fn2 = value.childForFieldName("function");
|
|
2557
|
-
const field = fn2?.childForFieldName("field")?.text;
|
|
2558
|
-
const first2 = value.childForFieldName("arguments")?.namedChild(0);
|
|
2559
|
-
if (field === "Group" && first2?.type === "interpreted_string_literal") {
|
|
2560
|
-
prefixes.set(name, first2.text.slice(1, -1));
|
|
2561
|
-
}
|
|
2562
|
-
}
|
|
2563
|
-
return;
|
|
2564
|
-
}
|
|
2565
|
-
if (node.type !== "call_expression") return;
|
|
2566
|
-
const fn = node.childForFieldName("function");
|
|
2567
|
-
if (fn?.type !== "selector_expression") return;
|
|
2568
|
-
const method = fn.childForFieldName("field")?.text?.toUpperCase();
|
|
2569
|
-
if (!method || !ROUTER_METHODS.has(method.toLowerCase())) return;
|
|
2570
|
-
const receiver = fn.childForFieldName("operand")?.text ?? "";
|
|
2571
|
-
const first = node.childForFieldName("arguments")?.namedChild(0);
|
|
2572
|
-
if (first?.type !== "interpreted_string_literal") return;
|
|
2573
|
-
const leaf = first.text.slice(1, -1);
|
|
2574
|
-
out.push({
|
|
2575
|
-
method: method === "ALL" ? "ALL" : method,
|
|
2576
|
-
pathTemplate: canonicalizeTemplate((prefixes.get(receiver) ?? "") + leaf),
|
|
2577
|
-
line: node.startPosition.row + 1,
|
|
2578
|
-
framework: "gin"
|
|
2579
|
-
});
|
|
2580
|
-
});
|
|
2581
|
-
return out;
|
|
2582
|
-
}
|
|
2583
|
-
var FASTAPI_METHODS = /* @__PURE__ */ new Set(["get", "post", "put", "patch", "delete", "options", "head", "trace"]);
|
|
2584
|
-
var NESTJS_METHODS = /* @__PURE__ */ new Map([
|
|
2585
|
-
["Get", "GET"],
|
|
2586
|
-
["Post", "POST"],
|
|
2587
|
-
["Put", "PUT"],
|
|
2588
|
-
["Patch", "PATCH"],
|
|
2589
|
-
["Delete", "DELETE"],
|
|
2590
|
-
["Options", "OPTIONS"],
|
|
2591
|
-
["Head", "HEAD"],
|
|
2592
|
-
["All", "ALL"]
|
|
2593
|
-
]);
|
|
2594
|
-
function canonicalizeTemplate(raw) {
|
|
2595
|
-
let p = raw.split("?")[0].split("#")[0];
|
|
2596
|
-
if (!p.startsWith("/")) p = "/" + p;
|
|
2597
|
-
if (p.length > 1 && p.endsWith("/")) p = p.slice(0, -1);
|
|
2598
|
-
return p;
|
|
2599
|
-
}
|
|
2600
|
-
function isDynamicSegment(seg) {
|
|
2601
|
-
if (seg.length === 0) return false;
|
|
2602
|
-
if (seg.includes(":")) return true;
|
|
2603
|
-
if (seg.startsWith("{") || seg.startsWith("[")) return true;
|
|
2604
|
-
if (/^\d+$/.test(seg)) return true;
|
|
2605
|
-
if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(seg)) return true;
|
|
2606
|
-
if (/^[0-9a-f]{24,}$/i.test(seg)) return true;
|
|
2607
|
-
return false;
|
|
2543
|
+
function stringLiteralText(node) {
|
|
2544
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
2545
|
+
const child = node.child(i);
|
|
2546
|
+
if (child?.type === "string_fragment") return child.text;
|
|
2547
|
+
}
|
|
2548
|
+
const raw = node.text;
|
|
2549
|
+
if (raw.length >= 2) return raw.slice(1, -1);
|
|
2550
|
+
return raw.length === 0 ? null : "";
|
|
2608
2551
|
}
|
|
2609
|
-
function
|
|
2610
|
-
const
|
|
2611
|
-
|
|
2612
|
-
const normalised = segments.map((seg) => isDynamicSegment(seg) ? ":param" : seg.toLowerCase());
|
|
2613
|
-
return "/" + normalised.join("/");
|
|
2552
|
+
function clipSnippet(text) {
|
|
2553
|
+
const oneLine = text.split("\n")[0] ?? text;
|
|
2554
|
+
return oneLine.length > 120 ? oneLine.slice(0, 120) : oneLine;
|
|
2614
2555
|
}
|
|
2615
|
-
function
|
|
2616
|
-
|
|
2556
|
+
function collectGoImports(node, out) {
|
|
2557
|
+
if (node.type === "import_spec") {
|
|
2558
|
+
const pathNode = node.childForFieldName("path");
|
|
2559
|
+
if (pathNode) {
|
|
2560
|
+
const specifier = pathNode.text.replace(/^`|`$/g, "").replace(/^"|"$/g, "");
|
|
2561
|
+
if (specifier) out.push({ specifier, line: node.startPosition.row + 1, snippet: clipSnippet(node.text) });
|
|
2562
|
+
}
|
|
2563
|
+
return;
|
|
2564
|
+
}
|
|
2617
2565
|
for (let i = 0; i < node.namedChildCount; i++) {
|
|
2618
2566
|
const child = node.namedChild(i);
|
|
2619
|
-
if (child)
|
|
2567
|
+
if (child) collectGoImports(child, out);
|
|
2620
2568
|
}
|
|
2621
2569
|
}
|
|
2622
|
-
function
|
|
2623
|
-
if (node.type === "
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2570
|
+
function collectJsImports(node, out) {
|
|
2571
|
+
if (node.type === "import_statement") {
|
|
2572
|
+
const source = node.childForFieldName("source");
|
|
2573
|
+
if (source) {
|
|
2574
|
+
const specifier = stringLiteralText(source);
|
|
2575
|
+
if (specifier) {
|
|
2576
|
+
out.push({ specifier, line: node.startPosition.row + 1, snippet: clipSnippet(node.text) });
|
|
2577
|
+
}
|
|
2627
2578
|
}
|
|
2628
|
-
return
|
|
2579
|
+
return;
|
|
2629
2580
|
}
|
|
2630
|
-
if (node.type === "
|
|
2631
|
-
|
|
2632
|
-
|
|
2581
|
+
if (node.type === "call_expression") {
|
|
2582
|
+
const fn = node.childForFieldName("function");
|
|
2583
|
+
if (fn?.type === "identifier" && fn.text === "require") {
|
|
2584
|
+
const args = node.childForFieldName("arguments");
|
|
2585
|
+
const firstArg = args?.namedChild(0);
|
|
2586
|
+
if (firstArg?.type === "string") {
|
|
2587
|
+
const specifier = stringLiteralText(firstArg);
|
|
2588
|
+
if (specifier) {
|
|
2589
|
+
out.push({ specifier, line: node.startPosition.row + 1, snippet: clipSnippet(node.text) });
|
|
2590
|
+
}
|
|
2591
|
+
}
|
|
2633
2592
|
}
|
|
2634
|
-
const raw = node.text;
|
|
2635
|
-
return raw.length >= 2 ? raw.slice(1, -1) : "";
|
|
2636
2593
|
}
|
|
2637
|
-
|
|
2594
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
2595
|
+
const child = node.namedChild(i);
|
|
2596
|
+
if (child) collectJsImports(child, out);
|
|
2597
|
+
}
|
|
2638
2598
|
}
|
|
2639
|
-
function
|
|
2640
|
-
|
|
2641
|
-
const
|
|
2642
|
-
if (
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2599
|
+
function collectImportedNames(node, out) {
|
|
2600
|
+
if (node.type === "aliased_import") {
|
|
2601
|
+
const nameNode = node.childForFieldName("name");
|
|
2602
|
+
if (nameNode) out.push(nameNode.text);
|
|
2603
|
+
return;
|
|
2604
|
+
}
|
|
2605
|
+
if (node.type === "dotted_name") {
|
|
2606
|
+
out.push(node.text);
|
|
2607
|
+
return;
|
|
2608
|
+
}
|
|
2609
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
2610
|
+
const child = node.namedChild(i);
|
|
2611
|
+
if (child) collectImportedNames(child, out);
|
|
2649
2612
|
}
|
|
2650
|
-
return null;
|
|
2651
2613
|
}
|
|
2652
|
-
function
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
const
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2614
|
+
function collectPyImports(node, out) {
|
|
2615
|
+
if (node.type === "import_from_statement") {
|
|
2616
|
+
let level = 0;
|
|
2617
|
+
let modulePath = "";
|
|
2618
|
+
const names = [];
|
|
2619
|
+
let pastFrom = false;
|
|
2620
|
+
let pastImport = false;
|
|
2621
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
2622
|
+
const child = node.child(i);
|
|
2623
|
+
if (!child) continue;
|
|
2624
|
+
if (!pastFrom) {
|
|
2625
|
+
if (child.type === "from") pastFrom = true;
|
|
2626
|
+
continue;
|
|
2627
|
+
}
|
|
2628
|
+
if (!pastImport) {
|
|
2629
|
+
if (child.type === "import") {
|
|
2630
|
+
pastImport = true;
|
|
2631
|
+
continue;
|
|
2632
|
+
}
|
|
2633
|
+
if (child.type === "relative_import") {
|
|
2634
|
+
for (let j = 0; j < child.childCount; j++) {
|
|
2635
|
+
const rc = child.child(j);
|
|
2636
|
+
if (!rc) continue;
|
|
2637
|
+
if (rc.type === "import_prefix") {
|
|
2638
|
+
for (let k = 0; k < rc.childCount; k++) {
|
|
2639
|
+
if (rc.child(k)?.type === ".") level++;
|
|
2640
|
+
}
|
|
2641
|
+
} else if (rc.type === "dotted_name") modulePath = rc.text;
|
|
2642
|
+
}
|
|
2643
|
+
} else if (child.type === "dotted_name") {
|
|
2644
|
+
modulePath = child.text;
|
|
2672
2645
|
}
|
|
2646
|
+
continue;
|
|
2673
2647
|
}
|
|
2674
|
-
|
|
2648
|
+
collectImportedNames(child, names);
|
|
2649
|
+
}
|
|
2650
|
+
if (level > 0 || modulePath) {
|
|
2651
|
+
out.push({ modulePath, level, names, line: node.startPosition.row + 1, snippet: clipSnippet(node.text) });
|
|
2675
2652
|
}
|
|
2676
2653
|
}
|
|
2677
|
-
|
|
2654
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
2655
|
+
const child = node.namedChild(i);
|
|
2656
|
+
if (child) collectPyImports(child, out);
|
|
2657
|
+
}
|
|
2678
2658
|
}
|
|
2679
|
-
function
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2659
|
+
async function fileExists(p) {
|
|
2660
|
+
try {
|
|
2661
|
+
await import_node_fs6.promises.access(p);
|
|
2662
|
+
return true;
|
|
2663
|
+
} catch {
|
|
2664
|
+
return false;
|
|
2665
|
+
}
|
|
2666
|
+
}
|
|
2667
|
+
function isWithinServiceDir(candidate, serviceDir) {
|
|
2668
|
+
const rel = import_node_path6.default.relative(serviceDir, candidate);
|
|
2669
|
+
return rel !== "" && !rel.startsWith("..") && !import_node_path6.default.isAbsolute(rel);
|
|
2670
|
+
}
|
|
2671
|
+
var JS_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"];
|
|
2672
|
+
var JS_INDEX_FILES = JS_EXTENSIONS.map((ext) => `index${ext}`);
|
|
2673
|
+
async function firstExistingCandidate(base, serviceDir) {
|
|
2674
|
+
for (const ext of JS_EXTENSIONS) {
|
|
2675
|
+
const candidate = base + ext;
|
|
2676
|
+
if (isWithinServiceDir(candidate, serviceDir) && await fileExists(candidate)) {
|
|
2677
|
+
return toPosix(import_node_path6.default.relative(serviceDir, candidate));
|
|
2678
|
+
}
|
|
2679
|
+
}
|
|
2680
|
+
for (const indexFile of JS_INDEX_FILES) {
|
|
2681
|
+
const candidate = import_node_path6.default.join(base, indexFile);
|
|
2682
|
+
if (isWithinServiceDir(candidate, serviceDir) && await fileExists(candidate)) {
|
|
2683
|
+
return toPosix(import_node_path6.default.relative(serviceDir, candidate));
|
|
2684
|
+
}
|
|
2685
|
+
}
|
|
2686
|
+
return null;
|
|
2687
|
+
}
|
|
2688
|
+
async function loadTsPathConfig(serviceDir) {
|
|
2689
|
+
const tsconfigPath = import_node_path6.default.join(serviceDir, "tsconfig.json");
|
|
2690
|
+
let raw;
|
|
2691
|
+
try {
|
|
2692
|
+
raw = await import_node_fs6.promises.readFile(tsconfigPath, "utf8");
|
|
2693
|
+
} catch {
|
|
2694
|
+
return null;
|
|
2695
|
+
}
|
|
2696
|
+
try {
|
|
2697
|
+
const parsed = JSON.parse(raw);
|
|
2698
|
+
const paths = parsed.compilerOptions?.paths;
|
|
2699
|
+
if (!paths || Object.keys(paths).length === 0) return null;
|
|
2700
|
+
const baseUrl = parsed.compilerOptions?.baseUrl;
|
|
2701
|
+
return { paths, baseDir: baseUrl ? import_node_path6.default.resolve(serviceDir, baseUrl) : serviceDir };
|
|
2702
|
+
} catch (err) {
|
|
2703
|
+
recordExtractionError("import alias resolution", tsconfigPath, err);
|
|
2704
|
+
return null;
|
|
2705
|
+
}
|
|
2706
|
+
}
|
|
2707
|
+
async function resolveTsAlias(specifier, config, serviceDir) {
|
|
2708
|
+
for (const [pattern, targets] of Object.entries(config.paths)) {
|
|
2709
|
+
let suffix = null;
|
|
2710
|
+
if (pattern === specifier) {
|
|
2711
|
+
suffix = "";
|
|
2712
|
+
} else if (pattern.endsWith("/*")) {
|
|
2713
|
+
const prefix = pattern.slice(0, -1);
|
|
2714
|
+
if (specifier.startsWith(prefix)) suffix = specifier.slice(prefix.length);
|
|
2715
|
+
}
|
|
2716
|
+
if (suffix === null) continue;
|
|
2717
|
+
for (const target of targets) {
|
|
2718
|
+
const targetBase = target.endsWith("/*") ? target.slice(0, -2) : target.replace(/\*$/, "");
|
|
2719
|
+
const resolvedBase = import_node_path6.default.resolve(config.baseDir, targetBase, suffix);
|
|
2720
|
+
const hit = await firstExistingCandidate(resolvedBase, serviceDir);
|
|
2721
|
+
if (hit) return hit;
|
|
2722
|
+
if (isWithinServiceDir(resolvedBase, serviceDir) && await fileExists(resolvedBase)) {
|
|
2723
|
+
return toPosix(import_node_path6.default.relative(serviceDir, resolvedBase));
|
|
2724
|
+
}
|
|
2725
|
+
}
|
|
2726
|
+
}
|
|
2727
|
+
return null;
|
|
2728
|
+
}
|
|
2729
|
+
async function resolveJsImport(specifier, importerDir, serviceDir, tsPaths) {
|
|
2730
|
+
if (!specifier) return null;
|
|
2731
|
+
if (specifier.startsWith("./") || specifier.startsWith("../")) {
|
|
2732
|
+
const base = import_node_path6.default.resolve(importerDir, specifier);
|
|
2733
|
+
const ext = import_node_path6.default.extname(specifier);
|
|
2734
|
+
if (ext) {
|
|
2735
|
+
if (ext === ".js" || ext === ".jsx") {
|
|
2736
|
+
const tsExt = ext === ".jsx" ? ".tsx" : ".ts";
|
|
2737
|
+
const tsSibling = base.slice(0, -ext.length) + tsExt;
|
|
2738
|
+
if (isWithinServiceDir(tsSibling, serviceDir) && await fileExists(tsSibling)) {
|
|
2739
|
+
return toPosix(import_node_path6.default.relative(serviceDir, tsSibling));
|
|
2740
|
+
}
|
|
2741
|
+
}
|
|
2742
|
+
if (isWithinServiceDir(base, serviceDir) && await fileExists(base)) {
|
|
2743
|
+
return toPosix(import_node_path6.default.relative(serviceDir, base));
|
|
2744
|
+
}
|
|
2745
|
+
if (!JS_EXTENSIONS.includes(ext)) {
|
|
2746
|
+
return firstExistingCandidate(base, serviceDir);
|
|
2747
|
+
}
|
|
2748
|
+
return null;
|
|
2749
|
+
}
|
|
2750
|
+
return firstExistingCandidate(base, serviceDir);
|
|
2751
|
+
}
|
|
2752
|
+
if (tsPaths) return resolveTsAlias(specifier, tsPaths, serviceDir);
|
|
2753
|
+
return null;
|
|
2754
|
+
}
|
|
2755
|
+
async function resolvePyImport(imp, importerPath, serviceDir) {
|
|
2756
|
+
let baseDir;
|
|
2757
|
+
if (imp.level > 0) {
|
|
2758
|
+
baseDir = import_node_path6.default.dirname(importerPath);
|
|
2759
|
+
for (let i = 1; i < imp.level; i++) baseDir = import_node_path6.default.dirname(baseDir);
|
|
2760
|
+
} else {
|
|
2761
|
+
baseDir = serviceDir;
|
|
2762
|
+
}
|
|
2763
|
+
const moduleBase = imp.modulePath ? import_node_path6.default.join(baseDir, imp.modulePath.split(".").join("/")) : baseDir;
|
|
2764
|
+
const resolved = /* @__PURE__ */ new Set();
|
|
2765
|
+
let needModuleFile = imp.names.length === 0;
|
|
2766
|
+
for (const name of imp.names) {
|
|
2767
|
+
const submoduleFile = import_node_path6.default.join(moduleBase, `${name}.py`);
|
|
2768
|
+
const subpackageInit = import_node_path6.default.join(moduleBase, name, "__init__.py");
|
|
2769
|
+
if (isWithinServiceDir(submoduleFile, serviceDir) && await fileExists(submoduleFile)) {
|
|
2770
|
+
resolved.add(toPosix(import_node_path6.default.relative(serviceDir, submoduleFile)));
|
|
2771
|
+
} else if (isWithinServiceDir(subpackageInit, serviceDir) && await fileExists(subpackageInit)) {
|
|
2772
|
+
resolved.add(toPosix(import_node_path6.default.relative(serviceDir, subpackageInit)));
|
|
2773
|
+
} else {
|
|
2774
|
+
needModuleFile = true;
|
|
2775
|
+
}
|
|
2776
|
+
}
|
|
2777
|
+
if (needModuleFile) {
|
|
2778
|
+
const moduleFileCandidates = imp.modulePath ? [`${moduleBase}.py`, import_node_path6.default.join(moduleBase, "__init__.py")] : [import_node_path6.default.join(moduleBase, "__init__.py")];
|
|
2779
|
+
for (const candidate of moduleFileCandidates) {
|
|
2780
|
+
if (isWithinServiceDir(candidate, serviceDir) && await fileExists(candidate)) {
|
|
2781
|
+
resolved.add(toPosix(import_node_path6.default.relative(serviceDir, candidate)));
|
|
2782
|
+
break;
|
|
2783
|
+
}
|
|
2784
|
+
}
|
|
2785
|
+
}
|
|
2786
|
+
return [...resolved];
|
|
2787
|
+
}
|
|
2788
|
+
async function resolveGoImport(specifier, modulePath, serviceDir) {
|
|
2789
|
+
if (specifier !== modulePath && !specifier.startsWith(`${modulePath}/`)) return null;
|
|
2790
|
+
const suffix = specifier === modulePath ? "" : specifier.slice(modulePath.length + 1);
|
|
2791
|
+
const dir = import_node_path6.default.join(serviceDir, suffix);
|
|
2792
|
+
const entries = await import_node_fs6.promises.readdir(dir, { withFileTypes: true }).catch(() => []);
|
|
2793
|
+
const candidates = entries.filter((entry2) => entry2.isFile() && entry2.name.endsWith(".go") && !entry2.name.endsWith("_test.go")).map((entry2) => import_node_path6.default.join(dir, entry2.name));
|
|
2794
|
+
if (candidates.length !== 1) return null;
|
|
2795
|
+
return toPosix(import_node_path6.default.relative(serviceDir, candidates[0]));
|
|
2796
|
+
}
|
|
2797
|
+
function emitImportEdge(graph, serviceName, importerFileId, importerRelPath, importeeRelPath, line, snippet2) {
|
|
2798
|
+
const importeeFileId = (0, import_types5.fileId)(serviceName, importeeRelPath);
|
|
2799
|
+
if (!graph.hasNode(importeeFileId)) return 0;
|
|
2800
|
+
const edgeId = (0, import_types5.extractedEdgeId)(importerFileId, importeeFileId, import_types5.EdgeType.IMPORTS);
|
|
2801
|
+
if (graph.hasEdge(edgeId)) return 0;
|
|
2802
|
+
const edge = {
|
|
2803
|
+
id: edgeId,
|
|
2804
|
+
source: importerFileId,
|
|
2805
|
+
target: importeeFileId,
|
|
2806
|
+
type: import_types5.EdgeType.IMPORTS,
|
|
2807
|
+
provenance: import_types5.Provenance.EXTRACTED,
|
|
2808
|
+
confidence: (0, import_types5.confidenceForExtracted)("structural"),
|
|
2809
|
+
evidence: { file: importerRelPath, line, snippet: snippet2 }
|
|
2810
|
+
};
|
|
2811
|
+
graph.addEdgeWithKey(edgeId, importerFileId, importeeFileId, edge);
|
|
2812
|
+
return 1;
|
|
2813
|
+
}
|
|
2814
|
+
async function addImports(graph, services) {
|
|
2815
|
+
const jsParser = makeJsParser();
|
|
2816
|
+
const pyParser = makePyParser();
|
|
2817
|
+
const goParser = makeGoParser();
|
|
2818
|
+
let edgesAdded = 0;
|
|
2819
|
+
for (const service of services) {
|
|
2820
|
+
const tsPaths = await loadTsPathConfig(service.dir);
|
|
2821
|
+
const files = await loadSourceFiles(service.dir);
|
|
2822
|
+
for (const file of files) {
|
|
2823
|
+
if (isTestPath(file.path)) continue;
|
|
2824
|
+
const relFile = toPosix(import_node_path6.default.relative(service.dir, file.path));
|
|
2825
|
+
const importerFileId = (0, import_types5.fileId)(service.pkg.name, relFile);
|
|
2826
|
+
const isPython = import_node_path6.default.extname(file.path) === ".py";
|
|
2827
|
+
const isGo = import_node_path6.default.extname(file.path) === ".go";
|
|
2828
|
+
if (isGo) {
|
|
2829
|
+
let goImports = [];
|
|
2830
|
+
try {
|
|
2831
|
+
const tree = parseSource(goParser, file.content);
|
|
2832
|
+
collectGoImports(tree.rootNode, goImports);
|
|
2833
|
+
} catch (err) {
|
|
2834
|
+
recordExtractionError("import extraction", file.path, err);
|
|
2835
|
+
continue;
|
|
2836
|
+
}
|
|
2837
|
+
const goMod = await import_node_fs6.promises.readFile(import_node_path6.default.join(service.dir, "go.mod"), "utf8").catch(() => "");
|
|
2838
|
+
const modulePath = goMod.match(/^\s*module\s+(\S+)\s*$/m)?.[1];
|
|
2839
|
+
if (!modulePath) continue;
|
|
2840
|
+
for (const imp of goImports) {
|
|
2841
|
+
const resolved = await resolveGoImport(imp.specifier, modulePath, service.dir);
|
|
2842
|
+
if (!resolved) continue;
|
|
2843
|
+
edgesAdded += emitImportEdge(graph, service.pkg.name, importerFileId, relFile, resolved, imp.line, imp.snippet);
|
|
2844
|
+
}
|
|
2845
|
+
continue;
|
|
2846
|
+
}
|
|
2847
|
+
if (isPython) {
|
|
2848
|
+
let pyImports = [];
|
|
2849
|
+
try {
|
|
2850
|
+
const tree = parseSource(pyParser, file.content);
|
|
2851
|
+
collectPyImports(tree.rootNode, pyImports);
|
|
2852
|
+
} catch (err) {
|
|
2853
|
+
recordExtractionError("import extraction", file.path, err);
|
|
2854
|
+
continue;
|
|
2855
|
+
}
|
|
2856
|
+
for (const imp of pyImports) {
|
|
2857
|
+
const resolvedPaths = await resolvePyImport(imp, file.path, service.dir);
|
|
2858
|
+
for (const resolved of resolvedPaths) {
|
|
2859
|
+
edgesAdded += emitImportEdge(
|
|
2860
|
+
graph,
|
|
2861
|
+
service.pkg.name,
|
|
2862
|
+
importerFileId,
|
|
2863
|
+
relFile,
|
|
2864
|
+
resolved,
|
|
2865
|
+
imp.line,
|
|
2866
|
+
imp.snippet
|
|
2867
|
+
);
|
|
2868
|
+
}
|
|
2869
|
+
}
|
|
2870
|
+
continue;
|
|
2871
|
+
}
|
|
2872
|
+
let jsImports = [];
|
|
2873
|
+
try {
|
|
2874
|
+
const tree = parseSource(jsParser, file.content);
|
|
2875
|
+
collectJsImports(tree.rootNode, jsImports);
|
|
2876
|
+
} catch (err) {
|
|
2877
|
+
recordExtractionError("import extraction", file.path, err);
|
|
2878
|
+
continue;
|
|
2879
|
+
}
|
|
2880
|
+
for (const imp of jsImports) {
|
|
2881
|
+
const resolved = await resolveJsImport(imp.specifier, import_node_path6.default.dirname(file.path), service.dir, tsPaths);
|
|
2882
|
+
if (!resolved) continue;
|
|
2883
|
+
edgesAdded += emitImportEdge(
|
|
2884
|
+
graph,
|
|
2885
|
+
service.pkg.name,
|
|
2886
|
+
importerFileId,
|
|
2887
|
+
relFile,
|
|
2888
|
+
resolved,
|
|
2889
|
+
imp.line,
|
|
2890
|
+
imp.snippet
|
|
2891
|
+
);
|
|
2892
|
+
}
|
|
2893
|
+
}
|
|
2894
|
+
}
|
|
2895
|
+
return { nodesAdded: 0, edgesAdded };
|
|
2896
|
+
}
|
|
2897
|
+
|
|
2898
|
+
// src/extract/routes.ts
|
|
2899
|
+
var PARSE_CHUNK2 = 16384;
|
|
2900
|
+
function parseSource2(parser, source) {
|
|
2901
|
+
return parser.parse(
|
|
2902
|
+
(index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK2)
|
|
2903
|
+
);
|
|
2904
|
+
}
|
|
2905
|
+
function makeJsParser2() {
|
|
2906
|
+
const p = new import_tree_sitter2.default();
|
|
2907
|
+
p.setLanguage(import_tree_sitter_javascript2.default);
|
|
2908
|
+
return p;
|
|
2909
|
+
}
|
|
2910
|
+
function makePyParser2() {
|
|
2911
|
+
const p = new import_tree_sitter2.default();
|
|
2912
|
+
p.setLanguage(import_tree_sitter_python2.default);
|
|
2913
|
+
return p;
|
|
2914
|
+
}
|
|
2915
|
+
function makeGoParser2() {
|
|
2916
|
+
const p = new import_tree_sitter2.default();
|
|
2917
|
+
p.setLanguage(import_tree_sitter_go2.default);
|
|
2918
|
+
return p;
|
|
2919
|
+
}
|
|
2920
|
+
var ROUTER_METHODS = /* @__PURE__ */ new Set([
|
|
2921
|
+
"get",
|
|
2922
|
+
"post",
|
|
2923
|
+
"put",
|
|
2924
|
+
"patch",
|
|
2925
|
+
"delete",
|
|
2926
|
+
"options",
|
|
2927
|
+
"head",
|
|
2928
|
+
"all"
|
|
2929
|
+
]);
|
|
2930
|
+
var NEXT_APP_METHODS = /* @__PURE__ */ new Set(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"]);
|
|
2931
|
+
var JS_ROUTE_EXTENSIONS = /* @__PURE__ */ new Set([".js", ".jsx", ".mjs", ".cjs", ".ts", ".tsx"]);
|
|
2932
|
+
function ginRoutesFromSource(source, parser) {
|
|
2933
|
+
const tree = parseSource2(parser, source);
|
|
2934
|
+
const prefixes = /* @__PURE__ */ new Map();
|
|
2935
|
+
const out = [];
|
|
2936
|
+
walk(tree.rootNode, (node) => {
|
|
2937
|
+
if (node.type === "short_var_declaration" || node.type === "var_spec") {
|
|
2938
|
+
const name = node.childForFieldName("left")?.namedChild(0)?.text ?? node.childForFieldName("name")?.text;
|
|
2939
|
+
const value = node.childForFieldName("right")?.namedChild(0) ?? node.childForFieldName("value");
|
|
2940
|
+
if (name && value?.type === "call_expression") {
|
|
2941
|
+
const fn2 = value.childForFieldName("function");
|
|
2942
|
+
const field = fn2?.childForFieldName("field")?.text;
|
|
2943
|
+
const first2 = value.childForFieldName("arguments")?.namedChild(0);
|
|
2944
|
+
if (field === "Group" && first2?.type === "interpreted_string_literal") {
|
|
2945
|
+
prefixes.set(name, first2.text.slice(1, -1));
|
|
2946
|
+
}
|
|
2947
|
+
}
|
|
2948
|
+
return;
|
|
2949
|
+
}
|
|
2950
|
+
if (node.type !== "call_expression") return;
|
|
2951
|
+
const fn = node.childForFieldName("function");
|
|
2952
|
+
if (fn?.type !== "selector_expression") return;
|
|
2953
|
+
const method = fn.childForFieldName("field")?.text?.toUpperCase();
|
|
2954
|
+
if (!method || !ROUTER_METHODS.has(method.toLowerCase())) return;
|
|
2955
|
+
const receiver = fn.childForFieldName("operand")?.text ?? "";
|
|
2956
|
+
const first = node.childForFieldName("arguments")?.namedChild(0);
|
|
2957
|
+
if (first?.type !== "interpreted_string_literal") return;
|
|
2958
|
+
const leaf = first.text.slice(1, -1);
|
|
2959
|
+
out.push({
|
|
2960
|
+
method: method === "ALL" ? "ALL" : method,
|
|
2961
|
+
pathTemplate: canonicalizeTemplate((prefixes.get(receiver) ?? "") + leaf),
|
|
2962
|
+
line: node.startPosition.row + 1,
|
|
2963
|
+
framework: "gin"
|
|
2964
|
+
});
|
|
2965
|
+
});
|
|
2966
|
+
return out;
|
|
2967
|
+
}
|
|
2968
|
+
var FASTAPI_METHODS = /* @__PURE__ */ new Set(["get", "post", "put", "patch", "delete", "options", "head", "trace"]);
|
|
2969
|
+
var NESTJS_METHODS = /* @__PURE__ */ new Map([
|
|
2970
|
+
["Get", "GET"],
|
|
2971
|
+
["Post", "POST"],
|
|
2972
|
+
["Put", "PUT"],
|
|
2973
|
+
["Patch", "PATCH"],
|
|
2974
|
+
["Delete", "DELETE"],
|
|
2975
|
+
["Options", "OPTIONS"],
|
|
2976
|
+
["Head", "HEAD"],
|
|
2977
|
+
["All", "ALL"]
|
|
2978
|
+
]);
|
|
2979
|
+
function canonicalizeTemplate(raw) {
|
|
2980
|
+
let p = raw.split("?")[0].split("#")[0];
|
|
2981
|
+
if (!p.startsWith("/")) p = "/" + p;
|
|
2982
|
+
if (p.length > 1 && p.endsWith("/")) p = p.slice(0, -1);
|
|
2983
|
+
return p;
|
|
2984
|
+
}
|
|
2985
|
+
function isDynamicSegment(seg) {
|
|
2986
|
+
if (seg.length === 0) return false;
|
|
2987
|
+
if (seg.includes(":")) return true;
|
|
2988
|
+
if (seg.startsWith("{") || seg.startsWith("[")) return true;
|
|
2989
|
+
if (/^\d+$/.test(seg)) return true;
|
|
2990
|
+
if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(seg)) return true;
|
|
2991
|
+
if (/^[0-9a-f]{24,}$/i.test(seg)) return true;
|
|
2992
|
+
return false;
|
|
2993
|
+
}
|
|
2994
|
+
function normalizePathTemplate(raw) {
|
|
2995
|
+
const canonical = canonicalizeTemplate(raw);
|
|
2996
|
+
const segments = canonical.split("/").filter((s) => s.length > 0);
|
|
2997
|
+
const normalised = segments.map((seg) => isDynamicSegment(seg) ? ":param" : seg.toLowerCase());
|
|
2998
|
+
return "/" + normalised.join("/");
|
|
2999
|
+
}
|
|
3000
|
+
function walk(node, visit) {
|
|
3001
|
+
visit(node);
|
|
3002
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
3003
|
+
const child = node.namedChild(i);
|
|
3004
|
+
if (child) walk(child, visit);
|
|
3005
|
+
}
|
|
3006
|
+
}
|
|
3007
|
+
function staticStringText(node) {
|
|
3008
|
+
if (node.type === "string") {
|
|
3009
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
3010
|
+
const child = node.namedChild(i);
|
|
3011
|
+
if (child?.type === "string_fragment") return child.text;
|
|
3012
|
+
}
|
|
3013
|
+
return "";
|
|
3014
|
+
}
|
|
3015
|
+
if (node.type === "template_string") {
|
|
3016
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
3017
|
+
if (node.namedChild(i)?.type === "template_substitution") return null;
|
|
3018
|
+
}
|
|
3019
|
+
const raw = node.text;
|
|
3020
|
+
return raw.length >= 2 ? raw.slice(1, -1) : "";
|
|
3021
|
+
}
|
|
3022
|
+
return null;
|
|
3023
|
+
}
|
|
3024
|
+
function objectStringProp(objNode, key) {
|
|
3025
|
+
for (let i = 0; i < objNode.namedChildCount; i++) {
|
|
3026
|
+
const pair = objNode.namedChild(i);
|
|
3027
|
+
if (!pair || pair.type !== "pair") continue;
|
|
3028
|
+
const k = pair.childForFieldName("key");
|
|
3029
|
+
if (!k) continue;
|
|
3030
|
+
const kText = k.type === "string" ? staticStringText(k) : k.text;
|
|
3031
|
+
if (kText !== key) continue;
|
|
3032
|
+
const v = pair.childForFieldName("value");
|
|
3033
|
+
if (v) return staticStringText(v);
|
|
3034
|
+
}
|
|
3035
|
+
return null;
|
|
3036
|
+
}
|
|
3037
|
+
function fastifyRouteMethods(objNode) {
|
|
3038
|
+
for (let i = 0; i < objNode.namedChildCount; i++) {
|
|
3039
|
+
const pair = objNode.namedChild(i);
|
|
3040
|
+
if (!pair || pair.type !== "pair") continue;
|
|
3041
|
+
const k = pair.childForFieldName("key");
|
|
3042
|
+
const kText = k ? k.type === "string" ? staticStringText(k) : k.text : null;
|
|
3043
|
+
if (kText !== "method") continue;
|
|
3044
|
+
const v = pair.childForFieldName("value");
|
|
3045
|
+
if (!v) return [];
|
|
3046
|
+
if (v.type === "string" || v.type === "template_string") {
|
|
3047
|
+
const s = staticStringText(v);
|
|
3048
|
+
return s ? [s.toUpperCase()] : [];
|
|
3049
|
+
}
|
|
3050
|
+
if (v.type === "array") {
|
|
3051
|
+
const out = [];
|
|
3052
|
+
for (let j = 0; j < v.namedChildCount; j++) {
|
|
3053
|
+
const el = v.namedChild(j);
|
|
3054
|
+
if (el && (el.type === "string" || el.type === "template_string")) {
|
|
3055
|
+
const s = staticStringText(el);
|
|
3056
|
+
if (s) out.push(s.toUpperCase());
|
|
3057
|
+
}
|
|
3058
|
+
}
|
|
3059
|
+
return out;
|
|
3060
|
+
}
|
|
3061
|
+
}
|
|
3062
|
+
return [];
|
|
3063
|
+
}
|
|
3064
|
+
function nestDecoratorImports(root) {
|
|
3065
|
+
const imports = /* @__PURE__ */ new Map();
|
|
3066
|
+
walk(root, (node) => {
|
|
3067
|
+
if (node.type !== "import_statement") return;
|
|
3068
|
+
const source = node.childForFieldName("source");
|
|
2684
3069
|
if (!source || staticStringText(source) !== "@nestjs/common") return;
|
|
2685
3070
|
walk(node, (child) => {
|
|
2686
3071
|
if (child.type !== "import_specifier") return;
|
|
@@ -2726,7 +3111,7 @@ function nestJoinedPath(prefix, leaf) {
|
|
|
2726
3111
|
return canonicalizeTemplate(segments.join("/"));
|
|
2727
3112
|
}
|
|
2728
3113
|
function nestjsRoutesFromSource(source, parser) {
|
|
2729
|
-
const tree =
|
|
3114
|
+
const tree = parseSource2(parser, source);
|
|
2730
3115
|
const imports = nestDecoratorImports(tree.rootNode);
|
|
2731
3116
|
if (![...imports.values()].includes("Controller")) return [];
|
|
2732
3117
|
const out = [];
|
|
@@ -2774,7 +3159,7 @@ function nestjsRoutesFromSource(source, parser) {
|
|
|
2774
3159
|
return out;
|
|
2775
3160
|
}
|
|
2776
3161
|
function serverRoutesFromSource(source, parser, hasExpress, hasFastify, hasHono = false) {
|
|
2777
|
-
const tree =
|
|
3162
|
+
const tree = parseSource2(parser, source);
|
|
2778
3163
|
const out = [];
|
|
2779
3164
|
const framework = hasExpress ? "express" : hasFastify ? "fastify" : hasHono ? "hono" : "unknown";
|
|
2780
3165
|
walk(tree.rootNode, (node) => {
|
|
@@ -2832,7 +3217,7 @@ function isNextPagesApiFile(relFile) {
|
|
|
2832
3217
|
if (pagesIdx === -1 || segs[pagesIdx + 1] !== "api") return false;
|
|
2833
3218
|
const base = segs[segs.length - 1] ?? "";
|
|
2834
3219
|
if (/^_(app|document|middleware)\./.test(base)) return false;
|
|
2835
|
-
return JS_ROUTE_EXTENSIONS.has(
|
|
3220
|
+
return JS_ROUTE_EXTENSIONS.has(import_node_path7.default.extname(base));
|
|
2836
3221
|
}
|
|
2837
3222
|
function nextSegment(seg) {
|
|
2838
3223
|
if (seg.startsWith("(") && seg.endsWith(")")) return null;
|
|
@@ -2894,7 +3279,7 @@ function nextAppMethods(root) {
|
|
|
2894
3279
|
}
|
|
2895
3280
|
function nextRoutesFromFile(source, relFile, parser) {
|
|
2896
3281
|
if (isNextAppRouteFile(relFile)) {
|
|
2897
|
-
const tree =
|
|
3282
|
+
const tree = parseSource2(parser, source);
|
|
2898
3283
|
const template = nextAppPathTemplate(relFile);
|
|
2899
3284
|
return nextAppMethods(tree.rootNode).map(({ method, line }) => ({
|
|
2900
3285
|
method,
|
|
@@ -3015,7 +3400,7 @@ function collectMountPrefixes(root, consts) {
|
|
|
3015
3400
|
return mounts;
|
|
3016
3401
|
}
|
|
3017
3402
|
function pythonRoutesFromSource(source, parser, framework) {
|
|
3018
|
-
const tree =
|
|
3403
|
+
const tree = parseSource2(parser, source);
|
|
3019
3404
|
const prefixes = collectPythonRouterPrefixes(tree.rootNode);
|
|
3020
3405
|
const consts = collectStringConstants(tree.rootNode);
|
|
3021
3406
|
const mounts = collectMountPrefixes(tree.rootNode, consts);
|
|
@@ -3055,7 +3440,7 @@ function pythonRoutesFromSource(source, parser, framework) {
|
|
|
3055
3440
|
return out;
|
|
3056
3441
|
}
|
|
3057
3442
|
function djangoRoutesFromSource(source, parser) {
|
|
3058
|
-
const tree =
|
|
3443
|
+
const tree = parseSource2(parser, source);
|
|
3059
3444
|
const out = [];
|
|
3060
3445
|
walk(tree.rootNode, (node) => {
|
|
3061
3446
|
if (node.type !== "assignment") return;
|
|
@@ -3083,10 +3468,320 @@ function djangoRoutesFromSource(source, parser) {
|
|
|
3083
3468
|
});
|
|
3084
3469
|
return out;
|
|
3085
3470
|
}
|
|
3471
|
+
function namedArgs(argsNode) {
|
|
3472
|
+
const out = [];
|
|
3473
|
+
if (!argsNode) return out;
|
|
3474
|
+
for (let i = 0; i < argsNode.namedChildCount; i++) {
|
|
3475
|
+
const c = argsNode.namedChild(i);
|
|
3476
|
+
if (c && c.type !== "comment") out.push(c);
|
|
3477
|
+
}
|
|
3478
|
+
return out;
|
|
3479
|
+
}
|
|
3480
|
+
function parseUseMount(callNode) {
|
|
3481
|
+
const args = namedArgs(callNode.childForFieldName("arguments"));
|
|
3482
|
+
if (args.length === 0) return null;
|
|
3483
|
+
const first = args[0];
|
|
3484
|
+
const firstStr = first.type === "string" || first.type === "template_string" ? staticStringText(first) : null;
|
|
3485
|
+
if (firstStr !== null && firstStr.startsWith("/")) {
|
|
3486
|
+
const prefix = canonicalizeTemplate(firstStr);
|
|
3487
|
+
const second = args[1];
|
|
3488
|
+
const target = second && second.type === "identifier" ? second.text : null;
|
|
3489
|
+
return { prefix: prefix === "/" ? "" : prefix, target };
|
|
3490
|
+
}
|
|
3491
|
+
if (args.length === 1 && first.type === "identifier") return { prefix: "", target: first.text };
|
|
3492
|
+
return null;
|
|
3493
|
+
}
|
|
3494
|
+
function unwrapRouterExpr(node, expressLocals, routerCtors) {
|
|
3495
|
+
if (node.type === "identifier") return { base: { alias: node.text }, mounts: [] };
|
|
3496
|
+
if (node.type !== "call_expression") return null;
|
|
3497
|
+
const fn = node.childForFieldName("function");
|
|
3498
|
+
if (!fn) return null;
|
|
3499
|
+
if (fn.type === "member_expression") {
|
|
3500
|
+
const prop = fn.childForFieldName("property")?.text;
|
|
3501
|
+
const obj = fn.childForFieldName("object");
|
|
3502
|
+
if (!prop || !obj) return null;
|
|
3503
|
+
if (prop === "use") {
|
|
3504
|
+
const inner = unwrapRouterExpr(obj, expressLocals, routerCtors);
|
|
3505
|
+
if (!inner) return null;
|
|
3506
|
+
const mount = parseUseMount(node);
|
|
3507
|
+
return { base: inner.base, mounts: mount ? [...inner.mounts, mount] : inner.mounts };
|
|
3508
|
+
}
|
|
3509
|
+
if (prop === "Router" && obj.type === "identifier" && expressLocals.has(obj.text)) {
|
|
3510
|
+
return { base: "newRouter", mounts: [] };
|
|
3511
|
+
}
|
|
3512
|
+
return null;
|
|
3513
|
+
}
|
|
3514
|
+
if (fn.type === "identifier") {
|
|
3515
|
+
if (expressLocals.has(fn.text)) return { base: "app", mounts: [] };
|
|
3516
|
+
if (routerCtors.has(fn.text)) return { base: "newRouter", mounts: [] };
|
|
3517
|
+
}
|
|
3518
|
+
return null;
|
|
3519
|
+
}
|
|
3520
|
+
function collectExpressImports(root) {
|
|
3521
|
+
const expressLocals = /* @__PURE__ */ new Set();
|
|
3522
|
+
const routerCtors = /* @__PURE__ */ new Set();
|
|
3523
|
+
const bindings = [];
|
|
3524
|
+
const addFromExpress = (local, sel, exported) => {
|
|
3525
|
+
if (sel === "default" || sel === "namespace") expressLocals.add(local);
|
|
3526
|
+
else if (exported === "Router") routerCtors.add(local);
|
|
3527
|
+
};
|
|
3528
|
+
walk(root, (node) => {
|
|
3529
|
+
if (node.type === "import_statement") {
|
|
3530
|
+
const source = node.childForFieldName("source");
|
|
3531
|
+
const spec = source ? staticStringText(source) : null;
|
|
3532
|
+
if (!spec) return;
|
|
3533
|
+
let clause = null;
|
|
3534
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
3535
|
+
const c = node.namedChild(i);
|
|
3536
|
+
if (c?.type === "import_clause") clause = c;
|
|
3537
|
+
}
|
|
3538
|
+
if (!clause) return;
|
|
3539
|
+
for (let i = 0; i < clause.namedChildCount; i++) {
|
|
3540
|
+
const c = clause.namedChild(i);
|
|
3541
|
+
if (!c) continue;
|
|
3542
|
+
if (c.type === "identifier") {
|
|
3543
|
+
if (spec === "express") addFromExpress(c.text, "default", "default");
|
|
3544
|
+
else bindings.push({ local: c.text, specifier: spec, sel: "default" });
|
|
3545
|
+
} else if (c.type === "namespace_import") {
|
|
3546
|
+
const id = c.namedChild(0);
|
|
3547
|
+
if (id?.type === "identifier") {
|
|
3548
|
+
if (spec === "express") addFromExpress(id.text, "namespace", "namespace");
|
|
3549
|
+
else bindings.push({ local: id.text, specifier: spec, sel: "namespace" });
|
|
3550
|
+
}
|
|
3551
|
+
} else if (c.type === "named_imports") {
|
|
3552
|
+
for (let j = 0; j < c.namedChildCount; j++) {
|
|
3553
|
+
const s = c.namedChild(j);
|
|
3554
|
+
if (s?.type !== "import_specifier") continue;
|
|
3555
|
+
const name = s.childForFieldName("name")?.text;
|
|
3556
|
+
if (!name) continue;
|
|
3557
|
+
const local = s.childForFieldName("alias")?.text ?? name;
|
|
3558
|
+
if (spec === "express") addFromExpress(local, name, name);
|
|
3559
|
+
else bindings.push({ local, specifier: spec, sel: name });
|
|
3560
|
+
}
|
|
3561
|
+
}
|
|
3562
|
+
}
|
|
3563
|
+
return;
|
|
3564
|
+
}
|
|
3565
|
+
if (node.type === "variable_declarator") {
|
|
3566
|
+
const value = node.childForFieldName("value");
|
|
3567
|
+
if (value?.type !== "call_expression") return;
|
|
3568
|
+
const fn = value.childForFieldName("function");
|
|
3569
|
+
if (fn?.type !== "identifier" || fn.text !== "require") return;
|
|
3570
|
+
const arg = namedArgs(value.childForFieldName("arguments"))[0];
|
|
3571
|
+
const spec = arg ? staticStringText(arg) : null;
|
|
3572
|
+
if (!spec) return;
|
|
3573
|
+
const name = node.childForFieldName("name");
|
|
3574
|
+
if (name?.type === "identifier") {
|
|
3575
|
+
if (spec === "express") expressLocals.add(name.text);
|
|
3576
|
+
else bindings.push({ local: name.text, specifier: spec, sel: "default" });
|
|
3577
|
+
} else if (name?.type === "object_pattern") {
|
|
3578
|
+
for (let i = 0; i < name.namedChildCount; i++) {
|
|
3579
|
+
const el = name.namedChild(i);
|
|
3580
|
+
if (!el) continue;
|
|
3581
|
+
let local;
|
|
3582
|
+
let exported;
|
|
3583
|
+
if (el.type === "shorthand_property_identifier_pattern") {
|
|
3584
|
+
local = el.text;
|
|
3585
|
+
exported = el.text;
|
|
3586
|
+
} else if (el.type === "pair_pattern") {
|
|
3587
|
+
exported = el.childForFieldName("key")?.text;
|
|
3588
|
+
local = el.childForFieldName("value")?.text ?? exported;
|
|
3589
|
+
}
|
|
3590
|
+
if (!local || !exported) continue;
|
|
3591
|
+
if (spec === "express") addFromExpress(local, exported, exported);
|
|
3592
|
+
else bindings.push({ local, specifier: spec, sel: exported });
|
|
3593
|
+
}
|
|
3594
|
+
}
|
|
3595
|
+
}
|
|
3596
|
+
});
|
|
3597
|
+
return { expressLocals, routerCtors, bindings };
|
|
3598
|
+
}
|
|
3599
|
+
function analyzeExpressFile(root, dir) {
|
|
3600
|
+
const { expressLocals, routerCtors, bindings } = collectExpressImports(root);
|
|
3601
|
+
const routerVars = /* @__PURE__ */ new Map();
|
|
3602
|
+
const appVars = /* @__PURE__ */ new Set();
|
|
3603
|
+
const exportNamed = /* @__PURE__ */ new Map();
|
|
3604
|
+
let exportDefaultName = null;
|
|
3605
|
+
const getVar = (name) => {
|
|
3606
|
+
let rv = routerVars.get(name);
|
|
3607
|
+
if (!rv) {
|
|
3608
|
+
rv = { declares: false, mounts: [] };
|
|
3609
|
+
routerVars.set(name, rv);
|
|
3610
|
+
}
|
|
3611
|
+
return rv;
|
|
3612
|
+
};
|
|
3613
|
+
const refFromExpr = (expr, key) => {
|
|
3614
|
+
if (expr.type === "identifier") return expr.text;
|
|
3615
|
+
const u = unwrapRouterExpr(expr, expressLocals, routerCtors);
|
|
3616
|
+
if (!u) return null;
|
|
3617
|
+
const rv = getVar(key);
|
|
3618
|
+
for (const m of u.mounts) rv.mounts.push(m);
|
|
3619
|
+
if (typeof u.base === "object") rv.aliasOf = u.base.alias;
|
|
3620
|
+
return key;
|
|
3621
|
+
};
|
|
3622
|
+
const isExported = (declarator) => {
|
|
3623
|
+
const decl = declarator.parent;
|
|
3624
|
+
return decl?.parent?.type === "export_statement";
|
|
3625
|
+
};
|
|
3626
|
+
walk(root, (node) => {
|
|
3627
|
+
if (node.type === "variable_declarator") {
|
|
3628
|
+
const value = node.childForFieldName("value");
|
|
3629
|
+
const name = node.childForFieldName("name");
|
|
3630
|
+
if (name?.type !== "identifier" || !value) return;
|
|
3631
|
+
if (value.type === "call_expression") {
|
|
3632
|
+
const fn = value.childForFieldName("function");
|
|
3633
|
+
if (fn?.type === "identifier" && fn.text === "require") return;
|
|
3634
|
+
}
|
|
3635
|
+
const u = unwrapRouterExpr(value, expressLocals, routerCtors);
|
|
3636
|
+
if (!u) return;
|
|
3637
|
+
const rv = getVar(name.text);
|
|
3638
|
+
for (const m of u.mounts) rv.mounts.push(m);
|
|
3639
|
+
if (u.base === "app") appVars.add(name.text);
|
|
3640
|
+
else if (typeof u.base === "object") rv.aliasOf = u.base.alias;
|
|
3641
|
+
if (isExported(node)) exportNamed.set(name.text, name.text);
|
|
3642
|
+
return;
|
|
3643
|
+
}
|
|
3644
|
+
if (node.type === "call_expression") {
|
|
3645
|
+
const fn = node.childForFieldName("function");
|
|
3646
|
+
if (fn?.type !== "member_expression") return;
|
|
3647
|
+
const obj = fn.childForFieldName("object");
|
|
3648
|
+
const prop = fn.childForFieldName("property")?.text;
|
|
3649
|
+
if (obj?.type !== "identifier" || !prop) return;
|
|
3650
|
+
if (prop === "use") {
|
|
3651
|
+
const m = parseUseMount(node);
|
|
3652
|
+
if (m) getVar(obj.text).mounts.push(m);
|
|
3653
|
+
} else if (ROUTER_METHODS.has(prop.toLowerCase())) {
|
|
3654
|
+
const first = namedArgs(node.childForFieldName("arguments"))[0];
|
|
3655
|
+
const p = first ? staticStringText(first) : null;
|
|
3656
|
+
if (p !== null && p.startsWith("/")) getVar(obj.text).declares = true;
|
|
3657
|
+
}
|
|
3658
|
+
return;
|
|
3659
|
+
}
|
|
3660
|
+
if (node.type === "export_statement") {
|
|
3661
|
+
let clause = null;
|
|
3662
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
3663
|
+
const c = node.namedChild(i);
|
|
3664
|
+
if (c?.type === "export_clause") clause = c;
|
|
3665
|
+
}
|
|
3666
|
+
if (clause) {
|
|
3667
|
+
for (let i = 0; i < clause.namedChildCount; i++) {
|
|
3668
|
+
const spec = clause.namedChild(i);
|
|
3669
|
+
if (spec?.type !== "export_specifier") continue;
|
|
3670
|
+
const local = spec.childForFieldName("name")?.text;
|
|
3671
|
+
if (!local) continue;
|
|
3672
|
+
const exportedAs = spec.childForFieldName("alias")?.text ?? local;
|
|
3673
|
+
if (exportedAs === "default") exportDefaultName = local;
|
|
3674
|
+
else exportNamed.set(exportedAs, local);
|
|
3675
|
+
}
|
|
3676
|
+
return;
|
|
3677
|
+
}
|
|
3678
|
+
if (node.childForFieldName("declaration")) return;
|
|
3679
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
3680
|
+
const c = node.namedChild(i);
|
|
3681
|
+
if (c && c.type !== "export_clause") {
|
|
3682
|
+
exportDefaultName = refFromExpr(c, "#default");
|
|
3683
|
+
break;
|
|
3684
|
+
}
|
|
3685
|
+
}
|
|
3686
|
+
return;
|
|
3687
|
+
}
|
|
3688
|
+
if (node.type === "assignment_expression") {
|
|
3689
|
+
const left = node.childForFieldName("left");
|
|
3690
|
+
const right = node.childForFieldName("right");
|
|
3691
|
+
if (left?.type !== "member_expression" || !right) return;
|
|
3692
|
+
const lobj = left.childForFieldName("object")?.text;
|
|
3693
|
+
const lprop = left.childForFieldName("property")?.text;
|
|
3694
|
+
if (lobj === "module" && lprop === "exports") exportDefaultName = refFromExpr(right, "#default");
|
|
3695
|
+
else if (lobj === "exports" && lprop) {
|
|
3696
|
+
const key = refFromExpr(right, `#exp:${lprop}`);
|
|
3697
|
+
if (key) exportNamed.set(lprop, key);
|
|
3698
|
+
}
|
|
3699
|
+
}
|
|
3700
|
+
});
|
|
3701
|
+
return {
|
|
3702
|
+
dir,
|
|
3703
|
+
routerVars,
|
|
3704
|
+
appVars,
|
|
3705
|
+
exportDefaultName,
|
|
3706
|
+
exportNamed,
|
|
3707
|
+
rawBindings: bindings,
|
|
3708
|
+
importedRouters: /* @__PURE__ */ new Map()
|
|
3709
|
+
};
|
|
3710
|
+
}
|
|
3711
|
+
async function expressMountPrefixes(files, serviceDir, tsPaths) {
|
|
3712
|
+
const jsParser = makeJsParser2();
|
|
3713
|
+
const fileInfo = /* @__PURE__ */ new Map();
|
|
3714
|
+
for (const f of files) {
|
|
3715
|
+
if (!JS_ROUTE_EXTENSIONS.has(import_node_path7.default.extname(f.path))) continue;
|
|
3716
|
+
if (isTestPath(f.path)) continue;
|
|
3717
|
+
const rel = toPosix(import_node_path7.default.relative(serviceDir, f.path));
|
|
3718
|
+
try {
|
|
3719
|
+
const tree = parseSource2(jsParser, f.content);
|
|
3720
|
+
fileInfo.set(rel, analyzeExpressFile(tree.rootNode, import_node_path7.default.dirname(f.path)));
|
|
3721
|
+
} catch {
|
|
3722
|
+
}
|
|
3723
|
+
}
|
|
3724
|
+
if (fileInfo.size === 0) return /* @__PURE__ */ new Map();
|
|
3725
|
+
for (const info of fileInfo.values()) {
|
|
3726
|
+
for (const b of info.rawBindings) {
|
|
3727
|
+
const resolved = await resolveJsImport(b.specifier, info.dir, serviceDir, tsPaths);
|
|
3728
|
+
if (!resolved || !fileInfo.has(resolved)) continue;
|
|
3729
|
+
info.importedRouters.set(b.local, { file: resolved, sel: b.sel === "namespace" ? "default" : b.sel });
|
|
3730
|
+
}
|
|
3731
|
+
}
|
|
3732
|
+
const resolveTarget = (name, file) => {
|
|
3733
|
+
const info = fileInfo.get(file);
|
|
3734
|
+
if (!info) return null;
|
|
3735
|
+
if (info.routerVars.has(name)) return { file, name };
|
|
3736
|
+
const imp = info.importedRouters.get(name);
|
|
3737
|
+
if (!imp) return null;
|
|
3738
|
+
const target = fileInfo.get(imp.file);
|
|
3739
|
+
if (!target) return null;
|
|
3740
|
+
const key = imp.sel === "default" ? target.exportDefaultName : target.exportNamed.get(imp.sel);
|
|
3741
|
+
if (!key) return null;
|
|
3742
|
+
return { file: imp.file, name: key };
|
|
3743
|
+
};
|
|
3744
|
+
const filePrefix = /* @__PURE__ */ new Map();
|
|
3745
|
+
const conflicted = /* @__PURE__ */ new Set();
|
|
3746
|
+
const apply = (file, prefix) => {
|
|
3747
|
+
if (conflicted.has(file)) return;
|
|
3748
|
+
const existing = filePrefix.get(file);
|
|
3749
|
+
if (existing === void 0) filePrefix.set(file, prefix);
|
|
3750
|
+
else if (existing !== prefix) {
|
|
3751
|
+
filePrefix.delete(file);
|
|
3752
|
+
conflicted.add(file);
|
|
3753
|
+
}
|
|
3754
|
+
};
|
|
3755
|
+
const visited = /* @__PURE__ */ new Set();
|
|
3756
|
+
const collect = (file, name, accPrefix) => {
|
|
3757
|
+
const key = `${file}|${name}|${accPrefix}`;
|
|
3758
|
+
if (visited.has(key)) return;
|
|
3759
|
+
visited.add(key);
|
|
3760
|
+
const info = fileInfo.get(file);
|
|
3761
|
+
const rv = info?.routerVars.get(name);
|
|
3762
|
+
if (!info || !rv) return;
|
|
3763
|
+
if (rv.declares && info.appVars.size === 0) apply(file, accPrefix);
|
|
3764
|
+
for (const m of rv.mounts) {
|
|
3765
|
+
if (!m.target) continue;
|
|
3766
|
+
const t = resolveTarget(m.target, file);
|
|
3767
|
+
if (t) collect(t.file, t.name, accPrefix + m.prefix);
|
|
3768
|
+
}
|
|
3769
|
+
if (rv.aliasOf) {
|
|
3770
|
+
const t = resolveTarget(rv.aliasOf, file);
|
|
3771
|
+
if (t) collect(t.file, t.name, accPrefix);
|
|
3772
|
+
}
|
|
3773
|
+
};
|
|
3774
|
+
for (const [rel, info] of fileInfo) {
|
|
3775
|
+
for (const appVar of info.appVars) collect(rel, appVar, "");
|
|
3776
|
+
}
|
|
3777
|
+
const out = /* @__PURE__ */ new Map();
|
|
3778
|
+
for (const [file, prefix] of filePrefix) if (prefix && prefix !== "/") out.set(file, prefix);
|
|
3779
|
+
return out;
|
|
3780
|
+
}
|
|
3086
3781
|
async function addRoutes(graph, services) {
|
|
3087
|
-
const jsParser =
|
|
3088
|
-
const pyParser =
|
|
3089
|
-
const goParser =
|
|
3782
|
+
const jsParser = makeJsParser2();
|
|
3783
|
+
const pyParser = makePyParser2();
|
|
3784
|
+
const goParser = makeGoParser2();
|
|
3090
3785
|
let nodesAdded = 0;
|
|
3091
3786
|
let edgesAdded = 0;
|
|
3092
3787
|
for (const service of services) {
|
|
@@ -3106,13 +3801,14 @@ async function addRoutes(graph, services) {
|
|
|
3106
3801
|
if (!hasExpress && !hasFastify && !hasHono && !hasNext && !hasNestjs && !hasFastapi && !hasFlask && !hasDjango && !hasGin)
|
|
3107
3802
|
continue;
|
|
3108
3803
|
const files = await loadSourceFiles(service.dir);
|
|
3804
|
+
const mountPrefixes = hasExpress ? await expressMountPrefixes(files, service.dir, await loadTsPathConfig(service.dir)) : /* @__PURE__ */ new Map();
|
|
3109
3805
|
for (const file of files) {
|
|
3110
3806
|
if (isTestPath(file.path)) continue;
|
|
3111
|
-
const ext =
|
|
3807
|
+
const ext = import_node_path7.default.extname(file.path);
|
|
3112
3808
|
const isPy = ext === ".py";
|
|
3113
3809
|
const isGo = ext === ".go";
|
|
3114
3810
|
if (!JS_ROUTE_EXTENSIONS.has(ext) && !isPy && !isGo) continue;
|
|
3115
|
-
const relFile = toPosix(
|
|
3811
|
+
const relFile = toPosix(import_node_path7.default.relative(service.dir, file.path));
|
|
3116
3812
|
let routes;
|
|
3117
3813
|
try {
|
|
3118
3814
|
if (isGo) {
|
|
@@ -3134,16 +3830,18 @@ async function addRoutes(graph, services) {
|
|
|
3134
3830
|
continue;
|
|
3135
3831
|
}
|
|
3136
3832
|
if (routes.length === 0) continue;
|
|
3833
|
+
const mountPrefix = mountPrefixes.get(relFile);
|
|
3137
3834
|
for (const route of routes) {
|
|
3138
|
-
const
|
|
3835
|
+
const pathTemplate = mountPrefix ? canonicalizeTemplate(mountPrefix + route.pathTemplate) : route.pathTemplate;
|
|
3836
|
+
const rid = (0, import_types6.routeId)(service.pkg.name, route.method, pathTemplate);
|
|
3139
3837
|
if (!graph.hasNode(rid)) {
|
|
3140
3838
|
const node = {
|
|
3141
3839
|
id: rid,
|
|
3142
|
-
type:
|
|
3143
|
-
name: `${route.method} ${
|
|
3840
|
+
type: import_types6.NodeType.RouteNode,
|
|
3841
|
+
name: `${route.method} ${pathTemplate}`,
|
|
3144
3842
|
service: service.pkg.name,
|
|
3145
3843
|
method: route.method,
|
|
3146
|
-
pathTemplate
|
|
3844
|
+
pathTemplate,
|
|
3147
3845
|
path: relFile,
|
|
3148
3846
|
line: route.line,
|
|
3149
3847
|
framework: route.framework,
|
|
@@ -3152,15 +3850,15 @@ async function addRoutes(graph, services) {
|
|
|
3152
3850
|
graph.addNode(rid, node);
|
|
3153
3851
|
nodesAdded++;
|
|
3154
3852
|
}
|
|
3155
|
-
const containsId = (0,
|
|
3853
|
+
const containsId = (0, import_types6.extractedEdgeId)(service.node.id, rid, import_types6.EdgeType.CONTAINS);
|
|
3156
3854
|
if (!graph.hasEdge(containsId)) {
|
|
3157
3855
|
const edge = {
|
|
3158
3856
|
id: containsId,
|
|
3159
3857
|
source: service.node.id,
|
|
3160
3858
|
target: rid,
|
|
3161
|
-
type:
|
|
3162
|
-
provenance:
|
|
3163
|
-
confidence: (0,
|
|
3859
|
+
type: import_types6.EdgeType.CONTAINS,
|
|
3860
|
+
provenance: import_types6.Provenance.EXTRACTED,
|
|
3861
|
+
confidence: (0, import_types6.confidenceForExtracted)("structural"),
|
|
3164
3862
|
evidence: {
|
|
3165
3863
|
file: relFile,
|
|
3166
3864
|
line: route.line,
|
|
@@ -3178,7 +3876,7 @@ async function addRoutes(graph, services) {
|
|
|
3178
3876
|
|
|
3179
3877
|
// src/columns.ts
|
|
3180
3878
|
init_cjs_shims();
|
|
3181
|
-
var
|
|
3879
|
+
var import_types7 = require("@neat.is/types");
|
|
3182
3880
|
var OBSERVED_COLUMN_CONFIDENCE = 0.9;
|
|
3183
3881
|
function normalizeProvenances(provenances) {
|
|
3184
3882
|
return [...new Set(provenances)].sort();
|
|
@@ -3206,10 +3904,10 @@ function foldColumns(existing, names, provenance, confidence) {
|
|
|
3206
3904
|
return out;
|
|
3207
3905
|
}
|
|
3208
3906
|
function columnIsDeclared(col) {
|
|
3209
|
-
return col.provenances.includes(
|
|
3907
|
+
return col.provenances.includes(import_types7.Provenance.EXTRACTED);
|
|
3210
3908
|
}
|
|
3211
3909
|
function columnIsObserved(col) {
|
|
3212
|
-
return col.provenances.includes(
|
|
3910
|
+
return col.provenances.includes(import_types7.Provenance.OBSERVED);
|
|
3213
3911
|
}
|
|
3214
3912
|
|
|
3215
3913
|
// src/ingest.ts
|
|
@@ -3432,7 +4130,7 @@ function languageForExt(relPath) {
|
|
|
3432
4130
|
function relPathForRuntimeFile(filepath, serviceNode, scanPath) {
|
|
3433
4131
|
let p = toPosix2(filepath).replace(/^file:\/\//, "");
|
|
3434
4132
|
if (scanPath && scanPath.length > 0) {
|
|
3435
|
-
const absRoot = toPosix2(
|
|
4133
|
+
const absRoot = toPosix2(import_node_path8.default.resolve(scanPath, serviceNode?.repoPath ?? ""));
|
|
3436
4134
|
const anchor = absRoot.endsWith("/") ? absRoot : `${absRoot}/`;
|
|
3437
4135
|
if (p.startsWith(anchor)) return p.slice(anchor.length);
|
|
3438
4136
|
}
|
|
@@ -3460,10 +4158,10 @@ function resolveDistToSrc(absFilepath, line) {
|
|
|
3460
4158
|
entry2 = null;
|
|
3461
4159
|
const mapPath = `${absFilepath}.map`;
|
|
3462
4160
|
try {
|
|
3463
|
-
if ((0,
|
|
3464
|
-
const raw = JSON.parse((0,
|
|
4161
|
+
if ((0, import_node_fs7.existsSync)(mapPath)) {
|
|
4162
|
+
const raw = JSON.parse((0, import_node_fs7.readFileSync)(mapPath, "utf8"));
|
|
3465
4163
|
const consumer = new sourceMapJs.SourceMapConsumer(raw);
|
|
3466
|
-
entry2 = { consumer, dir:
|
|
4164
|
+
entry2 = { consumer, dir: import_node_path8.default.dirname(mapPath) };
|
|
3467
4165
|
}
|
|
3468
4166
|
} catch {
|
|
3469
4167
|
entry2 = null;
|
|
@@ -3478,7 +4176,7 @@ function resolveDistToSrc(absFilepath, line) {
|
|
|
3478
4176
|
});
|
|
3479
4177
|
if (!pos || !pos.source) return null;
|
|
3480
4178
|
const root = entry2.consumer.sourceRoot ?? "";
|
|
3481
|
-
const resolved =
|
|
4179
|
+
const resolved = import_node_path8.default.resolve(entry2.dir, root, pos.source);
|
|
3482
4180
|
return { filepath: resolved, ...pos.line ? { line: pos.line } : {} };
|
|
3483
4181
|
} catch {
|
|
3484
4182
|
return null;
|
|
@@ -3511,11 +4209,11 @@ function callSiteFromSpan(span, serviceNode, scanPath) {
|
|
|
3511
4209
|
};
|
|
3512
4210
|
}
|
|
3513
4211
|
function reconcileObservedRelPath(graph, serviceName, relPath) {
|
|
3514
|
-
if (graph.hasNode((0,
|
|
4212
|
+
if (graph.hasNode((0, import_types8.fileId)(serviceName, relPath))) return relPath;
|
|
3515
4213
|
let best = null;
|
|
3516
4214
|
graph.forEachNode((_id, attrs) => {
|
|
3517
4215
|
const a = attrs;
|
|
3518
|
-
if (a.type !==
|
|
4216
|
+
if (a.type !== import_types8.NodeType.FileNode || a.service !== serviceName) return;
|
|
3519
4217
|
if (a.discoveredVia === "otel") return;
|
|
3520
4218
|
const p = a.path;
|
|
3521
4219
|
if (!p) return;
|
|
@@ -3527,14 +4225,14 @@ function reconcileObservedRelPath(graph, serviceName, relPath) {
|
|
|
3527
4225
|
}
|
|
3528
4226
|
function ensureObservedFileNode(graph, serviceName, serviceNodeId, callSite) {
|
|
3529
4227
|
const svcAttrs = graph.hasNode(serviceNodeId) ? graph.getNodeAttributes(serviceNodeId) : void 0;
|
|
3530
|
-
const canonicalService = svcAttrs && svcAttrs.type ===
|
|
4228
|
+
const canonicalService = svcAttrs && svcAttrs.type === import_types8.NodeType.ServiceNode && typeof svcAttrs.name === "string" ? svcAttrs.name : serviceName;
|
|
3531
4229
|
const relPath = reconcileObservedRelPath(graph, canonicalService, callSite.relPath);
|
|
3532
|
-
const fileNodeId = (0,
|
|
4230
|
+
const fileNodeId = (0, import_types8.fileId)(canonicalService, relPath);
|
|
3533
4231
|
if (!graph.hasNode(fileNodeId)) {
|
|
3534
4232
|
const language = languageForExt(relPath);
|
|
3535
4233
|
const node = {
|
|
3536
4234
|
id: fileNodeId,
|
|
3537
|
-
type:
|
|
4235
|
+
type: import_types8.NodeType.FileNode,
|
|
3538
4236
|
service: canonicalService,
|
|
3539
4237
|
path: relPath,
|
|
3540
4238
|
...language ? { language } : {},
|
|
@@ -3543,14 +4241,14 @@ function ensureObservedFileNode(graph, serviceName, serviceNodeId, callSite) {
|
|
|
3543
4241
|
};
|
|
3544
4242
|
graph.addNode(fileNodeId, node);
|
|
3545
4243
|
}
|
|
3546
|
-
const containsId = makeObservedEdgeId(
|
|
4244
|
+
const containsId = makeObservedEdgeId(import_types8.EdgeType.CONTAINS, serviceNodeId, fileNodeId);
|
|
3547
4245
|
if (!graph.hasEdge(containsId)) {
|
|
3548
4246
|
const edge = {
|
|
3549
4247
|
id: containsId,
|
|
3550
4248
|
source: serviceNodeId,
|
|
3551
4249
|
target: fileNodeId,
|
|
3552
|
-
type:
|
|
3553
|
-
provenance:
|
|
4250
|
+
type: import_types8.EdgeType.CONTAINS,
|
|
4251
|
+
provenance: import_types8.Provenance.OBSERVED
|
|
3554
4252
|
};
|
|
3555
4253
|
graph.addEdgeWithKey(containsId, serviceNodeId, fileNodeId, edge);
|
|
3556
4254
|
}
|
|
@@ -3574,11 +4272,11 @@ function pickContainingSymbol(candidates, fn) {
|
|
|
3574
4272
|
return [...candidates].sort(bySpan)[0].id;
|
|
3575
4273
|
}
|
|
3576
4274
|
function ensureObservedSymbolNode(graph, fileNodeId, service, relPath, fn, line) {
|
|
3577
|
-
const sid = (0,
|
|
4275
|
+
const sid = (0, import_types8.symbolId)(service, relPath, fn);
|
|
3578
4276
|
if (!graph.hasNode(sid)) {
|
|
3579
4277
|
const node = {
|
|
3580
4278
|
id: sid,
|
|
3581
|
-
type:
|
|
4279
|
+
type: import_types8.NodeType.SymbolNode,
|
|
3582
4280
|
kind: "function",
|
|
3583
4281
|
qualname: fn,
|
|
3584
4282
|
span: { startLine: line, endLine: line },
|
|
@@ -3588,14 +4286,14 @@ function ensureObservedSymbolNode(graph, fileNodeId, service, relPath, fn, line)
|
|
|
3588
4286
|
};
|
|
3589
4287
|
graph.addNode(sid, node);
|
|
3590
4288
|
}
|
|
3591
|
-
const containsId = makeObservedEdgeId(
|
|
4289
|
+
const containsId = makeObservedEdgeId(import_types8.EdgeType.CONTAINS, fileNodeId, sid);
|
|
3592
4290
|
if (!graph.hasEdge(containsId)) {
|
|
3593
4291
|
const edge = {
|
|
3594
4292
|
id: containsId,
|
|
3595
4293
|
source: fileNodeId,
|
|
3596
4294
|
target: sid,
|
|
3597
|
-
type:
|
|
3598
|
-
provenance:
|
|
4295
|
+
type: import_types8.EdgeType.CONTAINS,
|
|
4296
|
+
provenance: import_types8.Provenance.OBSERVED
|
|
3599
4297
|
};
|
|
3600
4298
|
graph.addEdgeWithKey(containsId, fileNodeId, sid, edge);
|
|
3601
4299
|
}
|
|
@@ -3607,9 +4305,9 @@ function landObservedSymbol(graph, fileNodeId, service, relPath, callSite) {
|
|
|
3607
4305
|
let sawSymbol = false;
|
|
3608
4306
|
const candidates = [];
|
|
3609
4307
|
graph.forEachOutboundEdge(fileNodeId, (_edge, edgeAttrs, _source, target) => {
|
|
3610
|
-
if (edgeAttrs.type !==
|
|
4308
|
+
if (edgeAttrs.type !== import_types8.EdgeType.CONTAINS) return;
|
|
3611
4309
|
const t = graph.getNodeAttributes(target);
|
|
3612
|
-
if (t.type !==
|
|
4310
|
+
if (t.type !== import_types8.NodeType.SymbolNode) return;
|
|
3613
4311
|
sawSymbol = true;
|
|
3614
4312
|
if (line >= t.span.startLine && line <= t.span.endLine) {
|
|
3615
4313
|
candidates.push({ id: target, symbol: t });
|
|
@@ -3622,17 +4320,17 @@ function landObservedSymbol(graph, fileNodeId, service, relPath, callSite) {
|
|
|
3622
4320
|
return fileNodeId;
|
|
3623
4321
|
}
|
|
3624
4322
|
function makeObservedEdgeId(type, source, target) {
|
|
3625
|
-
return (0,
|
|
4323
|
+
return (0, import_types8.observedEdgeId)(source, target, type);
|
|
3626
4324
|
}
|
|
3627
4325
|
function makeInferredEdgeId(type, source, target) {
|
|
3628
|
-
return (0,
|
|
4326
|
+
return (0, import_types8.inferredEdgeId)(source, target, type);
|
|
3629
4327
|
}
|
|
3630
4328
|
var INFERRED_CONFIDENCE = 0.6;
|
|
3631
4329
|
var STITCH_MAX_DEPTH = 2;
|
|
3632
4330
|
var STITCH_EDGE_TYPES = /* @__PURE__ */ new Set([
|
|
3633
|
-
|
|
3634
|
-
|
|
3635
|
-
|
|
4331
|
+
import_types8.EdgeType.CALLS,
|
|
4332
|
+
import_types8.EdgeType.CONNECTS_TO,
|
|
4333
|
+
import_types8.EdgeType.DEPENDS_ON
|
|
3636
4334
|
]);
|
|
3637
4335
|
var WIRE_SPAN_KIND_CLIENT = 3;
|
|
3638
4336
|
var WIRE_SPAN_KIND_PRODUCER = 4;
|
|
@@ -3648,11 +4346,11 @@ function spanServesGraphqlOperation(kind) {
|
|
|
3648
4346
|
return kind !== WIRE_SPAN_KIND_CLIENT && kind !== WIRE_SPAN_KIND_PRODUCER && kind !== WIRE_SPAN_KIND_CONSUMER;
|
|
3649
4347
|
}
|
|
3650
4348
|
function ensureGraphqlOperationNode(graph, serviceName, operationType, operationName) {
|
|
3651
|
-
const id = (0,
|
|
4349
|
+
const id = (0, import_types8.graphqlOperationId)(serviceName, operationType, operationName);
|
|
3652
4350
|
if (graph.hasNode(id)) return id;
|
|
3653
4351
|
const node = {
|
|
3654
4352
|
id,
|
|
3655
|
-
type:
|
|
4353
|
+
type: import_types8.NodeType.GraphQLOperationNode,
|
|
3656
4354
|
name: operationName,
|
|
3657
4355
|
service: serviceName,
|
|
3658
4356
|
operationType: operationType.toLowerCase(),
|
|
@@ -3666,11 +4364,11 @@ function spanServesGrpcMethod(kind) {
|
|
|
3666
4364
|
return kind !== WIRE_SPAN_KIND_CLIENT && kind !== WIRE_SPAN_KIND_PRODUCER && kind !== WIRE_SPAN_KIND_CONSUMER;
|
|
3667
4365
|
}
|
|
3668
4366
|
function ensureGrpcMethodNode(graph, rpcService, rpcMethod) {
|
|
3669
|
-
const id = (0,
|
|
4367
|
+
const id = (0, import_types8.grpcMethodId)(rpcService, rpcMethod);
|
|
3670
4368
|
if (graph.hasNode(id)) return id;
|
|
3671
4369
|
const node = {
|
|
3672
4370
|
id,
|
|
3673
|
-
type:
|
|
4371
|
+
type: import_types8.NodeType.GrpcMethodNode,
|
|
3674
4372
|
name: `${rpcService}/${rpcMethod}`,
|
|
3675
4373
|
rpcService,
|
|
3676
4374
|
rpcMethod,
|
|
@@ -3683,11 +4381,11 @@ function spanServesWebsocketChannel(kind) {
|
|
|
3683
4381
|
return kind !== WIRE_SPAN_KIND_CLIENT && kind !== WIRE_SPAN_KIND_PRODUCER && kind !== WIRE_SPAN_KIND_CONSUMER;
|
|
3684
4382
|
}
|
|
3685
4383
|
function ensureWebsocketChannelNode(graph, serviceName, channel) {
|
|
3686
|
-
const id = (0,
|
|
4384
|
+
const id = (0, import_types8.websocketChannelId)(serviceName, channel);
|
|
3687
4385
|
if (graph.hasNode(id)) return id;
|
|
3688
4386
|
const node = {
|
|
3689
4387
|
id,
|
|
3690
|
-
type:
|
|
4388
|
+
type: import_types8.NodeType.WebSocketChannelNode,
|
|
3691
4389
|
name: channel,
|
|
3692
4390
|
service: serviceName,
|
|
3693
4391
|
channel,
|
|
@@ -3700,11 +4398,11 @@ function messagingDestinationKind(system) {
|
|
|
3700
4398
|
return `${system}-topic`;
|
|
3701
4399
|
}
|
|
3702
4400
|
function ensureMessagingDestinationNode(graph, system, destination) {
|
|
3703
|
-
const id = (0,
|
|
4401
|
+
const id = (0, import_types8.infraId)(messagingDestinationKind(system), destination);
|
|
3704
4402
|
if (graph.hasNode(id)) return id;
|
|
3705
4403
|
const node = {
|
|
3706
4404
|
id,
|
|
3707
|
-
type:
|
|
4405
|
+
type: import_types8.NodeType.InfraNode,
|
|
3708
4406
|
name: destination,
|
|
3709
4407
|
provider: "self",
|
|
3710
4408
|
kind: messagingDestinationKind(system)
|
|
@@ -3748,9 +4446,9 @@ function lookupParentSpan(traceId, parentSpanId, now) {
|
|
|
3748
4446
|
};
|
|
3749
4447
|
}
|
|
3750
4448
|
function resolveServiceId(graph, host, env) {
|
|
3751
|
-
const envTagged = (0,
|
|
4449
|
+
const envTagged = (0, import_types8.serviceId)(host, env);
|
|
3752
4450
|
if (graph.hasNode(envTagged)) return envTagged;
|
|
3753
|
-
const envLess = (0,
|
|
4451
|
+
const envLess = (0, import_types8.serviceId)(host);
|
|
3754
4452
|
if (envLess !== envTagged && graph.hasNode(envLess)) return envLess;
|
|
3755
4453
|
let sameEnv = null;
|
|
3756
4454
|
let envLessMatch = null;
|
|
@@ -3758,7 +4456,7 @@ function resolveServiceId(graph, host, env) {
|
|
|
3758
4456
|
graph.forEachNode((id, attrs) => {
|
|
3759
4457
|
if (sameEnv) return;
|
|
3760
4458
|
const a = attrs;
|
|
3761
|
-
if (a.type !==
|
|
4459
|
+
if (a.type !== import_types8.NodeType.ServiceNode) return;
|
|
3762
4460
|
const matchesByName = a.name === host;
|
|
3763
4461
|
const matchesByAlias = a.aliases ? a.aliases.includes(host) : false;
|
|
3764
4462
|
if (!matchesByName && !matchesByAlias) return;
|
|
@@ -3773,14 +4471,14 @@ function resolveServiceId(graph, host, env) {
|
|
|
3773
4471
|
return sameEnv ?? envLessMatch ?? anyMatch;
|
|
3774
4472
|
}
|
|
3775
4473
|
function frontierIdFor(host) {
|
|
3776
|
-
return (0,
|
|
4474
|
+
return (0, import_types8.frontierId)(host);
|
|
3777
4475
|
}
|
|
3778
4476
|
function ensureServiceNode(graph, serviceName, env) {
|
|
3779
|
-
const id = (0,
|
|
4477
|
+
const id = (0, import_types8.serviceId)(serviceName, env);
|
|
3780
4478
|
if (graph.hasNode(id)) return id;
|
|
3781
4479
|
const wanted = serviceName.toLowerCase();
|
|
3782
4480
|
const extractedId = graph.findNode((_nid, attrs) => {
|
|
3783
|
-
if (attrs.type !==
|
|
4481
|
+
if (attrs.type !== import_types8.NodeType.ServiceNode) return false;
|
|
3784
4482
|
const svc = attrs;
|
|
3785
4483
|
if (svc.discoveredVia === "otel") return false;
|
|
3786
4484
|
return typeof svc.name === "string" && svc.name.toLowerCase() === wanted;
|
|
@@ -3788,7 +4486,7 @@ function ensureServiceNode(graph, serviceName, env) {
|
|
|
3788
4486
|
if (extractedId) return extractedId;
|
|
3789
4487
|
const node = {
|
|
3790
4488
|
id,
|
|
3791
|
-
type:
|
|
4489
|
+
type: import_types8.NodeType.ServiceNode,
|
|
3792
4490
|
name: serviceName,
|
|
3793
4491
|
language: "unknown",
|
|
3794
4492
|
discoveredVia: "otel",
|
|
@@ -3798,11 +4496,11 @@ function ensureServiceNode(graph, serviceName, env) {
|
|
|
3798
4496
|
return id;
|
|
3799
4497
|
}
|
|
3800
4498
|
function ensureInfraNode(graph, kind, name, provider) {
|
|
3801
|
-
const id = (0,
|
|
4499
|
+
const id = (0, import_types8.infraId)(kind, name);
|
|
3802
4500
|
if (graph.hasNode(id)) return id;
|
|
3803
4501
|
const node = {
|
|
3804
4502
|
id,
|
|
3805
|
-
type:
|
|
4503
|
+
type: import_types8.NodeType.InfraNode,
|
|
3806
4504
|
name,
|
|
3807
4505
|
provider,
|
|
3808
4506
|
kind
|
|
@@ -3814,7 +4512,7 @@ var COLUMN_BEARING_INFRA_KINDS = /* @__PURE__ */ new Set(["sql-table", "supabase
|
|
|
3814
4512
|
function mergeColumnsAt(graph, tableNodeId, columns, provenance, confidence) {
|
|
3815
4513
|
if (!columns || columns.length === 0 || !graph.hasNode(tableNodeId)) return;
|
|
3816
4514
|
const node = graph.getNodeAttributes(tableNodeId);
|
|
3817
|
-
if (node.type !==
|
|
4515
|
+
if (node.type !== import_types8.NodeType.InfraNode || !node.kind || !COLUMN_BEARING_INFRA_KINDS.has(node.kind)) {
|
|
3818
4516
|
return;
|
|
3819
4517
|
}
|
|
3820
4518
|
graph.replaceNodeAttributes(tableNodeId, {
|
|
@@ -3823,14 +4521,14 @@ function mergeColumnsAt(graph, tableNodeId, columns, provenance, confidence) {
|
|
|
3823
4521
|
});
|
|
3824
4522
|
}
|
|
3825
4523
|
function mergeObservedColumns(graph, tableNodeId, columns) {
|
|
3826
|
-
mergeColumnsAt(graph, tableNodeId, columns,
|
|
4524
|
+
mergeColumnsAt(graph, tableNodeId, columns, import_types8.Provenance.OBSERVED, OBSERVED_COLUMN_CONFIDENCE);
|
|
3827
4525
|
}
|
|
3828
4526
|
function ensureDatabaseNode(graph, host, engine) {
|
|
3829
|
-
const id = (0,
|
|
4527
|
+
const id = (0, import_types8.databaseId)(host);
|
|
3830
4528
|
if (graph.hasNode(id)) return id;
|
|
3831
4529
|
const node = {
|
|
3832
4530
|
id,
|
|
3833
|
-
type:
|
|
4531
|
+
type: import_types8.NodeType.DatabaseNode,
|
|
3834
4532
|
name: host,
|
|
3835
4533
|
engine,
|
|
3836
4534
|
engineVersion: "unknown",
|
|
@@ -3842,11 +4540,11 @@ function ensureDatabaseNode(graph, host, engine) {
|
|
|
3842
4540
|
return id;
|
|
3843
4541
|
}
|
|
3844
4542
|
function ensureLocalDatabaseNode(graph, serviceName, name, engine) {
|
|
3845
|
-
const id = (0,
|
|
4543
|
+
const id = (0, import_types8.localDatabaseId)(serviceName, name);
|
|
3846
4544
|
if (graph.hasNode(id)) return id;
|
|
3847
4545
|
const node = {
|
|
3848
4546
|
id,
|
|
3849
|
-
type:
|
|
4547
|
+
type: import_types8.NodeType.DatabaseNode,
|
|
3850
4548
|
name,
|
|
3851
4549
|
engine,
|
|
3852
4550
|
engineVersion: "unknown",
|
|
@@ -3861,17 +4559,17 @@ function findDeclaredDatabaseForService(graph, serviceNodeId, engine) {
|
|
|
3861
4559
|
const sources = [serviceNodeId];
|
|
3862
4560
|
for (const edgeId of graph.outboundEdges(serviceNodeId)) {
|
|
3863
4561
|
const e = graph.getEdgeAttributes(edgeId);
|
|
3864
|
-
if (e.type ===
|
|
4562
|
+
if (e.type === import_types8.EdgeType.CONTAINS) sources.push(e.target);
|
|
3865
4563
|
}
|
|
3866
4564
|
const matches = /* @__PURE__ */ new Set();
|
|
3867
4565
|
for (const src of sources) {
|
|
3868
4566
|
if (!graph.hasNode(src)) continue;
|
|
3869
4567
|
for (const edgeId of graph.outboundEdges(src)) {
|
|
3870
4568
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
3871
|
-
if (edge.type !==
|
|
4569
|
+
if (edge.type !== import_types8.EdgeType.CONNECTS_TO || edge.provenance !== import_types8.Provenance.EXTRACTED) continue;
|
|
3872
4570
|
if (!graph.hasNode(edge.target)) continue;
|
|
3873
4571
|
const target = graph.getNodeAttributes(edge.target);
|
|
3874
|
-
if (target.type !==
|
|
4572
|
+
if (target.type !== import_types8.NodeType.DatabaseNode || target.engine !== engine) continue;
|
|
3875
4573
|
matches.add(edge.target);
|
|
3876
4574
|
}
|
|
3877
4575
|
}
|
|
@@ -3886,7 +4584,7 @@ function ensureFrontierNode(graph, host, ts) {
|
|
|
3886
4584
|
}
|
|
3887
4585
|
const node = {
|
|
3888
4586
|
id,
|
|
3889
|
-
type:
|
|
4587
|
+
type: import_types8.NodeType.FrontierNode,
|
|
3890
4588
|
name: host,
|
|
3891
4589
|
host,
|
|
3892
4590
|
firstObserved: ts,
|
|
@@ -3910,11 +4608,11 @@ function upsertObservedEdge(graph, type, source, target, ts, isError = false, ev
|
|
|
3910
4608
|
};
|
|
3911
4609
|
const updated = {
|
|
3912
4610
|
...existing,
|
|
3913
|
-
provenance:
|
|
4611
|
+
provenance: import_types8.Provenance.OBSERVED,
|
|
3914
4612
|
lastObserved: ts,
|
|
3915
4613
|
callCount: newSpanCount,
|
|
3916
4614
|
signal: newSignal,
|
|
3917
|
-
confidence: (0,
|
|
4615
|
+
confidence: (0, import_types8.confidenceForObservedSignal)(newSignal),
|
|
3918
4616
|
grain
|
|
3919
4617
|
// backfills legacy edges that predate ADR-142
|
|
3920
4618
|
};
|
|
@@ -3931,8 +4629,8 @@ function upsertObservedEdge(graph, type, source, target, ts, isError = false, ev
|
|
|
3931
4629
|
source,
|
|
3932
4630
|
target,
|
|
3933
4631
|
type,
|
|
3934
|
-
provenance:
|
|
3935
|
-
confidence: (0,
|
|
4632
|
+
provenance: import_types8.Provenance.OBSERVED,
|
|
4633
|
+
confidence: (0, import_types8.confidenceForObservedSignal)(signal),
|
|
3936
4634
|
lastObserved: ts,
|
|
3937
4635
|
callCount: 1,
|
|
3938
4636
|
signal,
|
|
@@ -3954,9 +4652,9 @@ function stitchTrace(graph, sourceServiceId, ts) {
|
|
|
3954
4652
|
const outbound = graph.outboundEdges(nodeId);
|
|
3955
4653
|
for (const edgeId of outbound) {
|
|
3956
4654
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
3957
|
-
if (edge.provenance !==
|
|
4655
|
+
if (edge.provenance !== import_types8.Provenance.EXTRACTED) continue;
|
|
3958
4656
|
if (!STITCH_EDGE_TYPES.has(edge.type)) continue;
|
|
3959
|
-
if (graph.hasEdge((0,
|
|
4657
|
+
if (graph.hasEdge((0, import_types8.observedEdgeId)(edge.source, edge.target, edge.type))) continue;
|
|
3960
4658
|
upsertInferredEdge(graph, edge.type, edge.source, edge.target, ts);
|
|
3961
4659
|
if (!visited.has(edge.target)) {
|
|
3962
4660
|
visited.add(edge.target);
|
|
@@ -3978,23 +4676,23 @@ function upsertInferredEdge(graph, type, source, target, ts) {
|
|
|
3978
4676
|
source,
|
|
3979
4677
|
target,
|
|
3980
4678
|
type,
|
|
3981
|
-
provenance:
|
|
4679
|
+
provenance: import_types8.Provenance.INFERRED,
|
|
3982
4680
|
confidence: INFERRED_CONFIDENCE,
|
|
3983
4681
|
lastObserved: ts
|
|
3984
4682
|
};
|
|
3985
4683
|
graph.addEdgeWithKey(id, source, target, edge);
|
|
3986
4684
|
}
|
|
3987
4685
|
async function appendErrorEvent(ctx, ev) {
|
|
3988
|
-
await
|
|
3989
|
-
await
|
|
4686
|
+
await import_node_fs7.promises.mkdir(import_node_path8.default.dirname(ctx.errorsPath), { recursive: true });
|
|
4687
|
+
await import_node_fs7.promises.appendFile(ctx.errorsPath, JSON.stringify(ev) + "\n", "utf8");
|
|
3990
4688
|
}
|
|
3991
4689
|
function incidentAffectedNode(span, graph, scanPath) {
|
|
3992
|
-
const sid = (0,
|
|
4690
|
+
const sid = (0, import_types8.serviceId)(span.service, span.env);
|
|
3993
4691
|
const serviceNode = graph && graph.hasNode(sid) ? graph.getNodeAttributes(sid) : void 0;
|
|
3994
4692
|
const callSite = callSiteFromSpan(span, serviceNode, scanPath);
|
|
3995
4693
|
if (callSite) {
|
|
3996
4694
|
const relPath = graph ? reconcileObservedRelPath(graph, span.service, callSite.relPath) : callSite.relPath;
|
|
3997
|
-
return (0,
|
|
4695
|
+
return (0, import_types8.fileId)(span.service, relPath);
|
|
3998
4696
|
}
|
|
3999
4697
|
return sid;
|
|
4000
4698
|
}
|
|
@@ -4027,8 +4725,8 @@ function makeErrorSpanWriter(errorsPath, graph, scanPath) {
|
|
|
4027
4725
|
return async (span) => {
|
|
4028
4726
|
const ev = buildErrorEventForReceiver(span, graph, scanPath);
|
|
4029
4727
|
if (!ev) return;
|
|
4030
|
-
await
|
|
4031
|
-
await
|
|
4728
|
+
await import_node_fs7.promises.mkdir(import_node_path8.default.dirname(errorsPath), { recursive: true });
|
|
4729
|
+
await import_node_fs7.promises.appendFile(errorsPath, JSON.stringify(ev) + "\n", "utf8");
|
|
4032
4730
|
};
|
|
4033
4731
|
}
|
|
4034
4732
|
async function recordFailingResponseIncident(ctx, span, affectedNode, timestamp, statusCode, count, firstTimestamp) {
|
|
@@ -4119,7 +4817,7 @@ function findRouteNodeByHttpRoute(graph, serviceName, method, httpRoute) {
|
|
|
4119
4817
|
graph.forEachNode((id, attrs) => {
|
|
4120
4818
|
if (found) return;
|
|
4121
4819
|
const a = attrs;
|
|
4122
|
-
if (a.type !==
|
|
4820
|
+
if (a.type !== import_types8.NodeType.RouteNode || a.service !== serviceName) return;
|
|
4123
4821
|
if (m && a.method !== "ALL" && a.method !== m) return;
|
|
4124
4822
|
if (normalizePathTemplate(a.pathTemplate) === target) found = id;
|
|
4125
4823
|
});
|
|
@@ -4150,7 +4848,7 @@ async function handleSpan(ctx, span) {
|
|
|
4150
4848
|
let targetId;
|
|
4151
4849
|
if (host) {
|
|
4152
4850
|
ensureDatabaseNode(ctx.graph, host, span.dbSystem);
|
|
4153
|
-
targetId = (0,
|
|
4851
|
+
targetId = (0, import_types8.databaseId)(host);
|
|
4154
4852
|
} else {
|
|
4155
4853
|
const declared = findDeclaredDatabaseForService(ctx.graph, sourceId, span.dbSystem);
|
|
4156
4854
|
if (declared) {
|
|
@@ -4167,7 +4865,7 @@ async function handleSpan(ctx, span) {
|
|
|
4167
4865
|
}
|
|
4168
4866
|
const result = upsertObservedEdge(
|
|
4169
4867
|
ctx.graph,
|
|
4170
|
-
|
|
4868
|
+
import_types8.EdgeType.CONNECTS_TO,
|
|
4171
4869
|
observedSource(),
|
|
4172
4870
|
targetId,
|
|
4173
4871
|
ts,
|
|
@@ -4179,7 +4877,7 @@ async function handleSpan(ctx, span) {
|
|
|
4179
4877
|
const collectionId = ensureInfraNode(ctx.graph, "mongodb-collection", span.dbCollection, "self");
|
|
4180
4878
|
upsertObservedEdge(
|
|
4181
4879
|
ctx.graph,
|
|
4182
|
-
|
|
4880
|
+
import_types8.EdgeType.CALLS,
|
|
4183
4881
|
observedSource(),
|
|
4184
4882
|
collectionId,
|
|
4185
4883
|
ts,
|
|
@@ -4191,7 +4889,7 @@ async function handleSpan(ctx, span) {
|
|
|
4191
4889
|
const tableId = ensureInfraNode(ctx.graph, "sql-table", span.dbTable, "self");
|
|
4192
4890
|
upsertObservedEdge(
|
|
4193
4891
|
ctx.graph,
|
|
4194
|
-
|
|
4892
|
+
import_types8.EdgeType.CALLS,
|
|
4195
4893
|
observedSource(),
|
|
4196
4894
|
tableId,
|
|
4197
4895
|
ts,
|
|
@@ -4207,7 +4905,7 @@ async function handleSpan(ctx, span) {
|
|
|
4207
4905
|
span.messagingSystem,
|
|
4208
4906
|
span.messagingDestination
|
|
4209
4907
|
);
|
|
4210
|
-
const edgeType = span.kind === WIRE_SPAN_KIND_CONSUMER ?
|
|
4908
|
+
const edgeType = span.kind === WIRE_SPAN_KIND_CONSUMER ? import_types8.EdgeType.CONSUMES_FROM : import_types8.EdgeType.PUBLISHES_TO;
|
|
4211
4909
|
const result = upsertObservedEdge(
|
|
4212
4910
|
ctx.graph,
|
|
4213
4911
|
edgeType,
|
|
@@ -4227,7 +4925,7 @@ async function handleSpan(ctx, span) {
|
|
|
4227
4925
|
);
|
|
4228
4926
|
const result = upsertObservedEdge(
|
|
4229
4927
|
ctx.graph,
|
|
4230
|
-
|
|
4928
|
+
import_types8.EdgeType.CONTAINS,
|
|
4231
4929
|
observedSource(),
|
|
4232
4930
|
targetId,
|
|
4233
4931
|
ts,
|
|
@@ -4239,7 +4937,7 @@ async function handleSpan(ctx, span) {
|
|
|
4239
4937
|
const targetId = ensureGrpcMethodNode(ctx.graph, span.rpcService, span.rpcMethod);
|
|
4240
4938
|
const result = upsertObservedEdge(
|
|
4241
4939
|
ctx.graph,
|
|
4242
|
-
|
|
4940
|
+
import_types8.EdgeType.CONTAINS,
|
|
4243
4941
|
observedSource(),
|
|
4244
4942
|
targetId,
|
|
4245
4943
|
ts,
|
|
@@ -4255,7 +4953,7 @@ async function handleSpan(ctx, span) {
|
|
|
4255
4953
|
);
|
|
4256
4954
|
const result = upsertObservedEdge(
|
|
4257
4955
|
ctx.graph,
|
|
4258
|
-
|
|
4956
|
+
import_types8.EdgeType.CONNECTS_TO,
|
|
4259
4957
|
observedSource(),
|
|
4260
4958
|
targetId,
|
|
4261
4959
|
ts,
|
|
@@ -4271,7 +4969,7 @@ async function handleSpan(ctx, span) {
|
|
|
4271
4969
|
if (targetId && targetId !== sourceId) {
|
|
4272
4970
|
upsertObservedEdge(
|
|
4273
4971
|
ctx.graph,
|
|
4274
|
-
|
|
4972
|
+
import_types8.EdgeType.CALLS,
|
|
4275
4973
|
observedSource(),
|
|
4276
4974
|
targetId,
|
|
4277
4975
|
ts,
|
|
@@ -4284,7 +4982,7 @@ async function handleSpan(ctx, span) {
|
|
|
4284
4982
|
const frontierNodeId = ensureFrontierNode(ctx.graph, host, ts);
|
|
4285
4983
|
upsertObservedEdge(
|
|
4286
4984
|
ctx.graph,
|
|
4287
|
-
|
|
4985
|
+
import_types8.EdgeType.CALLS,
|
|
4288
4986
|
observedSource(),
|
|
4289
4987
|
frontierNodeId,
|
|
4290
4988
|
ts,
|
|
@@ -4310,7 +5008,7 @@ async function handleSpan(ctx, span) {
|
|
|
4310
5008
|
} : void 0;
|
|
4311
5009
|
upsertObservedEdge(
|
|
4312
5010
|
ctx.graph,
|
|
4313
|
-
|
|
5011
|
+
import_types8.EdgeType.CALLS,
|
|
4314
5012
|
fallbackSource,
|
|
4315
5013
|
sourceId,
|
|
4316
5014
|
ts,
|
|
@@ -4329,7 +5027,7 @@ async function handleSpan(ctx, span) {
|
|
|
4329
5027
|
);
|
|
4330
5028
|
if (routeNodeId) {
|
|
4331
5029
|
const routeSvc = ctx.graph.getNodeAttributes(routeNodeId).service;
|
|
4332
|
-
upsertObservedEdge(ctx.graph,
|
|
5030
|
+
upsertObservedEdge(ctx.graph, import_types8.EdgeType.CONTAINS, (0, import_types8.serviceId)(routeSvc), routeNodeId, ts, isError);
|
|
4333
5031
|
}
|
|
4334
5032
|
}
|
|
4335
5033
|
if (span.statusCode === 2) {
|
|
@@ -4368,7 +5066,7 @@ function promoteFrontierNodes(graph, opts = {}) {
|
|
|
4368
5066
|
const aliasIndex = /* @__PURE__ */ new Map();
|
|
4369
5067
|
graph.forEachNode((id, attrs) => {
|
|
4370
5068
|
const a = attrs;
|
|
4371
|
-
if (a.type !==
|
|
5069
|
+
if (a.type !== import_types8.NodeType.ServiceNode) return;
|
|
4372
5070
|
aliasIndex.set(a.name, id);
|
|
4373
5071
|
if (a.aliases) {
|
|
4374
5072
|
for (const alias of a.aliases) aliasIndex.set(alias, id);
|
|
@@ -4377,7 +5075,7 @@ function promoteFrontierNodes(graph, opts = {}) {
|
|
|
4377
5075
|
const toPromote = [];
|
|
4378
5076
|
graph.forEachNode((id, attrs) => {
|
|
4379
5077
|
const a = attrs;
|
|
4380
|
-
if (a.type !==
|
|
5078
|
+
if (a.type !== import_types8.NodeType.FrontierNode) return;
|
|
4381
5079
|
const target = aliasIndex.get(a.host);
|
|
4382
5080
|
if (!target) return;
|
|
4383
5081
|
if (target === id) return;
|
|
@@ -4411,7 +5109,7 @@ function rewireFrontierEdges(graph, frontierId2, serviceId7) {
|
|
|
4411
5109
|
}
|
|
4412
5110
|
function rebuildEdge(graph, edge, newSource, newTarget, oldEdgeId) {
|
|
4413
5111
|
graph.dropEdge(oldEdgeId);
|
|
4414
|
-
const newId = edge.provenance ===
|
|
5112
|
+
const newId = edge.provenance === import_types8.Provenance.OBSERVED ? (0, import_types8.observedEdgeId)(newSource, newTarget, edge.type) : edge.provenance === import_types8.Provenance.INFERRED ? (0, import_types8.inferredEdgeId)(newSource, newTarget, edge.type) : (0, import_types8.extractedEdgeId)(newSource, newTarget, edge.type);
|
|
4415
5113
|
if (graph.hasEdge(newId)) {
|
|
4416
5114
|
const existing = graph.getEdgeAttributes(newId);
|
|
4417
5115
|
const merged = {
|
|
@@ -4442,12 +5140,12 @@ async function markStaleEdges(graph, options = {}) {
|
|
|
4442
5140
|
const project = options.project ?? DEFAULT_PROJECT;
|
|
4443
5141
|
graph.forEachEdge((id, attrs) => {
|
|
4444
5142
|
const e = attrs;
|
|
4445
|
-
if (e.provenance !==
|
|
5143
|
+
if (e.provenance !== import_types8.Provenance.OBSERVED) return;
|
|
4446
5144
|
if (!e.lastObserved) return;
|
|
4447
5145
|
const threshold = thresholdForEdgeType(e.type, thresholds);
|
|
4448
5146
|
const age = now - new Date(e.lastObserved).getTime();
|
|
4449
5147
|
if (age > threshold) {
|
|
4450
|
-
const updated = { ...e, provenance:
|
|
5148
|
+
const updated = { ...e, provenance: import_types8.Provenance.STALE, confidence: 0.3 };
|
|
4451
5149
|
graph.replaceEdgeAttributes(id, updated);
|
|
4452
5150
|
events.push({
|
|
4453
5151
|
edgeId: id,
|
|
@@ -4464,8 +5162,8 @@ async function markStaleEdges(graph, options = {}) {
|
|
|
4464
5162
|
project,
|
|
4465
5163
|
payload: {
|
|
4466
5164
|
edgeId: id,
|
|
4467
|
-
from:
|
|
4468
|
-
to:
|
|
5165
|
+
from: import_types8.Provenance.OBSERVED,
|
|
5166
|
+
to: import_types8.Provenance.STALE
|
|
4469
5167
|
}
|
|
4470
5168
|
});
|
|
4471
5169
|
}
|
|
@@ -4476,13 +5174,13 @@ async function markStaleEdges(graph, options = {}) {
|
|
|
4476
5174
|
return { count: events.length, events };
|
|
4477
5175
|
}
|
|
4478
5176
|
async function appendStaleEvents(staleEventsPath, events) {
|
|
4479
|
-
await
|
|
5177
|
+
await import_node_fs7.promises.mkdir(import_node_path8.default.dirname(staleEventsPath), { recursive: true });
|
|
4480
5178
|
const lines = events.map((e) => JSON.stringify(e)).join("\n") + "\n";
|
|
4481
|
-
await
|
|
5179
|
+
await import_node_fs7.promises.appendFile(staleEventsPath, lines, "utf8");
|
|
4482
5180
|
}
|
|
4483
5181
|
async function readStaleEvents(staleEventsPath) {
|
|
4484
5182
|
try {
|
|
4485
|
-
const raw = await
|
|
5183
|
+
const raw = await import_node_fs7.promises.readFile(staleEventsPath, "utf8");
|
|
4486
5184
|
return raw.split("\n").filter((line) => line.length > 0).map((line) => JSON.parse(line));
|
|
4487
5185
|
} catch (err) {
|
|
4488
5186
|
if (err.code === "ENOENT") return [];
|
|
@@ -4516,7 +5214,7 @@ function startStalenessLoop(graph, options = {}) {
|
|
|
4516
5214
|
}
|
|
4517
5215
|
async function readErrorEvents(errorsPath) {
|
|
4518
5216
|
try {
|
|
4519
|
-
const raw = await
|
|
5217
|
+
const raw = await import_node_fs7.promises.readFile(errorsPath, "utf8");
|
|
4520
5218
|
const events = raw.split("\n").filter((line) => line.length > 0).map((line) => JSON.parse(line));
|
|
4521
5219
|
return dedupeIncidents(events);
|
|
4522
5220
|
} catch (err) {
|
|
@@ -4574,7 +5272,7 @@ function mergeSnapshot(graph, snapshot) {
|
|
|
4574
5272
|
const validEdges = [];
|
|
4575
5273
|
for (const node of incomingNodes) {
|
|
4576
5274
|
if (node.attributes === void 0) continue;
|
|
4577
|
-
const parsed =
|
|
5275
|
+
const parsed = import_types8.GraphNodeSchema.safeParse(node.attributes);
|
|
4578
5276
|
if (!parsed.success) {
|
|
4579
5277
|
issues.push(`node "${node.key}": ${describeZodIssues(parsed.error)}`);
|
|
4580
5278
|
continue;
|
|
@@ -4583,7 +5281,7 @@ function mergeSnapshot(graph, snapshot) {
|
|
|
4583
5281
|
}
|
|
4584
5282
|
for (const edge of incomingEdges) {
|
|
4585
5283
|
if (edge.attributes === void 0) continue;
|
|
4586
|
-
const parsed =
|
|
5284
|
+
const parsed = import_types8.GraphEdgeSchema.safeParse(edge.attributes);
|
|
4587
5285
|
if (!parsed.success) {
|
|
4588
5286
|
const label = edge.key ?? `${edge.source}->${edge.target}`;
|
|
4589
5287
|
issues.push(`edge "${label}": ${describeZodIssues(parsed.error)}`);
|
|
@@ -4613,16 +5311,16 @@ function mergeSnapshot(graph, snapshot) {
|
|
|
4613
5311
|
|
|
4614
5312
|
// src/extract/services.ts
|
|
4615
5313
|
init_cjs_shims();
|
|
4616
|
-
var
|
|
4617
|
-
var
|
|
5314
|
+
var import_node_fs11 = require("fs");
|
|
5315
|
+
var import_node_path12 = __toESM(require("path"), 1);
|
|
4618
5316
|
var import_ignore = __toESM(require("ignore"), 1);
|
|
4619
5317
|
var import_minimatch2 = require("minimatch");
|
|
4620
|
-
var
|
|
5318
|
+
var import_types10 = require("@neat.is/types");
|
|
4621
5319
|
|
|
4622
5320
|
// src/extract/python.ts
|
|
4623
5321
|
init_cjs_shims();
|
|
4624
|
-
var
|
|
4625
|
-
var
|
|
5322
|
+
var import_node_fs8 = require("fs");
|
|
5323
|
+
var import_node_path9 = __toESM(require("path"), 1);
|
|
4626
5324
|
var import_smol_toml = require("smol-toml");
|
|
4627
5325
|
var REQUIREMENT_LINE = /^\s*([A-Za-z0-9_.-]+)(?:\[[^\]]*\])?\s*(?:(==)\s*([A-Za-z0-9_.+-]+))?/;
|
|
4628
5326
|
function parseRequirementsTxt(content) {
|
|
@@ -4655,25 +5353,25 @@ function depsFromPyProject(pyproject) {
|
|
|
4655
5353
|
return out;
|
|
4656
5354
|
}
|
|
4657
5355
|
async function discoverPythonService(serviceDir) {
|
|
4658
|
-
const pyprojectPath =
|
|
4659
|
-
const requirementsPath =
|
|
4660
|
-
const setupPath =
|
|
5356
|
+
const pyprojectPath = import_node_path9.default.join(serviceDir, "pyproject.toml");
|
|
5357
|
+
const requirementsPath = import_node_path9.default.join(serviceDir, "requirements.txt");
|
|
5358
|
+
const setupPath = import_node_path9.default.join(serviceDir, "setup.py");
|
|
4661
5359
|
const hasPyproject = await exists(pyprojectPath);
|
|
4662
5360
|
const hasRequirements = await exists(requirementsPath);
|
|
4663
5361
|
const hasSetup = await exists(setupPath);
|
|
4664
5362
|
if (!hasPyproject && !hasRequirements && !hasSetup) return null;
|
|
4665
|
-
let name =
|
|
5363
|
+
let name = import_node_path9.default.basename(serviceDir);
|
|
4666
5364
|
let version;
|
|
4667
5365
|
const dependencies = {};
|
|
4668
5366
|
if (hasPyproject) {
|
|
4669
|
-
const raw = await
|
|
5367
|
+
const raw = await import_node_fs8.promises.readFile(pyprojectPath, "utf8");
|
|
4670
5368
|
const pyproject = (0, import_smol_toml.parse)(raw);
|
|
4671
5369
|
name = pyproject.project?.name ?? pyproject.tool?.poetry?.name ?? name;
|
|
4672
5370
|
version = pyproject.project?.version ?? pyproject.tool?.poetry?.version ?? void 0;
|
|
4673
5371
|
Object.assign(dependencies, depsFromPyProject(pyproject));
|
|
4674
5372
|
}
|
|
4675
5373
|
if (hasRequirements) {
|
|
4676
|
-
const raw = await
|
|
5374
|
+
const raw = await import_node_fs8.promises.readFile(requirementsPath, "utf8");
|
|
4677
5375
|
Object.assign(dependencies, parseRequirementsTxt(raw));
|
|
4678
5376
|
}
|
|
4679
5377
|
return { name, version, dependencies };
|
|
@@ -4688,9 +5386,9 @@ function pythonToPackage(service) {
|
|
|
4688
5386
|
|
|
4689
5387
|
// src/extract/go.ts
|
|
4690
5388
|
init_cjs_shims();
|
|
4691
|
-
var
|
|
4692
|
-
var
|
|
4693
|
-
var
|
|
5389
|
+
var import_node_fs9 = require("fs");
|
|
5390
|
+
var import_node_path10 = __toESM(require("path"), 1);
|
|
5391
|
+
var import_types9 = require("@neat.is/types");
|
|
4694
5392
|
function parseGoMod(source) {
|
|
4695
5393
|
const module2 = source.match(/^\s*module\s+(\S+)\s*$/m)?.[1];
|
|
4696
5394
|
if (!module2) return null;
|
|
@@ -4709,7 +5407,7 @@ function parseGoMod(source) {
|
|
|
4709
5407
|
async function discoverGoService(scanPath, dir) {
|
|
4710
5408
|
let raw;
|
|
4711
5409
|
try {
|
|
4712
|
-
raw = await
|
|
5410
|
+
raw = await import_node_fs9.promises.readFile(import_node_path10.default.join(dir, "go.mod"), "utf8");
|
|
4713
5411
|
} catch {
|
|
4714
5412
|
return null;
|
|
4715
5413
|
}
|
|
@@ -4718,12 +5416,12 @@ async function discoverGoService(scanPath, dir) {
|
|
|
4718
5416
|
const name = mod.module.split("/").filter(Boolean).pop() ?? mod.module;
|
|
4719
5417
|
const pkg = { name, dependencies: mod.dependencies };
|
|
4720
5418
|
const node = {
|
|
4721
|
-
id: (0,
|
|
4722
|
-
type:
|
|
5419
|
+
id: (0, import_types9.serviceId)(name),
|
|
5420
|
+
type: import_types9.NodeType.ServiceNode,
|
|
4723
5421
|
name,
|
|
4724
5422
|
language: "go",
|
|
4725
5423
|
dependencies: mod.dependencies,
|
|
4726
|
-
repoPath:
|
|
5424
|
+
repoPath: import_node_path10.default.relative(scanPath, dir),
|
|
4727
5425
|
...mod.dependencies["github.com/gin-gonic/gin"] ? { framework: "gin" } : {}
|
|
4728
5426
|
};
|
|
4729
5427
|
return { pkg, dir, node };
|
|
@@ -4731,17 +5429,17 @@ async function discoverGoService(scanPath, dir) {
|
|
|
4731
5429
|
|
|
4732
5430
|
// src/extract/owners.ts
|
|
4733
5431
|
init_cjs_shims();
|
|
4734
|
-
var
|
|
4735
|
-
var
|
|
5432
|
+
var import_node_fs10 = require("fs");
|
|
5433
|
+
var import_node_path11 = __toESM(require("path"), 1);
|
|
4736
5434
|
var import_minimatch = require("minimatch");
|
|
4737
5435
|
async function loadCodeowners(scanPath) {
|
|
4738
5436
|
const candidates = [
|
|
4739
|
-
|
|
4740
|
-
|
|
5437
|
+
import_node_path11.default.join(scanPath, "CODEOWNERS"),
|
|
5438
|
+
import_node_path11.default.join(scanPath, ".github", "CODEOWNERS")
|
|
4741
5439
|
];
|
|
4742
5440
|
for (const file of candidates) {
|
|
4743
5441
|
if (await exists(file)) {
|
|
4744
|
-
const raw = await
|
|
5442
|
+
const raw = await import_node_fs10.promises.readFile(file, "utf8");
|
|
4745
5443
|
return parseCodeowners(raw);
|
|
4746
5444
|
}
|
|
4747
5445
|
}
|
|
@@ -4759,7 +5457,7 @@ function parseCodeowners(raw) {
|
|
|
4759
5457
|
return { rules };
|
|
4760
5458
|
}
|
|
4761
5459
|
function matchOwner(file, repoPath) {
|
|
4762
|
-
const normalized = repoPath.split(
|
|
5460
|
+
const normalized = repoPath.split(import_node_path11.default.sep).join("/");
|
|
4763
5461
|
for (const rule of file.rules) {
|
|
4764
5462
|
if (matchesPattern(rule.pattern, normalized)) return rule.owners;
|
|
4765
5463
|
}
|
|
@@ -4775,7 +5473,7 @@ function matchesPattern(rawPattern, repoPath) {
|
|
|
4775
5473
|
return false;
|
|
4776
5474
|
}
|
|
4777
5475
|
async function readPackageJsonAuthor(serviceDir) {
|
|
4778
|
-
const pkgPath =
|
|
5476
|
+
const pkgPath = import_node_path11.default.join(serviceDir, "package.json");
|
|
4779
5477
|
if (!await exists(pkgPath)) return null;
|
|
4780
5478
|
try {
|
|
4781
5479
|
const pkg = await readJson(pkgPath);
|
|
@@ -4812,27 +5510,27 @@ function workspaceGlobs(pkg) {
|
|
|
4812
5510
|
return null;
|
|
4813
5511
|
}
|
|
4814
5512
|
async function hasPythonManifest(dir) {
|
|
4815
|
-
return await exists(
|
|
5513
|
+
return await exists(import_node_path12.default.join(dir, "pyproject.toml")) || await exists(import_node_path12.default.join(dir, "requirements.txt")) || await exists(import_node_path12.default.join(dir, "setup.py"));
|
|
4816
5514
|
}
|
|
4817
5515
|
async function hasGoManifest(dir) {
|
|
4818
|
-
return exists(
|
|
5516
|
+
return exists(import_node_path12.default.join(dir, "go.mod"));
|
|
4819
5517
|
}
|
|
4820
5518
|
async function loadGitignore(scanPath) {
|
|
4821
|
-
const gitignorePath =
|
|
5519
|
+
const gitignorePath = import_node_path12.default.join(scanPath, ".gitignore");
|
|
4822
5520
|
if (!await exists(gitignorePath)) return null;
|
|
4823
|
-
const raw = await
|
|
5521
|
+
const raw = await import_node_fs11.promises.readFile(gitignorePath, "utf8");
|
|
4824
5522
|
return (0, import_ignore.default)().add(raw);
|
|
4825
5523
|
}
|
|
4826
5524
|
async function walkDirs(start, scanPath, options, visit) {
|
|
4827
5525
|
async function recurse(current, depth) {
|
|
4828
5526
|
if (depth > options.maxDepth) return;
|
|
4829
|
-
const entries = await
|
|
5527
|
+
const entries = await import_node_fs11.promises.readdir(current, { withFileTypes: true }).catch(() => []);
|
|
4830
5528
|
for (const entry2 of entries) {
|
|
4831
5529
|
if (!entry2.isDirectory()) continue;
|
|
4832
5530
|
if (IGNORED_DIRS.has(entry2.name)) continue;
|
|
4833
|
-
const child =
|
|
5531
|
+
const child = import_node_path12.default.join(current, entry2.name);
|
|
4834
5532
|
if (options.ig) {
|
|
4835
|
-
const rel =
|
|
5533
|
+
const rel = import_node_path12.default.relative(scanPath, child).split(import_node_path12.default.sep).join("/");
|
|
4836
5534
|
if (rel && options.ig.ignores(rel + "/")) continue;
|
|
4837
5535
|
}
|
|
4838
5536
|
if (await isPythonVenvDir(child)) continue;
|
|
@@ -4848,8 +5546,8 @@ async function expandWorkspaceGlobs(scanPath, globs) {
|
|
|
4848
5546
|
for (const raw of globs) {
|
|
4849
5547
|
const pattern = raw.replace(/^\.\//, "");
|
|
4850
5548
|
if (!pattern.includes("*")) {
|
|
4851
|
-
const candidate =
|
|
4852
|
-
if (await exists(
|
|
5549
|
+
const candidate = import_node_path12.default.join(scanPath, pattern);
|
|
5550
|
+
if (await exists(import_node_path12.default.join(candidate, "package.json"))) found.add(candidate);
|
|
4853
5551
|
continue;
|
|
4854
5552
|
}
|
|
4855
5553
|
const segments = pattern.split("/");
|
|
@@ -4858,13 +5556,13 @@ async function expandWorkspaceGlobs(scanPath, globs) {
|
|
|
4858
5556
|
if (seg.includes("*")) break;
|
|
4859
5557
|
staticSegments.push(seg);
|
|
4860
5558
|
}
|
|
4861
|
-
const start =
|
|
5559
|
+
const start = import_node_path12.default.join(scanPath, ...staticSegments);
|
|
4862
5560
|
if (!await exists(start)) continue;
|
|
4863
5561
|
const hasDoubleStar = pattern.includes("**");
|
|
4864
5562
|
const walkDepth = hasDoubleStar ? scanDepth : Math.max(0, segments.length - staticSegments.length - 1);
|
|
4865
5563
|
await walkDirs(start, scanPath, { maxDepth: walkDepth, ig: null }, async (dir) => {
|
|
4866
|
-
const rel =
|
|
4867
|
-
if ((0, import_minimatch2.minimatch)(rel, pattern) && await exists(
|
|
5564
|
+
const rel = import_node_path12.default.relative(scanPath, dir).split(import_node_path12.default.sep).join("/");
|
|
5565
|
+
if ((0, import_minimatch2.minimatch)(rel, pattern) && await exists(import_node_path12.default.join(dir, "package.json"))) {
|
|
4868
5566
|
found.add(dir);
|
|
4869
5567
|
}
|
|
4870
5568
|
});
|
|
@@ -4887,31 +5585,31 @@ function detectJsFramework(pkg) {
|
|
|
4887
5585
|
async function detectJsServiceLanguage(dir, pkg) {
|
|
4888
5586
|
const deps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
4889
5587
|
if (deps["typescript"] !== void 0) return "typescript";
|
|
4890
|
-
const entries = await
|
|
5588
|
+
const entries = await import_node_fs11.promises.readdir(dir).catch(() => []);
|
|
4891
5589
|
if (entries.some((name) => /^tsconfig(\..+)?\.json$/.test(name))) return "typescript";
|
|
4892
5590
|
return "javascript";
|
|
4893
5591
|
}
|
|
4894
5592
|
async function discoverNodeService(scanPath, dir) {
|
|
4895
|
-
const pkgPath =
|
|
5593
|
+
const pkgPath = import_node_path12.default.join(dir, "package.json");
|
|
4896
5594
|
if (!await exists(pkgPath)) return null;
|
|
4897
5595
|
let pkg;
|
|
4898
5596
|
try {
|
|
4899
5597
|
pkg = await readJson(pkgPath);
|
|
4900
5598
|
} catch (err) {
|
|
4901
|
-
recordExtractionError("services",
|
|
5599
|
+
recordExtractionError("services", import_node_path12.default.relative(scanPath, pkgPath), err);
|
|
4902
5600
|
return null;
|
|
4903
5601
|
}
|
|
4904
5602
|
if (!pkg.name) return null;
|
|
4905
5603
|
const framework = detectJsFramework(pkg);
|
|
4906
5604
|
const language = await detectJsServiceLanguage(dir, pkg);
|
|
4907
5605
|
const node = {
|
|
4908
|
-
id: (0,
|
|
4909
|
-
type:
|
|
5606
|
+
id: (0, import_types10.serviceId)(pkg.name),
|
|
5607
|
+
type: import_types10.NodeType.ServiceNode,
|
|
4910
5608
|
name: pkg.name,
|
|
4911
5609
|
language,
|
|
4912
5610
|
version: pkg.version,
|
|
4913
5611
|
dependencies: pkg.dependencies ?? {},
|
|
4914
|
-
repoPath:
|
|
5612
|
+
repoPath: import_node_path12.default.relative(scanPath, dir),
|
|
4915
5613
|
...pkg.engines?.node ? { nodeEngine: pkg.engines.node } : {},
|
|
4916
5614
|
...framework ? { framework } : {}
|
|
4917
5615
|
};
|
|
@@ -4922,18 +5620,18 @@ async function discoverPyService(scanPath, dir) {
|
|
|
4922
5620
|
if (!py) return null;
|
|
4923
5621
|
const pkg = pythonToPackage(py);
|
|
4924
5622
|
const node = {
|
|
4925
|
-
id: (0,
|
|
4926
|
-
type:
|
|
5623
|
+
id: (0, import_types10.serviceId)(py.name),
|
|
5624
|
+
type: import_types10.NodeType.ServiceNode,
|
|
4927
5625
|
name: py.name,
|
|
4928
5626
|
language: "python",
|
|
4929
5627
|
version: py.version,
|
|
4930
5628
|
dependencies: py.dependencies,
|
|
4931
|
-
repoPath:
|
|
5629
|
+
repoPath: import_node_path12.default.relative(scanPath, dir)
|
|
4932
5630
|
};
|
|
4933
5631
|
return { pkg, dir, node };
|
|
4934
5632
|
}
|
|
4935
5633
|
async function discoverServices(scanPath) {
|
|
4936
|
-
const rootPkgPath =
|
|
5634
|
+
const rootPkgPath = import_node_path12.default.join(scanPath, "package.json");
|
|
4937
5635
|
let rootPkg = null;
|
|
4938
5636
|
if (await exists(rootPkgPath)) {
|
|
4939
5637
|
try {
|
|
@@ -4941,7 +5639,7 @@ async function discoverServices(scanPath) {
|
|
|
4941
5639
|
} catch (err) {
|
|
4942
5640
|
recordExtractionError(
|
|
4943
5641
|
"services workspaces",
|
|
4944
|
-
|
|
5642
|
+
import_node_path12.default.relative(scanPath, rootPkgPath),
|
|
4945
5643
|
err
|
|
4946
5644
|
);
|
|
4947
5645
|
}
|
|
@@ -4962,7 +5660,7 @@ async function discoverServices(scanPath) {
|
|
|
4962
5660
|
scanPath,
|
|
4963
5661
|
{ maxDepth: parseScanDepth(), ig },
|
|
4964
5662
|
async (dir) => {
|
|
4965
|
-
if (await exists(
|
|
5663
|
+
if (await exists(import_node_path12.default.join(dir, "package.json"))) {
|
|
4966
5664
|
candidateDirs.push(dir);
|
|
4967
5665
|
} else if (await hasPythonManifest(dir) || await hasGoManifest(dir)) {
|
|
4968
5666
|
candidateDirs.push(dir);
|
|
@@ -4978,8 +5676,8 @@ async function discoverServices(scanPath) {
|
|
|
4978
5676
|
if (!service) continue;
|
|
4979
5677
|
const existingDir = seen.get(service.node.name);
|
|
4980
5678
|
if (existingDir !== void 0) {
|
|
4981
|
-
const a =
|
|
4982
|
-
const b =
|
|
5679
|
+
const a = import_node_path12.default.relative(scanPath, existingDir) || ".";
|
|
5680
|
+
const b = import_node_path12.default.relative(scanPath, dir) || ".";
|
|
4983
5681
|
console.warn(
|
|
4984
5682
|
`[neat] duplicate package name "${service.node.name}" \u2014 keeping ${a}, ignoring ${b}`
|
|
4985
5683
|
);
|
|
@@ -5016,10 +5714,10 @@ function addServiceNodes(graph, services) {
|
|
|
5016
5714
|
|
|
5017
5715
|
// src/extract/aliases.ts
|
|
5018
5716
|
init_cjs_shims();
|
|
5019
|
-
var
|
|
5020
|
-
var
|
|
5717
|
+
var import_node_path13 = __toESM(require("path"), 1);
|
|
5718
|
+
var import_node_fs12 = require("fs");
|
|
5021
5719
|
var import_yaml2 = require("yaml");
|
|
5022
|
-
var
|
|
5720
|
+
var import_types11 = require("@neat.is/types");
|
|
5023
5721
|
var K8S_KINDS_WITH_HOSTNAMES = /* @__PURE__ */ new Set([
|
|
5024
5722
|
"Service",
|
|
5025
5723
|
"Deployment",
|
|
@@ -5029,7 +5727,7 @@ var K8S_KINDS_WITH_HOSTNAMES = /* @__PURE__ */ new Set([
|
|
|
5029
5727
|
function addAliases(graph, serviceId7, candidates) {
|
|
5030
5728
|
if (!graph.hasNode(serviceId7)) return;
|
|
5031
5729
|
const node = graph.getNodeAttributes(serviceId7);
|
|
5032
|
-
if (node.type !==
|
|
5730
|
+
if (node.type !== import_types11.NodeType.ServiceNode) return;
|
|
5033
5731
|
const set = new Set(node.aliases ?? []);
|
|
5034
5732
|
for (const c of candidates) {
|
|
5035
5733
|
if (!c) continue;
|
|
@@ -5044,14 +5742,14 @@ function indexServicesByName(services) {
|
|
|
5044
5742
|
const map = /* @__PURE__ */ new Map();
|
|
5045
5743
|
for (const s of services) {
|
|
5046
5744
|
map.set(s.node.name, s.node.id);
|
|
5047
|
-
map.set(
|
|
5745
|
+
map.set(import_node_path13.default.basename(s.dir), s.node.id);
|
|
5048
5746
|
}
|
|
5049
5747
|
return map;
|
|
5050
5748
|
}
|
|
5051
5749
|
async function collectComposeAliases(graph, scanPath, serviceIndex) {
|
|
5052
5750
|
let composePath = null;
|
|
5053
5751
|
for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
|
|
5054
|
-
const abs =
|
|
5752
|
+
const abs = import_node_path13.default.join(scanPath, name);
|
|
5055
5753
|
if (await exists(abs)) {
|
|
5056
5754
|
composePath = abs;
|
|
5057
5755
|
break;
|
|
@@ -5064,7 +5762,7 @@ async function collectComposeAliases(graph, scanPath, serviceIndex) {
|
|
|
5064
5762
|
} catch (err) {
|
|
5065
5763
|
recordExtractionError(
|
|
5066
5764
|
"aliases compose",
|
|
5067
|
-
|
|
5765
|
+
import_node_path13.default.relative(scanPath, composePath),
|
|
5068
5766
|
err
|
|
5069
5767
|
);
|
|
5070
5768
|
return;
|
|
@@ -5107,11 +5805,11 @@ function parseDockerfileLabels(content) {
|
|
|
5107
5805
|
}
|
|
5108
5806
|
async function collectDockerfileAliases(graph, services) {
|
|
5109
5807
|
for (const service of services) {
|
|
5110
|
-
const dockerfilePath =
|
|
5808
|
+
const dockerfilePath = import_node_path13.default.join(service.dir, "Dockerfile");
|
|
5111
5809
|
if (!await exists(dockerfilePath)) continue;
|
|
5112
5810
|
let content;
|
|
5113
5811
|
try {
|
|
5114
|
-
content = await
|
|
5812
|
+
content = await import_node_fs12.promises.readFile(dockerfilePath, "utf8");
|
|
5115
5813
|
} catch (err) {
|
|
5116
5814
|
recordExtractionError("aliases dockerfile", dockerfilePath, err);
|
|
5117
5815
|
continue;
|
|
@@ -5123,15 +5821,15 @@ async function collectDockerfileAliases(graph, services) {
|
|
|
5123
5821
|
async function walkYamlFiles(start, depth = 0, max = 5) {
|
|
5124
5822
|
if (depth > max) return [];
|
|
5125
5823
|
const out = [];
|
|
5126
|
-
const entries = await
|
|
5824
|
+
const entries = await import_node_fs12.promises.readdir(start, { withFileTypes: true }).catch(() => []);
|
|
5127
5825
|
for (const entry2 of entries) {
|
|
5128
5826
|
if (entry2.isDirectory()) {
|
|
5129
5827
|
if (IGNORED_DIRS.has(entry2.name)) continue;
|
|
5130
|
-
const child =
|
|
5828
|
+
const child = import_node_path13.default.join(start, entry2.name);
|
|
5131
5829
|
if (await isPythonVenvDir(child)) continue;
|
|
5132
5830
|
out.push(...await walkYamlFiles(child, depth + 1, max));
|
|
5133
|
-
} else if (entry2.isFile() && CONFIG_FILE_EXTENSIONS.has(
|
|
5134
|
-
out.push(
|
|
5831
|
+
} else if (entry2.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path13.default.extname(entry2.name))) {
|
|
5832
|
+
out.push(import_node_path13.default.join(start, entry2.name));
|
|
5135
5833
|
}
|
|
5136
5834
|
}
|
|
5137
5835
|
return out;
|
|
@@ -5158,614 +5856,230 @@ function k8sServiceTarget(doc, byName) {
|
|
|
5158
5856
|
async function collectK8sAliases(graph, scanPath, serviceIndex) {
|
|
5159
5857
|
const files = await walkYamlFiles(scanPath);
|
|
5160
5858
|
for (const file of files) {
|
|
5161
|
-
const content = await
|
|
5859
|
+
const content = await import_node_fs12.promises.readFile(file, "utf8");
|
|
5162
5860
|
let docs;
|
|
5163
5861
|
try {
|
|
5164
|
-
docs = (0, import_yaml2.parseAllDocuments)(content).map((d) => d.toJSON());
|
|
5165
|
-
} catch {
|
|
5166
|
-
continue;
|
|
5167
|
-
}
|
|
5168
|
-
for (const doc of docs) {
|
|
5169
|
-
if (!doc?.kind || !doc.metadata?.name) continue;
|
|
5170
|
-
if (!K8S_KINDS_WITH_HOSTNAMES.has(doc.kind)) continue;
|
|
5171
|
-
const target = k8sServiceTarget(doc, serviceIndex);
|
|
5172
|
-
if (!target) continue;
|
|
5173
|
-
addAliases(graph, target, k8sHostnames(doc.metadata.name, doc.metadata.namespace));
|
|
5174
|
-
}
|
|
5175
|
-
}
|
|
5176
|
-
}
|
|
5177
|
-
async function addServiceAliases(graph, scanPath, services) {
|
|
5178
|
-
const byName = indexServicesByName(services);
|
|
5179
|
-
await collectComposeAliases(graph, scanPath, byName);
|
|
5180
|
-
await collectDockerfileAliases(graph, services);
|
|
5181
|
-
await collectK8sAliases(graph, scanPath, byName);
|
|
5182
|
-
}
|
|
5183
|
-
|
|
5184
|
-
// src/extract/files.ts
|
|
5185
|
-
init_cjs_shims();
|
|
5186
|
-
var import_node_path13 = __toESM(require("path"), 1);
|
|
5187
|
-
async function addFiles(graph, services) {
|
|
5188
|
-
let nodesAdded = 0;
|
|
5189
|
-
let edgesAdded = 0;
|
|
5190
|
-
for (const service of services) {
|
|
5191
|
-
const filePaths = await walkSourceFiles(service.dir);
|
|
5192
|
-
for (const filePath of filePaths) {
|
|
5193
|
-
const relPath = toPosix(import_node_path13.default.relative(service.dir, filePath));
|
|
5194
|
-
const { nodesAdded: n, edgesAdded: e } = ensureFileNode(
|
|
5195
|
-
graph,
|
|
5196
|
-
service.pkg.name,
|
|
5197
|
-
service.node.id,
|
|
5198
|
-
relPath
|
|
5199
|
-
);
|
|
5200
|
-
nodesAdded += n;
|
|
5201
|
-
edgesAdded += e;
|
|
5202
|
-
}
|
|
5203
|
-
}
|
|
5204
|
-
return { nodesAdded, edgesAdded };
|
|
5205
|
-
}
|
|
5206
|
-
|
|
5207
|
-
// src/extract/symbols.ts
|
|
5208
|
-
init_cjs_shims();
|
|
5209
|
-
var import_node_path14 = __toESM(require("path"), 1);
|
|
5210
|
-
var import_tree_sitter2 = __toESM(require("tree-sitter"), 1);
|
|
5211
|
-
var import_tree_sitter_javascript2 = __toESM(require("tree-sitter-javascript"), 1);
|
|
5212
|
-
var import_tree_sitter_typescript = __toESM(require("tree-sitter-typescript"), 1);
|
|
5213
|
-
var import_types11 = require("@neat.is/types");
|
|
5214
|
-
var PARSE_CHUNK2 = 16384;
|
|
5215
|
-
var GRAMMAR_BY_EXT = {
|
|
5216
|
-
".ts": import_tree_sitter_typescript.default.typescript,
|
|
5217
|
-
".tsx": import_tree_sitter_typescript.default.tsx,
|
|
5218
|
-
".js": import_tree_sitter_javascript2.default,
|
|
5219
|
-
".jsx": import_tree_sitter_javascript2.default,
|
|
5220
|
-
".mjs": import_tree_sitter_javascript2.default,
|
|
5221
|
-
".cjs": import_tree_sitter_javascript2.default
|
|
5222
|
-
};
|
|
5223
|
-
function parseSource2(parser, source) {
|
|
5224
|
-
return parser.parse(
|
|
5225
|
-
(index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK2)
|
|
5226
|
-
);
|
|
5227
|
-
}
|
|
5228
|
-
function methodName(node) {
|
|
5229
|
-
const name = node.childForFieldName("name");
|
|
5230
|
-
return name ? name.text : null;
|
|
5231
|
-
}
|
|
5232
|
-
function collectSymbolDefs(root) {
|
|
5233
|
-
const out = [];
|
|
5234
|
-
const push = (kind, qualname, node) => {
|
|
5235
|
-
out.push({
|
|
5236
|
-
kind,
|
|
5237
|
-
qualname,
|
|
5238
|
-
startLine: node.startPosition.row + 1,
|
|
5239
|
-
endLine: node.endPosition.row + 1
|
|
5240
|
-
});
|
|
5241
|
-
};
|
|
5242
|
-
const visit = (node, classCtx) => {
|
|
5243
|
-
switch (node.type) {
|
|
5244
|
-
case "function_declaration":
|
|
5245
|
-
case "generator_function_declaration": {
|
|
5246
|
-
const name = node.childForFieldName("name")?.text;
|
|
5247
|
-
if (name) push("function", name, node);
|
|
5248
|
-
break;
|
|
5249
|
-
}
|
|
5250
|
-
case "class_declaration":
|
|
5251
|
-
case "abstract_class_declaration":
|
|
5252
|
-
case "class": {
|
|
5253
|
-
const name = node.childForFieldName("name")?.text;
|
|
5254
|
-
if (name) push("class", name, node);
|
|
5255
|
-
const body = node.childForFieldName("body");
|
|
5256
|
-
if (body) {
|
|
5257
|
-
for (let i = 0; i < body.namedChildCount; i++) {
|
|
5258
|
-
const child = body.namedChild(i);
|
|
5259
|
-
if (child) visit(child, name ?? classCtx);
|
|
5260
|
-
}
|
|
5261
|
-
}
|
|
5262
|
-
return;
|
|
5263
|
-
}
|
|
5264
|
-
case "method_definition": {
|
|
5265
|
-
const name = methodName(node);
|
|
5266
|
-
if (name) {
|
|
5267
|
-
const kind = name === "constructor" ? "constructor" : "method";
|
|
5268
|
-
push(kind, classCtx ? `${classCtx}.${name}` : name, node);
|
|
5269
|
-
}
|
|
5270
|
-
break;
|
|
5271
|
-
}
|
|
5272
|
-
case "variable_declarator": {
|
|
5273
|
-
const value = node.childForFieldName("value");
|
|
5274
|
-
if (value && (value.type === "arrow_function" || value.type === "function" || value.type === "function_expression" || value.type === "generator_function")) {
|
|
5275
|
-
const nameNode = node.childForFieldName("name");
|
|
5276
|
-
if (nameNode && nameNode.type === "identifier") {
|
|
5277
|
-
push("function", nameNode.text, node);
|
|
5278
|
-
}
|
|
5279
|
-
}
|
|
5280
|
-
break;
|
|
5281
|
-
}
|
|
5282
|
-
}
|
|
5283
|
-
for (let i = 0; i < node.namedChildCount; i++) {
|
|
5284
|
-
const child = node.namedChild(i);
|
|
5285
|
-
if (child) visit(child, classCtx);
|
|
5286
|
-
}
|
|
5287
|
-
};
|
|
5288
|
-
visit(root, void 0);
|
|
5289
|
-
return out;
|
|
5290
|
-
}
|
|
5291
|
-
function disambiguate(defs) {
|
|
5292
|
-
const counts = /* @__PURE__ */ new Map();
|
|
5293
|
-
for (const def of defs) counts.set(def.qualname, (counts.get(def.qualname) ?? 0) + 1);
|
|
5294
|
-
const seen = /* @__PURE__ */ new Map();
|
|
5295
|
-
return defs.map((def) => {
|
|
5296
|
-
if ((counts.get(def.qualname) ?? 0) <= 1) return { def };
|
|
5297
|
-
const ordinal = seen.get(def.qualname) ?? 0;
|
|
5298
|
-
seen.set(def.qualname, ordinal + 1);
|
|
5299
|
-
return { def, disambiguator: ordinal };
|
|
5300
|
-
});
|
|
5301
|
-
}
|
|
5302
|
-
async function addSymbols(graph, services) {
|
|
5303
|
-
const parsers = /* @__PURE__ */ new Map();
|
|
5304
|
-
const parserForExt2 = (ext) => {
|
|
5305
|
-
const grammar = GRAMMAR_BY_EXT[ext];
|
|
5306
|
-
if (!grammar) return null;
|
|
5307
|
-
let parser = parsers.get(ext);
|
|
5308
|
-
if (!parser) {
|
|
5309
|
-
parser = new import_tree_sitter2.default();
|
|
5310
|
-
parser.setLanguage(grammar);
|
|
5311
|
-
parsers.set(ext, parser);
|
|
5862
|
+
docs = (0, import_yaml2.parseAllDocuments)(content).map((d) => d.toJSON());
|
|
5863
|
+
} catch {
|
|
5864
|
+
continue;
|
|
5312
5865
|
}
|
|
5313
|
-
|
|
5314
|
-
|
|
5866
|
+
for (const doc of docs) {
|
|
5867
|
+
if (!doc?.kind || !doc.metadata?.name) continue;
|
|
5868
|
+
if (!K8S_KINDS_WITH_HOSTNAMES.has(doc.kind)) continue;
|
|
5869
|
+
const target = k8sServiceTarget(doc, serviceIndex);
|
|
5870
|
+
if (!target) continue;
|
|
5871
|
+
addAliases(graph, target, k8sHostnames(doc.metadata.name, doc.metadata.namespace));
|
|
5872
|
+
}
|
|
5873
|
+
}
|
|
5874
|
+
}
|
|
5875
|
+
async function addServiceAliases(graph, scanPath, services) {
|
|
5876
|
+
const byName = indexServicesByName(services);
|
|
5877
|
+
await collectComposeAliases(graph, scanPath, byName);
|
|
5878
|
+
await collectDockerfileAliases(graph, services);
|
|
5879
|
+
await collectK8sAliases(graph, scanPath, byName);
|
|
5880
|
+
}
|
|
5881
|
+
|
|
5882
|
+
// src/extract/files.ts
|
|
5883
|
+
init_cjs_shims();
|
|
5884
|
+
var import_node_path14 = __toESM(require("path"), 1);
|
|
5885
|
+
async function addFiles(graph, services) {
|
|
5315
5886
|
let nodesAdded = 0;
|
|
5316
5887
|
let edgesAdded = 0;
|
|
5317
5888
|
for (const service of services) {
|
|
5318
|
-
const
|
|
5319
|
-
for (const
|
|
5320
|
-
const
|
|
5321
|
-
|
|
5322
|
-
const relPath = toPosix(import_node_path14.default.relative(service.dir, file.path));
|
|
5323
|
-
let defs;
|
|
5324
|
-
try {
|
|
5325
|
-
const tree = parseSource2(parser, file.content);
|
|
5326
|
-
defs = collectSymbolDefs(tree.rootNode);
|
|
5327
|
-
} catch (err) {
|
|
5328
|
-
recordExtractionError("symbol extraction", file.path, err);
|
|
5329
|
-
continue;
|
|
5330
|
-
}
|
|
5331
|
-
if (defs.length === 0) continue;
|
|
5332
|
-
const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
|
|
5889
|
+
const filePaths = await walkSourceFiles(service.dir);
|
|
5890
|
+
for (const filePath of filePaths) {
|
|
5891
|
+
const relPath = toPosix(import_node_path14.default.relative(service.dir, filePath));
|
|
5892
|
+
const { nodesAdded: n, edgesAdded: e } = ensureFileNode(
|
|
5333
5893
|
graph,
|
|
5334
5894
|
service.pkg.name,
|
|
5335
5895
|
service.node.id,
|
|
5336
5896
|
relPath
|
|
5337
5897
|
);
|
|
5338
|
-
nodesAdded +=
|
|
5339
|
-
edgesAdded +=
|
|
5340
|
-
for (const { def, disambiguator } of disambiguate(defs)) {
|
|
5341
|
-
const sid = (0, import_types11.symbolId)(service.pkg.name, relPath, def.qualname, disambiguator);
|
|
5342
|
-
if (!graph.hasNode(sid)) {
|
|
5343
|
-
const node = {
|
|
5344
|
-
id: sid,
|
|
5345
|
-
type: import_types11.NodeType.SymbolNode,
|
|
5346
|
-
kind: def.kind,
|
|
5347
|
-
qualname: def.qualname,
|
|
5348
|
-
span: { startLine: def.startLine, endLine: def.endLine },
|
|
5349
|
-
service: service.pkg.name,
|
|
5350
|
-
relPath,
|
|
5351
|
-
discoveredVia: "static"
|
|
5352
|
-
};
|
|
5353
|
-
graph.addNode(sid, node);
|
|
5354
|
-
nodesAdded++;
|
|
5355
|
-
}
|
|
5356
|
-
const containsId = (0, import_types11.extractedEdgeId)(fileNodeId, sid, import_types11.EdgeType.CONTAINS);
|
|
5357
|
-
if (!graph.hasEdge(containsId)) {
|
|
5358
|
-
const edge = {
|
|
5359
|
-
id: containsId,
|
|
5360
|
-
source: fileNodeId,
|
|
5361
|
-
target: sid,
|
|
5362
|
-
type: import_types11.EdgeType.CONTAINS,
|
|
5363
|
-
provenance: import_types11.Provenance.EXTRACTED,
|
|
5364
|
-
confidence: (0, import_types11.confidenceForExtracted)("structural"),
|
|
5365
|
-
evidence: {
|
|
5366
|
-
file: relPath,
|
|
5367
|
-
line: def.startLine,
|
|
5368
|
-
snippet: snippet(file.content, def.startLine)
|
|
5369
|
-
}
|
|
5370
|
-
};
|
|
5371
|
-
graph.addEdgeWithKey(containsId, fileNodeId, sid, edge);
|
|
5372
|
-
edgesAdded++;
|
|
5373
|
-
}
|
|
5374
|
-
}
|
|
5898
|
+
nodesAdded += n;
|
|
5899
|
+
edgesAdded += e;
|
|
5375
5900
|
}
|
|
5376
5901
|
}
|
|
5377
5902
|
return { nodesAdded, edgesAdded };
|
|
5378
5903
|
}
|
|
5379
5904
|
|
|
5380
|
-
// src/extract/
|
|
5381
|
-
init_cjs_shims();
|
|
5382
|
-
var import_node_path16 = __toESM(require("path"), 1);
|
|
5383
|
-
var import_tree_sitter4 = __toESM(require("tree-sitter"), 1);
|
|
5384
|
-
var import_types13 = require("@neat.is/types");
|
|
5385
|
-
|
|
5386
|
-
// src/extract/imports.ts
|
|
5905
|
+
// src/extract/symbols.ts
|
|
5387
5906
|
init_cjs_shims();
|
|
5388
5907
|
var import_node_path15 = __toESM(require("path"), 1);
|
|
5389
|
-
var import_node_fs12 = require("fs");
|
|
5390
5908
|
var import_tree_sitter3 = __toESM(require("tree-sitter"), 1);
|
|
5391
5909
|
var import_tree_sitter_javascript3 = __toESM(require("tree-sitter-javascript"), 1);
|
|
5392
|
-
var
|
|
5393
|
-
var import_tree_sitter_go2 = __toESM(require("tree-sitter-go"), 1);
|
|
5910
|
+
var import_tree_sitter_typescript = __toESM(require("tree-sitter-typescript"), 1);
|
|
5394
5911
|
var import_types12 = require("@neat.is/types");
|
|
5395
5912
|
var PARSE_CHUNK3 = 16384;
|
|
5913
|
+
var GRAMMAR_BY_EXT = {
|
|
5914
|
+
".ts": import_tree_sitter_typescript.default.typescript,
|
|
5915
|
+
".tsx": import_tree_sitter_typescript.default.tsx,
|
|
5916
|
+
".js": import_tree_sitter_javascript3.default,
|
|
5917
|
+
".jsx": import_tree_sitter_javascript3.default,
|
|
5918
|
+
".mjs": import_tree_sitter_javascript3.default,
|
|
5919
|
+
".cjs": import_tree_sitter_javascript3.default
|
|
5920
|
+
};
|
|
5396
5921
|
function parseSource3(parser, source) {
|
|
5397
5922
|
return parser.parse(
|
|
5398
5923
|
(index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK3)
|
|
5399
|
-
);
|
|
5400
|
-
}
|
|
5401
|
-
function makeJsParser2() {
|
|
5402
|
-
const p = new import_tree_sitter3.default();
|
|
5403
|
-
p.setLanguage(import_tree_sitter_javascript3.default);
|
|
5404
|
-
return p;
|
|
5405
|
-
}
|
|
5406
|
-
function makePyParser2() {
|
|
5407
|
-
const p = new import_tree_sitter3.default();
|
|
5408
|
-
p.setLanguage(import_tree_sitter_python2.default);
|
|
5409
|
-
return p;
|
|
5410
|
-
}
|
|
5411
|
-
function makeGoParser2() {
|
|
5412
|
-
const p = new import_tree_sitter3.default();
|
|
5413
|
-
p.setLanguage(import_tree_sitter_go2.default);
|
|
5414
|
-
return p;
|
|
5415
|
-
}
|
|
5416
|
-
function stringLiteralText(node) {
|
|
5417
|
-
for (let i = 0; i < node.childCount; i++) {
|
|
5418
|
-
const child = node.child(i);
|
|
5419
|
-
if (child?.type === "string_fragment") return child.text;
|
|
5420
|
-
}
|
|
5421
|
-
const raw = node.text;
|
|
5422
|
-
if (raw.length >= 2) return raw.slice(1, -1);
|
|
5423
|
-
return raw.length === 0 ? null : "";
|
|
5424
|
-
}
|
|
5425
|
-
function clipSnippet(text) {
|
|
5426
|
-
const oneLine = text.split("\n")[0] ?? text;
|
|
5427
|
-
return oneLine.length > 120 ? oneLine.slice(0, 120) : oneLine;
|
|
5428
|
-
}
|
|
5429
|
-
function collectGoImports(node, out) {
|
|
5430
|
-
if (node.type === "import_spec") {
|
|
5431
|
-
const pathNode = node.childForFieldName("path");
|
|
5432
|
-
if (pathNode) {
|
|
5433
|
-
const specifier = pathNode.text.replace(/^`|`$/g, "").replace(/^"|"$/g, "");
|
|
5434
|
-
if (specifier) out.push({ specifier, line: node.startPosition.row + 1, snippet: clipSnippet(node.text) });
|
|
5435
|
-
}
|
|
5436
|
-
return;
|
|
5437
|
-
}
|
|
5438
|
-
for (let i = 0; i < node.namedChildCount; i++) {
|
|
5439
|
-
const child = node.namedChild(i);
|
|
5440
|
-
if (child) collectGoImports(child, out);
|
|
5441
|
-
}
|
|
5442
|
-
}
|
|
5443
|
-
function collectJsImports(node, out) {
|
|
5444
|
-
if (node.type === "import_statement") {
|
|
5445
|
-
const source = node.childForFieldName("source");
|
|
5446
|
-
if (source) {
|
|
5447
|
-
const specifier = stringLiteralText(source);
|
|
5448
|
-
if (specifier) {
|
|
5449
|
-
out.push({ specifier, line: node.startPosition.row + 1, snippet: clipSnippet(node.text) });
|
|
5450
|
-
}
|
|
5451
|
-
}
|
|
5452
|
-
return;
|
|
5453
|
-
}
|
|
5454
|
-
if (node.type === "call_expression") {
|
|
5455
|
-
const fn = node.childForFieldName("function");
|
|
5456
|
-
if (fn?.type === "identifier" && fn.text === "require") {
|
|
5457
|
-
const args = node.childForFieldName("arguments");
|
|
5458
|
-
const firstArg = args?.namedChild(0);
|
|
5459
|
-
if (firstArg?.type === "string") {
|
|
5460
|
-
const specifier = stringLiteralText(firstArg);
|
|
5461
|
-
if (specifier) {
|
|
5462
|
-
out.push({ specifier, line: node.startPosition.row + 1, snippet: clipSnippet(node.text) });
|
|
5463
|
-
}
|
|
5464
|
-
}
|
|
5465
|
-
}
|
|
5466
|
-
}
|
|
5467
|
-
for (let i = 0; i < node.namedChildCount; i++) {
|
|
5468
|
-
const child = node.namedChild(i);
|
|
5469
|
-
if (child) collectJsImports(child, out);
|
|
5470
|
-
}
|
|
5471
|
-
}
|
|
5472
|
-
function collectImportedNames(node, out) {
|
|
5473
|
-
if (node.type === "aliased_import") {
|
|
5474
|
-
const nameNode = node.childForFieldName("name");
|
|
5475
|
-
if (nameNode) out.push(nameNode.text);
|
|
5476
|
-
return;
|
|
5477
|
-
}
|
|
5478
|
-
if (node.type === "dotted_name") {
|
|
5479
|
-
out.push(node.text);
|
|
5480
|
-
return;
|
|
5481
|
-
}
|
|
5482
|
-
for (let i = 0; i < node.namedChildCount; i++) {
|
|
5483
|
-
const child = node.namedChild(i);
|
|
5484
|
-
if (child) collectImportedNames(child, out);
|
|
5485
|
-
}
|
|
5486
|
-
}
|
|
5487
|
-
function collectPyImports(node, out) {
|
|
5488
|
-
if (node.type === "import_from_statement") {
|
|
5489
|
-
let level = 0;
|
|
5490
|
-
let modulePath = "";
|
|
5491
|
-
const names = [];
|
|
5492
|
-
let pastFrom = false;
|
|
5493
|
-
let pastImport = false;
|
|
5494
|
-
for (let i = 0; i < node.childCount; i++) {
|
|
5495
|
-
const child = node.child(i);
|
|
5496
|
-
if (!child) continue;
|
|
5497
|
-
if (!pastFrom) {
|
|
5498
|
-
if (child.type === "from") pastFrom = true;
|
|
5499
|
-
continue;
|
|
5500
|
-
}
|
|
5501
|
-
if (!pastImport) {
|
|
5502
|
-
if (child.type === "import") {
|
|
5503
|
-
pastImport = true;
|
|
5504
|
-
continue;
|
|
5505
|
-
}
|
|
5506
|
-
if (child.type === "relative_import") {
|
|
5507
|
-
for (let j = 0; j < child.childCount; j++) {
|
|
5508
|
-
const rc = child.child(j);
|
|
5509
|
-
if (!rc) continue;
|
|
5510
|
-
if (rc.type === "import_prefix") {
|
|
5511
|
-
for (let k = 0; k < rc.childCount; k++) {
|
|
5512
|
-
if (rc.child(k)?.type === ".") level++;
|
|
5513
|
-
}
|
|
5514
|
-
} else if (rc.type === "dotted_name") modulePath = rc.text;
|
|
5515
|
-
}
|
|
5516
|
-
} else if (child.type === "dotted_name") {
|
|
5517
|
-
modulePath = child.text;
|
|
5518
|
-
}
|
|
5519
|
-
continue;
|
|
5520
|
-
}
|
|
5521
|
-
collectImportedNames(child, names);
|
|
5522
|
-
}
|
|
5523
|
-
if (level > 0 || modulePath) {
|
|
5524
|
-
out.push({ modulePath, level, names, line: node.startPosition.row + 1, snippet: clipSnippet(node.text) });
|
|
5525
|
-
}
|
|
5526
|
-
}
|
|
5527
|
-
for (let i = 0; i < node.namedChildCount; i++) {
|
|
5528
|
-
const child = node.namedChild(i);
|
|
5529
|
-
if (child) collectPyImports(child, out);
|
|
5530
|
-
}
|
|
5531
|
-
}
|
|
5532
|
-
async function fileExists(p) {
|
|
5533
|
-
try {
|
|
5534
|
-
await import_node_fs12.promises.access(p);
|
|
5535
|
-
return true;
|
|
5536
|
-
} catch {
|
|
5537
|
-
return false;
|
|
5538
|
-
}
|
|
5539
|
-
}
|
|
5540
|
-
function isWithinServiceDir(candidate, serviceDir) {
|
|
5541
|
-
const rel = import_node_path15.default.relative(serviceDir, candidate);
|
|
5542
|
-
return rel !== "" && !rel.startsWith("..") && !import_node_path15.default.isAbsolute(rel);
|
|
5543
|
-
}
|
|
5544
|
-
var JS_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"];
|
|
5545
|
-
var JS_INDEX_FILES = JS_EXTENSIONS.map((ext) => `index${ext}`);
|
|
5546
|
-
async function firstExistingCandidate(base, serviceDir) {
|
|
5547
|
-
for (const ext of JS_EXTENSIONS) {
|
|
5548
|
-
const candidate = base + ext;
|
|
5549
|
-
if (isWithinServiceDir(candidate, serviceDir) && await fileExists(candidate)) {
|
|
5550
|
-
return toPosix(import_node_path15.default.relative(serviceDir, candidate));
|
|
5551
|
-
}
|
|
5552
|
-
}
|
|
5553
|
-
for (const indexFile of JS_INDEX_FILES) {
|
|
5554
|
-
const candidate = import_node_path15.default.join(base, indexFile);
|
|
5555
|
-
if (isWithinServiceDir(candidate, serviceDir) && await fileExists(candidate)) {
|
|
5556
|
-
return toPosix(import_node_path15.default.relative(serviceDir, candidate));
|
|
5557
|
-
}
|
|
5558
|
-
}
|
|
5559
|
-
return null;
|
|
5560
|
-
}
|
|
5561
|
-
async function loadTsPathConfig(serviceDir) {
|
|
5562
|
-
const tsconfigPath = import_node_path15.default.join(serviceDir, "tsconfig.json");
|
|
5563
|
-
let raw;
|
|
5564
|
-
try {
|
|
5565
|
-
raw = await import_node_fs12.promises.readFile(tsconfigPath, "utf8");
|
|
5566
|
-
} catch {
|
|
5567
|
-
return null;
|
|
5568
|
-
}
|
|
5569
|
-
try {
|
|
5570
|
-
const parsed = JSON.parse(raw);
|
|
5571
|
-
const paths = parsed.compilerOptions?.paths;
|
|
5572
|
-
if (!paths || Object.keys(paths).length === 0) return null;
|
|
5573
|
-
const baseUrl = parsed.compilerOptions?.baseUrl;
|
|
5574
|
-
return { paths, baseDir: baseUrl ? import_node_path15.default.resolve(serviceDir, baseUrl) : serviceDir };
|
|
5575
|
-
} catch (err) {
|
|
5576
|
-
recordExtractionError("import alias resolution", tsconfigPath, err);
|
|
5577
|
-
return null;
|
|
5578
|
-
}
|
|
5924
|
+
);
|
|
5579
5925
|
}
|
|
5580
|
-
|
|
5581
|
-
|
|
5582
|
-
|
|
5583
|
-
if (pattern === specifier) {
|
|
5584
|
-
suffix = "";
|
|
5585
|
-
} else if (pattern.endsWith("/*")) {
|
|
5586
|
-
const prefix = pattern.slice(0, -1);
|
|
5587
|
-
if (specifier.startsWith(prefix)) suffix = specifier.slice(prefix.length);
|
|
5588
|
-
}
|
|
5589
|
-
if (suffix === null) continue;
|
|
5590
|
-
for (const target of targets) {
|
|
5591
|
-
const targetBase = target.endsWith("/*") ? target.slice(0, -2) : target.replace(/\*$/, "");
|
|
5592
|
-
const resolvedBase = import_node_path15.default.resolve(config.baseDir, targetBase, suffix);
|
|
5593
|
-
const hit = await firstExistingCandidate(resolvedBase, serviceDir);
|
|
5594
|
-
if (hit) return hit;
|
|
5595
|
-
if (isWithinServiceDir(resolvedBase, serviceDir) && await fileExists(resolvedBase)) {
|
|
5596
|
-
return toPosix(import_node_path15.default.relative(serviceDir, resolvedBase));
|
|
5597
|
-
}
|
|
5598
|
-
}
|
|
5599
|
-
}
|
|
5600
|
-
return null;
|
|
5926
|
+
function methodName(node) {
|
|
5927
|
+
const name = node.childForFieldName("name");
|
|
5928
|
+
return name ? name.text : null;
|
|
5601
5929
|
}
|
|
5602
|
-
|
|
5603
|
-
|
|
5604
|
-
|
|
5605
|
-
|
|
5606
|
-
|
|
5607
|
-
|
|
5608
|
-
|
|
5609
|
-
|
|
5610
|
-
|
|
5611
|
-
|
|
5612
|
-
|
|
5930
|
+
function collectSymbolDefs(root) {
|
|
5931
|
+
const out = [];
|
|
5932
|
+
const push = (kind, qualname, node) => {
|
|
5933
|
+
out.push({
|
|
5934
|
+
kind,
|
|
5935
|
+
qualname,
|
|
5936
|
+
startLine: node.startPosition.row + 1,
|
|
5937
|
+
endLine: node.endPosition.row + 1
|
|
5938
|
+
});
|
|
5939
|
+
};
|
|
5940
|
+
const visit = (node, classCtx) => {
|
|
5941
|
+
switch (node.type) {
|
|
5942
|
+
case "function_declaration":
|
|
5943
|
+
case "generator_function_declaration": {
|
|
5944
|
+
const name = node.childForFieldName("name")?.text;
|
|
5945
|
+
if (name) push("function", name, node);
|
|
5946
|
+
break;
|
|
5947
|
+
}
|
|
5948
|
+
case "class_declaration":
|
|
5949
|
+
case "abstract_class_declaration":
|
|
5950
|
+
case "class": {
|
|
5951
|
+
const name = node.childForFieldName("name")?.text;
|
|
5952
|
+
if (name) push("class", name, node);
|
|
5953
|
+
const body = node.childForFieldName("body");
|
|
5954
|
+
if (body) {
|
|
5955
|
+
for (let i = 0; i < body.namedChildCount; i++) {
|
|
5956
|
+
const child = body.namedChild(i);
|
|
5957
|
+
if (child) visit(child, name ?? classCtx);
|
|
5958
|
+
}
|
|
5613
5959
|
}
|
|
5960
|
+
return;
|
|
5614
5961
|
}
|
|
5615
|
-
|
|
5616
|
-
|
|
5962
|
+
case "method_definition": {
|
|
5963
|
+
const name = methodName(node);
|
|
5964
|
+
if (name) {
|
|
5965
|
+
const kind = name === "constructor" ? "constructor" : "method";
|
|
5966
|
+
push(kind, classCtx ? `${classCtx}.${name}` : name, node);
|
|
5967
|
+
}
|
|
5968
|
+
break;
|
|
5617
5969
|
}
|
|
5618
|
-
|
|
5619
|
-
|
|
5620
|
-
|
|
5621
|
-
|
|
5622
|
-
|
|
5623
|
-
|
|
5624
|
-
}
|
|
5625
|
-
|
|
5626
|
-
let baseDir;
|
|
5627
|
-
if (imp.level > 0) {
|
|
5628
|
-
baseDir = import_node_path15.default.dirname(importerPath);
|
|
5629
|
-
for (let i = 1; i < imp.level; i++) baseDir = import_node_path15.default.dirname(baseDir);
|
|
5630
|
-
} else {
|
|
5631
|
-
baseDir = serviceDir;
|
|
5632
|
-
}
|
|
5633
|
-
const moduleBase = imp.modulePath ? import_node_path15.default.join(baseDir, imp.modulePath.split(".").join("/")) : baseDir;
|
|
5634
|
-
const resolved = /* @__PURE__ */ new Set();
|
|
5635
|
-
let needModuleFile = imp.names.length === 0;
|
|
5636
|
-
for (const name of imp.names) {
|
|
5637
|
-
const submoduleFile = import_node_path15.default.join(moduleBase, `${name}.py`);
|
|
5638
|
-
const subpackageInit = import_node_path15.default.join(moduleBase, name, "__init__.py");
|
|
5639
|
-
if (isWithinServiceDir(submoduleFile, serviceDir) && await fileExists(submoduleFile)) {
|
|
5640
|
-
resolved.add(toPosix(import_node_path15.default.relative(serviceDir, submoduleFile)));
|
|
5641
|
-
} else if (isWithinServiceDir(subpackageInit, serviceDir) && await fileExists(subpackageInit)) {
|
|
5642
|
-
resolved.add(toPosix(import_node_path15.default.relative(serviceDir, subpackageInit)));
|
|
5643
|
-
} else {
|
|
5644
|
-
needModuleFile = true;
|
|
5645
|
-
}
|
|
5646
|
-
}
|
|
5647
|
-
if (needModuleFile) {
|
|
5648
|
-
const moduleFileCandidates = imp.modulePath ? [`${moduleBase}.py`, import_node_path15.default.join(moduleBase, "__init__.py")] : [import_node_path15.default.join(moduleBase, "__init__.py")];
|
|
5649
|
-
for (const candidate of moduleFileCandidates) {
|
|
5650
|
-
if (isWithinServiceDir(candidate, serviceDir) && await fileExists(candidate)) {
|
|
5651
|
-
resolved.add(toPosix(import_node_path15.default.relative(serviceDir, candidate)));
|
|
5970
|
+
case "variable_declarator": {
|
|
5971
|
+
const value = node.childForFieldName("value");
|
|
5972
|
+
if (value && (value.type === "arrow_function" || value.type === "function" || value.type === "function_expression" || value.type === "generator_function")) {
|
|
5973
|
+
const nameNode = node.childForFieldName("name");
|
|
5974
|
+
if (nameNode && nameNode.type === "identifier") {
|
|
5975
|
+
push("function", nameNode.text, node);
|
|
5976
|
+
}
|
|
5977
|
+
}
|
|
5652
5978
|
break;
|
|
5653
5979
|
}
|
|
5654
5980
|
}
|
|
5655
|
-
|
|
5656
|
-
|
|
5981
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
5982
|
+
const child = node.namedChild(i);
|
|
5983
|
+
if (child) visit(child, classCtx);
|
|
5984
|
+
}
|
|
5985
|
+
};
|
|
5986
|
+
visit(root, void 0);
|
|
5987
|
+
return out;
|
|
5657
5988
|
}
|
|
5658
|
-
|
|
5659
|
-
|
|
5660
|
-
const
|
|
5661
|
-
const
|
|
5662
|
-
|
|
5663
|
-
|
|
5664
|
-
|
|
5665
|
-
|
|
5989
|
+
function disambiguate(defs) {
|
|
5990
|
+
const counts = /* @__PURE__ */ new Map();
|
|
5991
|
+
for (const def of defs) counts.set(def.qualname, (counts.get(def.qualname) ?? 0) + 1);
|
|
5992
|
+
const seen = /* @__PURE__ */ new Map();
|
|
5993
|
+
return defs.map((def) => {
|
|
5994
|
+
if ((counts.get(def.qualname) ?? 0) <= 1) return { def };
|
|
5995
|
+
const ordinal = seen.get(def.qualname) ?? 0;
|
|
5996
|
+
seen.set(def.qualname, ordinal + 1);
|
|
5997
|
+
return { def, disambiguator: ordinal };
|
|
5998
|
+
});
|
|
5666
5999
|
}
|
|
5667
|
-
function
|
|
5668
|
-
const
|
|
5669
|
-
|
|
5670
|
-
|
|
5671
|
-
|
|
5672
|
-
|
|
5673
|
-
|
|
5674
|
-
|
|
5675
|
-
|
|
5676
|
-
|
|
5677
|
-
|
|
5678
|
-
|
|
5679
|
-
evidence: { file: importerRelPath, line, snippet: snippet2 }
|
|
6000
|
+
async function addSymbols(graph, services) {
|
|
6001
|
+
const parsers = /* @__PURE__ */ new Map();
|
|
6002
|
+
const parserForExt2 = (ext) => {
|
|
6003
|
+
const grammar = GRAMMAR_BY_EXT[ext];
|
|
6004
|
+
if (!grammar) return null;
|
|
6005
|
+
let parser = parsers.get(ext);
|
|
6006
|
+
if (!parser) {
|
|
6007
|
+
parser = new import_tree_sitter3.default();
|
|
6008
|
+
parser.setLanguage(grammar);
|
|
6009
|
+
parsers.set(ext, parser);
|
|
6010
|
+
}
|
|
6011
|
+
return parser;
|
|
5680
6012
|
};
|
|
5681
|
-
|
|
5682
|
-
return 1;
|
|
5683
|
-
}
|
|
5684
|
-
async function addImports(graph, services) {
|
|
5685
|
-
const jsParser = makeJsParser2();
|
|
5686
|
-
const pyParser = makePyParser2();
|
|
5687
|
-
const goParser = makeGoParser2();
|
|
6013
|
+
let nodesAdded = 0;
|
|
5688
6014
|
let edgesAdded = 0;
|
|
5689
6015
|
for (const service of services) {
|
|
5690
|
-
const tsPaths = await loadTsPathConfig(service.dir);
|
|
5691
6016
|
const files = await loadSourceFiles(service.dir);
|
|
5692
6017
|
for (const file of files) {
|
|
5693
|
-
|
|
5694
|
-
|
|
5695
|
-
const
|
|
5696
|
-
|
|
5697
|
-
const isGo = import_node_path15.default.extname(file.path) === ".go";
|
|
5698
|
-
if (isGo) {
|
|
5699
|
-
let goImports = [];
|
|
5700
|
-
try {
|
|
5701
|
-
const tree = parseSource3(goParser, file.content);
|
|
5702
|
-
collectGoImports(tree.rootNode, goImports);
|
|
5703
|
-
} catch (err) {
|
|
5704
|
-
recordExtractionError("import extraction", file.path, err);
|
|
5705
|
-
continue;
|
|
5706
|
-
}
|
|
5707
|
-
const goMod = await import_node_fs12.promises.readFile(import_node_path15.default.join(service.dir, "go.mod"), "utf8").catch(() => "");
|
|
5708
|
-
const modulePath = goMod.match(/^\s*module\s+(\S+)\s*$/m)?.[1];
|
|
5709
|
-
if (!modulePath) continue;
|
|
5710
|
-
for (const imp of goImports) {
|
|
5711
|
-
const resolved = await resolveGoImport(imp.specifier, modulePath, service.dir);
|
|
5712
|
-
if (!resolved) continue;
|
|
5713
|
-
edgesAdded += emitImportEdge(graph, service.pkg.name, importerFileId, relFile, resolved, imp.line, imp.snippet);
|
|
5714
|
-
}
|
|
5715
|
-
continue;
|
|
5716
|
-
}
|
|
5717
|
-
if (isPython) {
|
|
5718
|
-
let pyImports = [];
|
|
5719
|
-
try {
|
|
5720
|
-
const tree = parseSource3(pyParser, file.content);
|
|
5721
|
-
collectPyImports(tree.rootNode, pyImports);
|
|
5722
|
-
} catch (err) {
|
|
5723
|
-
recordExtractionError("import extraction", file.path, err);
|
|
5724
|
-
continue;
|
|
5725
|
-
}
|
|
5726
|
-
for (const imp of pyImports) {
|
|
5727
|
-
const resolvedPaths = await resolvePyImport(imp, file.path, service.dir);
|
|
5728
|
-
for (const resolved of resolvedPaths) {
|
|
5729
|
-
edgesAdded += emitImportEdge(
|
|
5730
|
-
graph,
|
|
5731
|
-
service.pkg.name,
|
|
5732
|
-
importerFileId,
|
|
5733
|
-
relFile,
|
|
5734
|
-
resolved,
|
|
5735
|
-
imp.line,
|
|
5736
|
-
imp.snippet
|
|
5737
|
-
);
|
|
5738
|
-
}
|
|
5739
|
-
}
|
|
5740
|
-
continue;
|
|
5741
|
-
}
|
|
5742
|
-
let jsImports = [];
|
|
6018
|
+
const parser = parserForExt2(import_node_path15.default.extname(file.path));
|
|
6019
|
+
if (!parser) continue;
|
|
6020
|
+
const relPath = toPosix(import_node_path15.default.relative(service.dir, file.path));
|
|
6021
|
+
let defs;
|
|
5743
6022
|
try {
|
|
5744
|
-
const tree = parseSource3(
|
|
5745
|
-
|
|
6023
|
+
const tree = parseSource3(parser, file.content);
|
|
6024
|
+
defs = collectSymbolDefs(tree.rootNode);
|
|
5746
6025
|
} catch (err) {
|
|
5747
|
-
recordExtractionError("
|
|
6026
|
+
recordExtractionError("symbol extraction", file.path, err);
|
|
5748
6027
|
continue;
|
|
5749
6028
|
}
|
|
5750
|
-
|
|
5751
|
-
|
|
5752
|
-
|
|
5753
|
-
|
|
5754
|
-
|
|
5755
|
-
|
|
5756
|
-
|
|
5757
|
-
|
|
5758
|
-
|
|
5759
|
-
|
|
5760
|
-
|
|
5761
|
-
)
|
|
6029
|
+
if (defs.length === 0) continue;
|
|
6030
|
+
const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
|
|
6031
|
+
graph,
|
|
6032
|
+
service.pkg.name,
|
|
6033
|
+
service.node.id,
|
|
6034
|
+
relPath
|
|
6035
|
+
);
|
|
6036
|
+
nodesAdded += fn;
|
|
6037
|
+
edgesAdded += fe;
|
|
6038
|
+
for (const { def, disambiguator } of disambiguate(defs)) {
|
|
6039
|
+
const sid = (0, import_types12.symbolId)(service.pkg.name, relPath, def.qualname, disambiguator);
|
|
6040
|
+
if (!graph.hasNode(sid)) {
|
|
6041
|
+
const node = {
|
|
6042
|
+
id: sid,
|
|
6043
|
+
type: import_types12.NodeType.SymbolNode,
|
|
6044
|
+
kind: def.kind,
|
|
6045
|
+
qualname: def.qualname,
|
|
6046
|
+
span: { startLine: def.startLine, endLine: def.endLine },
|
|
6047
|
+
service: service.pkg.name,
|
|
6048
|
+
relPath,
|
|
6049
|
+
discoveredVia: "static"
|
|
6050
|
+
};
|
|
6051
|
+
graph.addNode(sid, node);
|
|
6052
|
+
nodesAdded++;
|
|
6053
|
+
}
|
|
6054
|
+
const containsId = (0, import_types12.extractedEdgeId)(fileNodeId, sid, import_types12.EdgeType.CONTAINS);
|
|
6055
|
+
if (!graph.hasEdge(containsId)) {
|
|
6056
|
+
const edge = {
|
|
6057
|
+
id: containsId,
|
|
6058
|
+
source: fileNodeId,
|
|
6059
|
+
target: sid,
|
|
6060
|
+
type: import_types12.EdgeType.CONTAINS,
|
|
6061
|
+
provenance: import_types12.Provenance.EXTRACTED,
|
|
6062
|
+
confidence: (0, import_types12.confidenceForExtracted)("structural"),
|
|
6063
|
+
evidence: {
|
|
6064
|
+
file: relPath,
|
|
6065
|
+
line: def.startLine,
|
|
6066
|
+
snippet: snippet(file.content, def.startLine)
|
|
6067
|
+
}
|
|
6068
|
+
};
|
|
6069
|
+
graph.addEdgeWithKey(containsId, fileNodeId, sid, edge);
|
|
6070
|
+
edgesAdded++;
|
|
6071
|
+
}
|
|
5762
6072
|
}
|
|
5763
6073
|
}
|
|
5764
6074
|
}
|
|
5765
|
-
return { nodesAdded
|
|
6075
|
+
return { nodesAdded, edgesAdded };
|
|
5766
6076
|
}
|
|
5767
6077
|
|
|
5768
6078
|
// src/extract/symbol-edges.ts
|
|
6079
|
+
init_cjs_shims();
|
|
6080
|
+
var import_node_path16 = __toESM(require("path"), 1);
|
|
6081
|
+
var import_tree_sitter4 = __toESM(require("tree-sitter"), 1);
|
|
6082
|
+
var import_types13 = require("@neat.is/types");
|
|
5769
6083
|
function extendsInfo(classHeritage) {
|
|
5770
6084
|
for (let i = 0; i < classHeritage.namedChildCount; i++) {
|
|
5771
6085
|
const child = classHeritage.namedChild(i);
|
|
@@ -5876,7 +6190,7 @@ async function addSymbolEdges(graph, services) {
|
|
|
5876
6190
|
const fileDir = import_node_path16.default.dirname(file.path);
|
|
5877
6191
|
let root;
|
|
5878
6192
|
try {
|
|
5879
|
-
root =
|
|
6193
|
+
root = parseSource3(parser, file.content).rootNode;
|
|
5880
6194
|
} catch (err) {
|
|
5881
6195
|
recordExtractionError("symbol edge extraction", file.path, err);
|
|
5882
6196
|
continue;
|
|
@@ -8328,7 +8642,7 @@ function columnsFromObject(obj) {
|
|
|
8328
8642
|
}
|
|
8329
8643
|
function drizzleEndpointsFromFile(file, serviceDir) {
|
|
8330
8644
|
if (!DRIZZLE_IMPORT_RE.test(file.content)) return [];
|
|
8331
|
-
const tree =
|
|
8645
|
+
const tree = parseSource3(parserForExt(import_node_path37.default.extname(file.path)), file.content);
|
|
8332
8646
|
const out = [];
|
|
8333
8647
|
const seen = /* @__PURE__ */ new Set();
|
|
8334
8648
|
const walk6 = (node) => {
|