@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/index.cjs
CHANGED
|
@@ -817,8 +817,8 @@ init_cjs_shims();
|
|
|
817
817
|
|
|
818
818
|
// src/ingest.ts
|
|
819
819
|
init_cjs_shims();
|
|
820
|
-
var
|
|
821
|
-
var
|
|
820
|
+
var import_node_fs7 = require("fs");
|
|
821
|
+
var import_node_path8 = __toESM(require("path"), 1);
|
|
822
822
|
var sourceMapJs = __toESM(require("source-map-js"), 1);
|
|
823
823
|
|
|
824
824
|
// src/policy.ts
|
|
@@ -2167,16 +2167,16 @@ var PolicyViolationsLog = class {
|
|
|
2167
2167
|
};
|
|
2168
2168
|
|
|
2169
2169
|
// src/ingest.ts
|
|
2170
|
-
var
|
|
2170
|
+
var import_types8 = require("@neat.is/types");
|
|
2171
2171
|
|
|
2172
2172
|
// src/extract/routes.ts
|
|
2173
2173
|
init_cjs_shims();
|
|
2174
|
-
var
|
|
2175
|
-
var
|
|
2176
|
-
var
|
|
2177
|
-
var
|
|
2178
|
-
var
|
|
2179
|
-
var
|
|
2174
|
+
var import_node_path7 = __toESM(require("path"), 1);
|
|
2175
|
+
var import_tree_sitter2 = __toESM(require("tree-sitter"), 1);
|
|
2176
|
+
var import_tree_sitter_javascript2 = __toESM(require("tree-sitter-javascript"), 1);
|
|
2177
|
+
var import_tree_sitter_python2 = __toESM(require("tree-sitter-python"), 1);
|
|
2178
|
+
var import_tree_sitter_go2 = __toESM(require("tree-sitter-go"), 1);
|
|
2179
|
+
var import_types6 = require("@neat.is/types");
|
|
2180
2180
|
|
|
2181
2181
|
// src/extract/shared.ts
|
|
2182
2182
|
init_cjs_shims();
|
|
@@ -2547,7 +2547,15 @@ function ensureFileNode(graph, serviceName, serviceNodeId, relPath) {
|
|
|
2547
2547
|
return { fileNodeId, nodesAdded, edgesAdded };
|
|
2548
2548
|
}
|
|
2549
2549
|
|
|
2550
|
-
// src/extract/
|
|
2550
|
+
// src/extract/imports.ts
|
|
2551
|
+
init_cjs_shims();
|
|
2552
|
+
var import_node_path6 = __toESM(require("path"), 1);
|
|
2553
|
+
var import_node_fs6 = require("fs");
|
|
2554
|
+
var import_tree_sitter = __toESM(require("tree-sitter"), 1);
|
|
2555
|
+
var import_tree_sitter_javascript = __toESM(require("tree-sitter-javascript"), 1);
|
|
2556
|
+
var import_tree_sitter_python = __toESM(require("tree-sitter-python"), 1);
|
|
2557
|
+
var import_tree_sitter_go = __toESM(require("tree-sitter-go"), 1);
|
|
2558
|
+
var import_types5 = require("@neat.is/types");
|
|
2551
2559
|
var PARSE_CHUNK = 16384;
|
|
2552
2560
|
function parseSource(parser, source) {
|
|
2553
2561
|
return parser.parse(
|
|
@@ -2569,155 +2577,532 @@ function makeGoParser() {
|
|
|
2569
2577
|
p.setLanguage(import_tree_sitter_go.default);
|
|
2570
2578
|
return p;
|
|
2571
2579
|
}
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
"
|
|
2580
|
-
"all"
|
|
2581
|
-
]);
|
|
2582
|
-
var NEXT_APP_METHODS = /* @__PURE__ */ new Set(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"]);
|
|
2583
|
-
var JS_ROUTE_EXTENSIONS = /* @__PURE__ */ new Set([".js", ".jsx", ".mjs", ".cjs", ".ts", ".tsx"]);
|
|
2584
|
-
function ginRoutesFromSource(source, parser) {
|
|
2585
|
-
const tree = parseSource(parser, source);
|
|
2586
|
-
const prefixes = /* @__PURE__ */ new Map();
|
|
2587
|
-
const out = [];
|
|
2588
|
-
walk(tree.rootNode, (node) => {
|
|
2589
|
-
if (node.type === "short_var_declaration" || node.type === "var_spec") {
|
|
2590
|
-
const name = node.childForFieldName("left")?.namedChild(0)?.text ?? node.childForFieldName("name")?.text;
|
|
2591
|
-
const value = node.childForFieldName("right")?.namedChild(0) ?? node.childForFieldName("value");
|
|
2592
|
-
if (name && value?.type === "call_expression") {
|
|
2593
|
-
const fn2 = value.childForFieldName("function");
|
|
2594
|
-
const field = fn2?.childForFieldName("field")?.text;
|
|
2595
|
-
const first2 = value.childForFieldName("arguments")?.namedChild(0);
|
|
2596
|
-
if (field === "Group" && first2?.type === "interpreted_string_literal") {
|
|
2597
|
-
prefixes.set(name, first2.text.slice(1, -1));
|
|
2598
|
-
}
|
|
2599
|
-
}
|
|
2600
|
-
return;
|
|
2601
|
-
}
|
|
2602
|
-
if (node.type !== "call_expression") return;
|
|
2603
|
-
const fn = node.childForFieldName("function");
|
|
2604
|
-
if (fn?.type !== "selector_expression") return;
|
|
2605
|
-
const method = fn.childForFieldName("field")?.text?.toUpperCase();
|
|
2606
|
-
if (!method || !ROUTER_METHODS.has(method.toLowerCase())) return;
|
|
2607
|
-
const receiver = fn.childForFieldName("operand")?.text ?? "";
|
|
2608
|
-
const first = node.childForFieldName("arguments")?.namedChild(0);
|
|
2609
|
-
if (first?.type !== "interpreted_string_literal") return;
|
|
2610
|
-
const leaf = first.text.slice(1, -1);
|
|
2611
|
-
out.push({
|
|
2612
|
-
method: method === "ALL" ? "ALL" : method,
|
|
2613
|
-
pathTemplate: canonicalizeTemplate((prefixes.get(receiver) ?? "") + leaf),
|
|
2614
|
-
line: node.startPosition.row + 1,
|
|
2615
|
-
framework: "gin"
|
|
2616
|
-
});
|
|
2617
|
-
});
|
|
2618
|
-
return out;
|
|
2619
|
-
}
|
|
2620
|
-
var FASTAPI_METHODS = /* @__PURE__ */ new Set(["get", "post", "put", "patch", "delete", "options", "head", "trace"]);
|
|
2621
|
-
var NESTJS_METHODS = /* @__PURE__ */ new Map([
|
|
2622
|
-
["Get", "GET"],
|
|
2623
|
-
["Post", "POST"],
|
|
2624
|
-
["Put", "PUT"],
|
|
2625
|
-
["Patch", "PATCH"],
|
|
2626
|
-
["Delete", "DELETE"],
|
|
2627
|
-
["Options", "OPTIONS"],
|
|
2628
|
-
["Head", "HEAD"],
|
|
2629
|
-
["All", "ALL"]
|
|
2630
|
-
]);
|
|
2631
|
-
function canonicalizeTemplate(raw) {
|
|
2632
|
-
let p = raw.split("?")[0].split("#")[0];
|
|
2633
|
-
if (!p.startsWith("/")) p = "/" + p;
|
|
2634
|
-
if (p.length > 1 && p.endsWith("/")) p = p.slice(0, -1);
|
|
2635
|
-
return p;
|
|
2636
|
-
}
|
|
2637
|
-
function isDynamicSegment(seg) {
|
|
2638
|
-
if (seg.length === 0) return false;
|
|
2639
|
-
if (seg.includes(":")) return true;
|
|
2640
|
-
if (seg.startsWith("{") || seg.startsWith("[")) return true;
|
|
2641
|
-
if (/^\d+$/.test(seg)) return true;
|
|
2642
|
-
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;
|
|
2643
|
-
if (/^[0-9a-f]{24,}$/i.test(seg)) return true;
|
|
2644
|
-
return false;
|
|
2580
|
+
function stringLiteralText(node) {
|
|
2581
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
2582
|
+
const child = node.child(i);
|
|
2583
|
+
if (child?.type === "string_fragment") return child.text;
|
|
2584
|
+
}
|
|
2585
|
+
const raw = node.text;
|
|
2586
|
+
if (raw.length >= 2) return raw.slice(1, -1);
|
|
2587
|
+
return raw.length === 0 ? null : "";
|
|
2645
2588
|
}
|
|
2646
|
-
function
|
|
2647
|
-
const
|
|
2648
|
-
|
|
2649
|
-
const normalised = segments.map((seg) => isDynamicSegment(seg) ? ":param" : seg.toLowerCase());
|
|
2650
|
-
return "/" + normalised.join("/");
|
|
2589
|
+
function clipSnippet(text) {
|
|
2590
|
+
const oneLine = text.split("\n")[0] ?? text;
|
|
2591
|
+
return oneLine.length > 120 ? oneLine.slice(0, 120) : oneLine;
|
|
2651
2592
|
}
|
|
2652
|
-
function
|
|
2653
|
-
|
|
2593
|
+
function collectGoImports(node, out) {
|
|
2594
|
+
if (node.type === "import_spec") {
|
|
2595
|
+
const pathNode = node.childForFieldName("path");
|
|
2596
|
+
if (pathNode) {
|
|
2597
|
+
const specifier = pathNode.text.replace(/^`|`$/g, "").replace(/^"|"$/g, "");
|
|
2598
|
+
if (specifier) out.push({ specifier, line: node.startPosition.row + 1, snippet: clipSnippet(node.text) });
|
|
2599
|
+
}
|
|
2600
|
+
return;
|
|
2601
|
+
}
|
|
2654
2602
|
for (let i = 0; i < node.namedChildCount; i++) {
|
|
2655
2603
|
const child = node.namedChild(i);
|
|
2656
|
-
if (child)
|
|
2604
|
+
if (child) collectGoImports(child, out);
|
|
2657
2605
|
}
|
|
2658
2606
|
}
|
|
2659
|
-
function
|
|
2660
|
-
if (node.type === "
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2607
|
+
function collectJsImports(node, out) {
|
|
2608
|
+
if (node.type === "import_statement") {
|
|
2609
|
+
const source = node.childForFieldName("source");
|
|
2610
|
+
if (source) {
|
|
2611
|
+
const specifier = stringLiteralText(source);
|
|
2612
|
+
if (specifier) {
|
|
2613
|
+
out.push({ specifier, line: node.startPosition.row + 1, snippet: clipSnippet(node.text) });
|
|
2614
|
+
}
|
|
2664
2615
|
}
|
|
2665
|
-
return
|
|
2616
|
+
return;
|
|
2666
2617
|
}
|
|
2667
|
-
if (node.type === "
|
|
2668
|
-
|
|
2669
|
-
|
|
2618
|
+
if (node.type === "call_expression") {
|
|
2619
|
+
const fn = node.childForFieldName("function");
|
|
2620
|
+
if (fn?.type === "identifier" && fn.text === "require") {
|
|
2621
|
+
const args = node.childForFieldName("arguments");
|
|
2622
|
+
const firstArg = args?.namedChild(0);
|
|
2623
|
+
if (firstArg?.type === "string") {
|
|
2624
|
+
const specifier = stringLiteralText(firstArg);
|
|
2625
|
+
if (specifier) {
|
|
2626
|
+
out.push({ specifier, line: node.startPosition.row + 1, snippet: clipSnippet(node.text) });
|
|
2627
|
+
}
|
|
2628
|
+
}
|
|
2670
2629
|
}
|
|
2671
|
-
const raw = node.text;
|
|
2672
|
-
return raw.length >= 2 ? raw.slice(1, -1) : "";
|
|
2673
2630
|
}
|
|
2674
|
-
|
|
2631
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
2632
|
+
const child = node.namedChild(i);
|
|
2633
|
+
if (child) collectJsImports(child, out);
|
|
2634
|
+
}
|
|
2675
2635
|
}
|
|
2676
|
-
function
|
|
2677
|
-
|
|
2678
|
-
const
|
|
2679
|
-
if (
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2636
|
+
function collectImportedNames(node, out) {
|
|
2637
|
+
if (node.type === "aliased_import") {
|
|
2638
|
+
const nameNode = node.childForFieldName("name");
|
|
2639
|
+
if (nameNode) out.push(nameNode.text);
|
|
2640
|
+
return;
|
|
2641
|
+
}
|
|
2642
|
+
if (node.type === "dotted_name") {
|
|
2643
|
+
out.push(node.text);
|
|
2644
|
+
return;
|
|
2645
|
+
}
|
|
2646
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
2647
|
+
const child = node.namedChild(i);
|
|
2648
|
+
if (child) collectImportedNames(child, out);
|
|
2686
2649
|
}
|
|
2687
|
-
return null;
|
|
2688
2650
|
}
|
|
2689
|
-
function
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
const
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2651
|
+
function collectPyImports(node, out) {
|
|
2652
|
+
if (node.type === "import_from_statement") {
|
|
2653
|
+
let level = 0;
|
|
2654
|
+
let modulePath = "";
|
|
2655
|
+
const names = [];
|
|
2656
|
+
let pastFrom = false;
|
|
2657
|
+
let pastImport = false;
|
|
2658
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
2659
|
+
const child = node.child(i);
|
|
2660
|
+
if (!child) continue;
|
|
2661
|
+
if (!pastFrom) {
|
|
2662
|
+
if (child.type === "from") pastFrom = true;
|
|
2663
|
+
continue;
|
|
2664
|
+
}
|
|
2665
|
+
if (!pastImport) {
|
|
2666
|
+
if (child.type === "import") {
|
|
2667
|
+
pastImport = true;
|
|
2668
|
+
continue;
|
|
2669
|
+
}
|
|
2670
|
+
if (child.type === "relative_import") {
|
|
2671
|
+
for (let j = 0; j < child.childCount; j++) {
|
|
2672
|
+
const rc = child.child(j);
|
|
2673
|
+
if (!rc) continue;
|
|
2674
|
+
if (rc.type === "import_prefix") {
|
|
2675
|
+
for (let k = 0; k < rc.childCount; k++) {
|
|
2676
|
+
if (rc.child(k)?.type === ".") level++;
|
|
2677
|
+
}
|
|
2678
|
+
} else if (rc.type === "dotted_name") modulePath = rc.text;
|
|
2679
|
+
}
|
|
2680
|
+
} else if (child.type === "dotted_name") {
|
|
2681
|
+
modulePath = child.text;
|
|
2709
2682
|
}
|
|
2683
|
+
continue;
|
|
2710
2684
|
}
|
|
2711
|
-
|
|
2685
|
+
collectImportedNames(child, names);
|
|
2686
|
+
}
|
|
2687
|
+
if (level > 0 || modulePath) {
|
|
2688
|
+
out.push({ modulePath, level, names, line: node.startPosition.row + 1, snippet: clipSnippet(node.text) });
|
|
2712
2689
|
}
|
|
2713
2690
|
}
|
|
2714
|
-
|
|
2691
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
2692
|
+
const child = node.namedChild(i);
|
|
2693
|
+
if (child) collectPyImports(child, out);
|
|
2694
|
+
}
|
|
2715
2695
|
}
|
|
2716
|
-
function
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2696
|
+
async function fileExists(p) {
|
|
2697
|
+
try {
|
|
2698
|
+
await import_node_fs6.promises.access(p);
|
|
2699
|
+
return true;
|
|
2700
|
+
} catch {
|
|
2701
|
+
return false;
|
|
2702
|
+
}
|
|
2703
|
+
}
|
|
2704
|
+
function isWithinServiceDir(candidate, serviceDir) {
|
|
2705
|
+
const rel = import_node_path6.default.relative(serviceDir, candidate);
|
|
2706
|
+
return rel !== "" && !rel.startsWith("..") && !import_node_path6.default.isAbsolute(rel);
|
|
2707
|
+
}
|
|
2708
|
+
var JS_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"];
|
|
2709
|
+
var JS_INDEX_FILES = JS_EXTENSIONS.map((ext) => `index${ext}`);
|
|
2710
|
+
async function firstExistingCandidate(base, serviceDir) {
|
|
2711
|
+
for (const ext of JS_EXTENSIONS) {
|
|
2712
|
+
const candidate = base + ext;
|
|
2713
|
+
if (isWithinServiceDir(candidate, serviceDir) && await fileExists(candidate)) {
|
|
2714
|
+
return toPosix(import_node_path6.default.relative(serviceDir, candidate));
|
|
2715
|
+
}
|
|
2716
|
+
}
|
|
2717
|
+
for (const indexFile of JS_INDEX_FILES) {
|
|
2718
|
+
const candidate = import_node_path6.default.join(base, indexFile);
|
|
2719
|
+
if (isWithinServiceDir(candidate, serviceDir) && await fileExists(candidate)) {
|
|
2720
|
+
return toPosix(import_node_path6.default.relative(serviceDir, candidate));
|
|
2721
|
+
}
|
|
2722
|
+
}
|
|
2723
|
+
return null;
|
|
2724
|
+
}
|
|
2725
|
+
async function loadTsPathConfig(serviceDir) {
|
|
2726
|
+
const tsconfigPath = import_node_path6.default.join(serviceDir, "tsconfig.json");
|
|
2727
|
+
let raw;
|
|
2728
|
+
try {
|
|
2729
|
+
raw = await import_node_fs6.promises.readFile(tsconfigPath, "utf8");
|
|
2730
|
+
} catch {
|
|
2731
|
+
return null;
|
|
2732
|
+
}
|
|
2733
|
+
try {
|
|
2734
|
+
const parsed = JSON.parse(raw);
|
|
2735
|
+
const paths = parsed.compilerOptions?.paths;
|
|
2736
|
+
if (!paths || Object.keys(paths).length === 0) return null;
|
|
2737
|
+
const baseUrl = parsed.compilerOptions?.baseUrl;
|
|
2738
|
+
return { paths, baseDir: baseUrl ? import_node_path6.default.resolve(serviceDir, baseUrl) : serviceDir };
|
|
2739
|
+
} catch (err) {
|
|
2740
|
+
recordExtractionError("import alias resolution", tsconfigPath, err);
|
|
2741
|
+
return null;
|
|
2742
|
+
}
|
|
2743
|
+
}
|
|
2744
|
+
async function resolveTsAlias(specifier, config, serviceDir) {
|
|
2745
|
+
for (const [pattern, targets] of Object.entries(config.paths)) {
|
|
2746
|
+
let suffix = null;
|
|
2747
|
+
if (pattern === specifier) {
|
|
2748
|
+
suffix = "";
|
|
2749
|
+
} else if (pattern.endsWith("/*")) {
|
|
2750
|
+
const prefix = pattern.slice(0, -1);
|
|
2751
|
+
if (specifier.startsWith(prefix)) suffix = specifier.slice(prefix.length);
|
|
2752
|
+
}
|
|
2753
|
+
if (suffix === null) continue;
|
|
2754
|
+
for (const target of targets) {
|
|
2755
|
+
const targetBase = target.endsWith("/*") ? target.slice(0, -2) : target.replace(/\*$/, "");
|
|
2756
|
+
const resolvedBase = import_node_path6.default.resolve(config.baseDir, targetBase, suffix);
|
|
2757
|
+
const hit = await firstExistingCandidate(resolvedBase, serviceDir);
|
|
2758
|
+
if (hit) return hit;
|
|
2759
|
+
if (isWithinServiceDir(resolvedBase, serviceDir) && await fileExists(resolvedBase)) {
|
|
2760
|
+
return toPosix(import_node_path6.default.relative(serviceDir, resolvedBase));
|
|
2761
|
+
}
|
|
2762
|
+
}
|
|
2763
|
+
}
|
|
2764
|
+
return null;
|
|
2765
|
+
}
|
|
2766
|
+
async function resolveJsImport(specifier, importerDir, serviceDir, tsPaths) {
|
|
2767
|
+
if (!specifier) return null;
|
|
2768
|
+
if (specifier.startsWith("./") || specifier.startsWith("../")) {
|
|
2769
|
+
const base = import_node_path6.default.resolve(importerDir, specifier);
|
|
2770
|
+
const ext = import_node_path6.default.extname(specifier);
|
|
2771
|
+
if (ext) {
|
|
2772
|
+
if (ext === ".js" || ext === ".jsx") {
|
|
2773
|
+
const tsExt = ext === ".jsx" ? ".tsx" : ".ts";
|
|
2774
|
+
const tsSibling = base.slice(0, -ext.length) + tsExt;
|
|
2775
|
+
if (isWithinServiceDir(tsSibling, serviceDir) && await fileExists(tsSibling)) {
|
|
2776
|
+
return toPosix(import_node_path6.default.relative(serviceDir, tsSibling));
|
|
2777
|
+
}
|
|
2778
|
+
}
|
|
2779
|
+
if (isWithinServiceDir(base, serviceDir) && await fileExists(base)) {
|
|
2780
|
+
return toPosix(import_node_path6.default.relative(serviceDir, base));
|
|
2781
|
+
}
|
|
2782
|
+
if (!JS_EXTENSIONS.includes(ext)) {
|
|
2783
|
+
return firstExistingCandidate(base, serviceDir);
|
|
2784
|
+
}
|
|
2785
|
+
return null;
|
|
2786
|
+
}
|
|
2787
|
+
return firstExistingCandidate(base, serviceDir);
|
|
2788
|
+
}
|
|
2789
|
+
if (tsPaths) return resolveTsAlias(specifier, tsPaths, serviceDir);
|
|
2790
|
+
return null;
|
|
2791
|
+
}
|
|
2792
|
+
async function resolvePyImport(imp, importerPath, serviceDir) {
|
|
2793
|
+
let baseDir;
|
|
2794
|
+
if (imp.level > 0) {
|
|
2795
|
+
baseDir = import_node_path6.default.dirname(importerPath);
|
|
2796
|
+
for (let i = 1; i < imp.level; i++) baseDir = import_node_path6.default.dirname(baseDir);
|
|
2797
|
+
} else {
|
|
2798
|
+
baseDir = serviceDir;
|
|
2799
|
+
}
|
|
2800
|
+
const moduleBase = imp.modulePath ? import_node_path6.default.join(baseDir, imp.modulePath.split(".").join("/")) : baseDir;
|
|
2801
|
+
const resolved = /* @__PURE__ */ new Set();
|
|
2802
|
+
let needModuleFile = imp.names.length === 0;
|
|
2803
|
+
for (const name of imp.names) {
|
|
2804
|
+
const submoduleFile = import_node_path6.default.join(moduleBase, `${name}.py`);
|
|
2805
|
+
const subpackageInit = import_node_path6.default.join(moduleBase, name, "__init__.py");
|
|
2806
|
+
if (isWithinServiceDir(submoduleFile, serviceDir) && await fileExists(submoduleFile)) {
|
|
2807
|
+
resolved.add(toPosix(import_node_path6.default.relative(serviceDir, submoduleFile)));
|
|
2808
|
+
} else if (isWithinServiceDir(subpackageInit, serviceDir) && await fileExists(subpackageInit)) {
|
|
2809
|
+
resolved.add(toPosix(import_node_path6.default.relative(serviceDir, subpackageInit)));
|
|
2810
|
+
} else {
|
|
2811
|
+
needModuleFile = true;
|
|
2812
|
+
}
|
|
2813
|
+
}
|
|
2814
|
+
if (needModuleFile) {
|
|
2815
|
+
const moduleFileCandidates = imp.modulePath ? [`${moduleBase}.py`, import_node_path6.default.join(moduleBase, "__init__.py")] : [import_node_path6.default.join(moduleBase, "__init__.py")];
|
|
2816
|
+
for (const candidate of moduleFileCandidates) {
|
|
2817
|
+
if (isWithinServiceDir(candidate, serviceDir) && await fileExists(candidate)) {
|
|
2818
|
+
resolved.add(toPosix(import_node_path6.default.relative(serviceDir, candidate)));
|
|
2819
|
+
break;
|
|
2820
|
+
}
|
|
2821
|
+
}
|
|
2822
|
+
}
|
|
2823
|
+
return [...resolved];
|
|
2824
|
+
}
|
|
2825
|
+
async function resolveGoImport(specifier, modulePath, serviceDir) {
|
|
2826
|
+
if (specifier !== modulePath && !specifier.startsWith(`${modulePath}/`)) return null;
|
|
2827
|
+
const suffix = specifier === modulePath ? "" : specifier.slice(modulePath.length + 1);
|
|
2828
|
+
const dir = import_node_path6.default.join(serviceDir, suffix);
|
|
2829
|
+
const entries = await import_node_fs6.promises.readdir(dir, { withFileTypes: true }).catch(() => []);
|
|
2830
|
+
const candidates = entries.filter((entry) => entry.isFile() && entry.name.endsWith(".go") && !entry.name.endsWith("_test.go")).map((entry) => import_node_path6.default.join(dir, entry.name));
|
|
2831
|
+
if (candidates.length !== 1) return null;
|
|
2832
|
+
return toPosix(import_node_path6.default.relative(serviceDir, candidates[0]));
|
|
2833
|
+
}
|
|
2834
|
+
function emitImportEdge(graph, serviceName, importerFileId, importerRelPath, importeeRelPath, line, snippet2) {
|
|
2835
|
+
const importeeFileId = (0, import_types5.fileId)(serviceName, importeeRelPath);
|
|
2836
|
+
if (!graph.hasNode(importeeFileId)) return 0;
|
|
2837
|
+
const edgeId = (0, import_types5.extractedEdgeId)(importerFileId, importeeFileId, import_types5.EdgeType.IMPORTS);
|
|
2838
|
+
if (graph.hasEdge(edgeId)) return 0;
|
|
2839
|
+
const edge = {
|
|
2840
|
+
id: edgeId,
|
|
2841
|
+
source: importerFileId,
|
|
2842
|
+
target: importeeFileId,
|
|
2843
|
+
type: import_types5.EdgeType.IMPORTS,
|
|
2844
|
+
provenance: import_types5.Provenance.EXTRACTED,
|
|
2845
|
+
confidence: (0, import_types5.confidenceForExtracted)("structural"),
|
|
2846
|
+
evidence: { file: importerRelPath, line, snippet: snippet2 }
|
|
2847
|
+
};
|
|
2848
|
+
graph.addEdgeWithKey(edgeId, importerFileId, importeeFileId, edge);
|
|
2849
|
+
return 1;
|
|
2850
|
+
}
|
|
2851
|
+
async function addImports(graph, services) {
|
|
2852
|
+
const jsParser = makeJsParser();
|
|
2853
|
+
const pyParser = makePyParser();
|
|
2854
|
+
const goParser = makeGoParser();
|
|
2855
|
+
let edgesAdded = 0;
|
|
2856
|
+
for (const service of services) {
|
|
2857
|
+
const tsPaths = await loadTsPathConfig(service.dir);
|
|
2858
|
+
const files = await loadSourceFiles(service.dir);
|
|
2859
|
+
for (const file of files) {
|
|
2860
|
+
if (isTestPath(file.path)) continue;
|
|
2861
|
+
const relFile = toPosix(import_node_path6.default.relative(service.dir, file.path));
|
|
2862
|
+
const importerFileId = (0, import_types5.fileId)(service.pkg.name, relFile);
|
|
2863
|
+
const isPython = import_node_path6.default.extname(file.path) === ".py";
|
|
2864
|
+
const isGo = import_node_path6.default.extname(file.path) === ".go";
|
|
2865
|
+
if (isGo) {
|
|
2866
|
+
let goImports = [];
|
|
2867
|
+
try {
|
|
2868
|
+
const tree = parseSource(goParser, file.content);
|
|
2869
|
+
collectGoImports(tree.rootNode, goImports);
|
|
2870
|
+
} catch (err) {
|
|
2871
|
+
recordExtractionError("import extraction", file.path, err);
|
|
2872
|
+
continue;
|
|
2873
|
+
}
|
|
2874
|
+
const goMod = await import_node_fs6.promises.readFile(import_node_path6.default.join(service.dir, "go.mod"), "utf8").catch(() => "");
|
|
2875
|
+
const modulePath = goMod.match(/^\s*module\s+(\S+)\s*$/m)?.[1];
|
|
2876
|
+
if (!modulePath) continue;
|
|
2877
|
+
for (const imp of goImports) {
|
|
2878
|
+
const resolved = await resolveGoImport(imp.specifier, modulePath, service.dir);
|
|
2879
|
+
if (!resolved) continue;
|
|
2880
|
+
edgesAdded += emitImportEdge(graph, service.pkg.name, importerFileId, relFile, resolved, imp.line, imp.snippet);
|
|
2881
|
+
}
|
|
2882
|
+
continue;
|
|
2883
|
+
}
|
|
2884
|
+
if (isPython) {
|
|
2885
|
+
let pyImports = [];
|
|
2886
|
+
try {
|
|
2887
|
+
const tree = parseSource(pyParser, file.content);
|
|
2888
|
+
collectPyImports(tree.rootNode, pyImports);
|
|
2889
|
+
} catch (err) {
|
|
2890
|
+
recordExtractionError("import extraction", file.path, err);
|
|
2891
|
+
continue;
|
|
2892
|
+
}
|
|
2893
|
+
for (const imp of pyImports) {
|
|
2894
|
+
const resolvedPaths = await resolvePyImport(imp, file.path, service.dir);
|
|
2895
|
+
for (const resolved of resolvedPaths) {
|
|
2896
|
+
edgesAdded += emitImportEdge(
|
|
2897
|
+
graph,
|
|
2898
|
+
service.pkg.name,
|
|
2899
|
+
importerFileId,
|
|
2900
|
+
relFile,
|
|
2901
|
+
resolved,
|
|
2902
|
+
imp.line,
|
|
2903
|
+
imp.snippet
|
|
2904
|
+
);
|
|
2905
|
+
}
|
|
2906
|
+
}
|
|
2907
|
+
continue;
|
|
2908
|
+
}
|
|
2909
|
+
let jsImports = [];
|
|
2910
|
+
try {
|
|
2911
|
+
const tree = parseSource(jsParser, file.content);
|
|
2912
|
+
collectJsImports(tree.rootNode, jsImports);
|
|
2913
|
+
} catch (err) {
|
|
2914
|
+
recordExtractionError("import extraction", file.path, err);
|
|
2915
|
+
continue;
|
|
2916
|
+
}
|
|
2917
|
+
for (const imp of jsImports) {
|
|
2918
|
+
const resolved = await resolveJsImport(imp.specifier, import_node_path6.default.dirname(file.path), service.dir, tsPaths);
|
|
2919
|
+
if (!resolved) continue;
|
|
2920
|
+
edgesAdded += emitImportEdge(
|
|
2921
|
+
graph,
|
|
2922
|
+
service.pkg.name,
|
|
2923
|
+
importerFileId,
|
|
2924
|
+
relFile,
|
|
2925
|
+
resolved,
|
|
2926
|
+
imp.line,
|
|
2927
|
+
imp.snippet
|
|
2928
|
+
);
|
|
2929
|
+
}
|
|
2930
|
+
}
|
|
2931
|
+
}
|
|
2932
|
+
return { nodesAdded: 0, edgesAdded };
|
|
2933
|
+
}
|
|
2934
|
+
|
|
2935
|
+
// src/extract/routes.ts
|
|
2936
|
+
var PARSE_CHUNK2 = 16384;
|
|
2937
|
+
function parseSource2(parser, source) {
|
|
2938
|
+
return parser.parse(
|
|
2939
|
+
(index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK2)
|
|
2940
|
+
);
|
|
2941
|
+
}
|
|
2942
|
+
function makeJsParser2() {
|
|
2943
|
+
const p = new import_tree_sitter2.default();
|
|
2944
|
+
p.setLanguage(import_tree_sitter_javascript2.default);
|
|
2945
|
+
return p;
|
|
2946
|
+
}
|
|
2947
|
+
function makePyParser2() {
|
|
2948
|
+
const p = new import_tree_sitter2.default();
|
|
2949
|
+
p.setLanguage(import_tree_sitter_python2.default);
|
|
2950
|
+
return p;
|
|
2951
|
+
}
|
|
2952
|
+
function makeGoParser2() {
|
|
2953
|
+
const p = new import_tree_sitter2.default();
|
|
2954
|
+
p.setLanguage(import_tree_sitter_go2.default);
|
|
2955
|
+
return p;
|
|
2956
|
+
}
|
|
2957
|
+
var ROUTER_METHODS = /* @__PURE__ */ new Set([
|
|
2958
|
+
"get",
|
|
2959
|
+
"post",
|
|
2960
|
+
"put",
|
|
2961
|
+
"patch",
|
|
2962
|
+
"delete",
|
|
2963
|
+
"options",
|
|
2964
|
+
"head",
|
|
2965
|
+
"all"
|
|
2966
|
+
]);
|
|
2967
|
+
var NEXT_APP_METHODS = /* @__PURE__ */ new Set(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"]);
|
|
2968
|
+
var JS_ROUTE_EXTENSIONS = /* @__PURE__ */ new Set([".js", ".jsx", ".mjs", ".cjs", ".ts", ".tsx"]);
|
|
2969
|
+
function ginRoutesFromSource(source, parser) {
|
|
2970
|
+
const tree = parseSource2(parser, source);
|
|
2971
|
+
const prefixes = /* @__PURE__ */ new Map();
|
|
2972
|
+
const out = [];
|
|
2973
|
+
walk(tree.rootNode, (node) => {
|
|
2974
|
+
if (node.type === "short_var_declaration" || node.type === "var_spec") {
|
|
2975
|
+
const name = node.childForFieldName("left")?.namedChild(0)?.text ?? node.childForFieldName("name")?.text;
|
|
2976
|
+
const value = node.childForFieldName("right")?.namedChild(0) ?? node.childForFieldName("value");
|
|
2977
|
+
if (name && value?.type === "call_expression") {
|
|
2978
|
+
const fn2 = value.childForFieldName("function");
|
|
2979
|
+
const field = fn2?.childForFieldName("field")?.text;
|
|
2980
|
+
const first2 = value.childForFieldName("arguments")?.namedChild(0);
|
|
2981
|
+
if (field === "Group" && first2?.type === "interpreted_string_literal") {
|
|
2982
|
+
prefixes.set(name, first2.text.slice(1, -1));
|
|
2983
|
+
}
|
|
2984
|
+
}
|
|
2985
|
+
return;
|
|
2986
|
+
}
|
|
2987
|
+
if (node.type !== "call_expression") return;
|
|
2988
|
+
const fn = node.childForFieldName("function");
|
|
2989
|
+
if (fn?.type !== "selector_expression") return;
|
|
2990
|
+
const method = fn.childForFieldName("field")?.text?.toUpperCase();
|
|
2991
|
+
if (!method || !ROUTER_METHODS.has(method.toLowerCase())) return;
|
|
2992
|
+
const receiver = fn.childForFieldName("operand")?.text ?? "";
|
|
2993
|
+
const first = node.childForFieldName("arguments")?.namedChild(0);
|
|
2994
|
+
if (first?.type !== "interpreted_string_literal") return;
|
|
2995
|
+
const leaf = first.text.slice(1, -1);
|
|
2996
|
+
out.push({
|
|
2997
|
+
method: method === "ALL" ? "ALL" : method,
|
|
2998
|
+
pathTemplate: canonicalizeTemplate((prefixes.get(receiver) ?? "") + leaf),
|
|
2999
|
+
line: node.startPosition.row + 1,
|
|
3000
|
+
framework: "gin"
|
|
3001
|
+
});
|
|
3002
|
+
});
|
|
3003
|
+
return out;
|
|
3004
|
+
}
|
|
3005
|
+
var FASTAPI_METHODS = /* @__PURE__ */ new Set(["get", "post", "put", "patch", "delete", "options", "head", "trace"]);
|
|
3006
|
+
var NESTJS_METHODS = /* @__PURE__ */ new Map([
|
|
3007
|
+
["Get", "GET"],
|
|
3008
|
+
["Post", "POST"],
|
|
3009
|
+
["Put", "PUT"],
|
|
3010
|
+
["Patch", "PATCH"],
|
|
3011
|
+
["Delete", "DELETE"],
|
|
3012
|
+
["Options", "OPTIONS"],
|
|
3013
|
+
["Head", "HEAD"],
|
|
3014
|
+
["All", "ALL"]
|
|
3015
|
+
]);
|
|
3016
|
+
function canonicalizeTemplate(raw) {
|
|
3017
|
+
let p = raw.split("?")[0].split("#")[0];
|
|
3018
|
+
if (!p.startsWith("/")) p = "/" + p;
|
|
3019
|
+
if (p.length > 1 && p.endsWith("/")) p = p.slice(0, -1);
|
|
3020
|
+
return p;
|
|
3021
|
+
}
|
|
3022
|
+
function isDynamicSegment(seg) {
|
|
3023
|
+
if (seg.length === 0) return false;
|
|
3024
|
+
if (seg.includes(":")) return true;
|
|
3025
|
+
if (seg.startsWith("{") || seg.startsWith("[")) return true;
|
|
3026
|
+
if (/^\d+$/.test(seg)) return true;
|
|
3027
|
+
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;
|
|
3028
|
+
if (/^[0-9a-f]{24,}$/i.test(seg)) return true;
|
|
3029
|
+
return false;
|
|
3030
|
+
}
|
|
3031
|
+
function normalizePathTemplate(raw) {
|
|
3032
|
+
const canonical = canonicalizeTemplate(raw);
|
|
3033
|
+
const segments = canonical.split("/").filter((s) => s.length > 0);
|
|
3034
|
+
const normalised = segments.map((seg) => isDynamicSegment(seg) ? ":param" : seg.toLowerCase());
|
|
3035
|
+
return "/" + normalised.join("/");
|
|
3036
|
+
}
|
|
3037
|
+
function walk(node, visit) {
|
|
3038
|
+
visit(node);
|
|
3039
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
3040
|
+
const child = node.namedChild(i);
|
|
3041
|
+
if (child) walk(child, visit);
|
|
3042
|
+
}
|
|
3043
|
+
}
|
|
3044
|
+
function staticStringText(node) {
|
|
3045
|
+
if (node.type === "string") {
|
|
3046
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
3047
|
+
const child = node.namedChild(i);
|
|
3048
|
+
if (child?.type === "string_fragment") return child.text;
|
|
3049
|
+
}
|
|
3050
|
+
return "";
|
|
3051
|
+
}
|
|
3052
|
+
if (node.type === "template_string") {
|
|
3053
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
3054
|
+
if (node.namedChild(i)?.type === "template_substitution") return null;
|
|
3055
|
+
}
|
|
3056
|
+
const raw = node.text;
|
|
3057
|
+
return raw.length >= 2 ? raw.slice(1, -1) : "";
|
|
3058
|
+
}
|
|
3059
|
+
return null;
|
|
3060
|
+
}
|
|
3061
|
+
function objectStringProp(objNode, key) {
|
|
3062
|
+
for (let i = 0; i < objNode.namedChildCount; i++) {
|
|
3063
|
+
const pair = objNode.namedChild(i);
|
|
3064
|
+
if (!pair || pair.type !== "pair") continue;
|
|
3065
|
+
const k = pair.childForFieldName("key");
|
|
3066
|
+
if (!k) continue;
|
|
3067
|
+
const kText = k.type === "string" ? staticStringText(k) : k.text;
|
|
3068
|
+
if (kText !== key) continue;
|
|
3069
|
+
const v = pair.childForFieldName("value");
|
|
3070
|
+
if (v) return staticStringText(v);
|
|
3071
|
+
}
|
|
3072
|
+
return null;
|
|
3073
|
+
}
|
|
3074
|
+
function fastifyRouteMethods(objNode) {
|
|
3075
|
+
for (let i = 0; i < objNode.namedChildCount; i++) {
|
|
3076
|
+
const pair = objNode.namedChild(i);
|
|
3077
|
+
if (!pair || pair.type !== "pair") continue;
|
|
3078
|
+
const k = pair.childForFieldName("key");
|
|
3079
|
+
const kText = k ? k.type === "string" ? staticStringText(k) : k.text : null;
|
|
3080
|
+
if (kText !== "method") continue;
|
|
3081
|
+
const v = pair.childForFieldName("value");
|
|
3082
|
+
if (!v) return [];
|
|
3083
|
+
if (v.type === "string" || v.type === "template_string") {
|
|
3084
|
+
const s = staticStringText(v);
|
|
3085
|
+
return s ? [s.toUpperCase()] : [];
|
|
3086
|
+
}
|
|
3087
|
+
if (v.type === "array") {
|
|
3088
|
+
const out = [];
|
|
3089
|
+
for (let j = 0; j < v.namedChildCount; j++) {
|
|
3090
|
+
const el = v.namedChild(j);
|
|
3091
|
+
if (el && (el.type === "string" || el.type === "template_string")) {
|
|
3092
|
+
const s = staticStringText(el);
|
|
3093
|
+
if (s) out.push(s.toUpperCase());
|
|
3094
|
+
}
|
|
3095
|
+
}
|
|
3096
|
+
return out;
|
|
3097
|
+
}
|
|
3098
|
+
}
|
|
3099
|
+
return [];
|
|
3100
|
+
}
|
|
3101
|
+
function nestDecoratorImports(root) {
|
|
3102
|
+
const imports = /* @__PURE__ */ new Map();
|
|
3103
|
+
walk(root, (node) => {
|
|
3104
|
+
if (node.type !== "import_statement") return;
|
|
3105
|
+
const source = node.childForFieldName("source");
|
|
2721
3106
|
if (!source || staticStringText(source) !== "@nestjs/common") return;
|
|
2722
3107
|
walk(node, (child) => {
|
|
2723
3108
|
if (child.type !== "import_specifier") return;
|
|
@@ -2763,7 +3148,7 @@ function nestJoinedPath(prefix, leaf) {
|
|
|
2763
3148
|
return canonicalizeTemplate(segments.join("/"));
|
|
2764
3149
|
}
|
|
2765
3150
|
function nestjsRoutesFromSource(source, parser) {
|
|
2766
|
-
const tree =
|
|
3151
|
+
const tree = parseSource2(parser, source);
|
|
2767
3152
|
const imports = nestDecoratorImports(tree.rootNode);
|
|
2768
3153
|
if (![...imports.values()].includes("Controller")) return [];
|
|
2769
3154
|
const out = [];
|
|
@@ -2811,7 +3196,7 @@ function nestjsRoutesFromSource(source, parser) {
|
|
|
2811
3196
|
return out;
|
|
2812
3197
|
}
|
|
2813
3198
|
function serverRoutesFromSource(source, parser, hasExpress, hasFastify, hasHono = false) {
|
|
2814
|
-
const tree =
|
|
3199
|
+
const tree = parseSource2(parser, source);
|
|
2815
3200
|
const out = [];
|
|
2816
3201
|
const framework = hasExpress ? "express" : hasFastify ? "fastify" : hasHono ? "hono" : "unknown";
|
|
2817
3202
|
walk(tree.rootNode, (node) => {
|
|
@@ -2869,7 +3254,7 @@ function isNextPagesApiFile(relFile) {
|
|
|
2869
3254
|
if (pagesIdx === -1 || segs[pagesIdx + 1] !== "api") return false;
|
|
2870
3255
|
const base = segs[segs.length - 1] ?? "";
|
|
2871
3256
|
if (/^_(app|document|middleware)\./.test(base)) return false;
|
|
2872
|
-
return JS_ROUTE_EXTENSIONS.has(
|
|
3257
|
+
return JS_ROUTE_EXTENSIONS.has(import_node_path7.default.extname(base));
|
|
2873
3258
|
}
|
|
2874
3259
|
function nextSegment(seg) {
|
|
2875
3260
|
if (seg.startsWith("(") && seg.endsWith(")")) return null;
|
|
@@ -2931,7 +3316,7 @@ function nextAppMethods(root) {
|
|
|
2931
3316
|
}
|
|
2932
3317
|
function nextRoutesFromFile(source, relFile, parser) {
|
|
2933
3318
|
if (isNextAppRouteFile(relFile)) {
|
|
2934
|
-
const tree =
|
|
3319
|
+
const tree = parseSource2(parser, source);
|
|
2935
3320
|
const template = nextAppPathTemplate(relFile);
|
|
2936
3321
|
return nextAppMethods(tree.rootNode).map(({ method, line }) => ({
|
|
2937
3322
|
method,
|
|
@@ -3052,7 +3437,7 @@ function collectMountPrefixes(root, consts) {
|
|
|
3052
3437
|
return mounts;
|
|
3053
3438
|
}
|
|
3054
3439
|
function pythonRoutesFromSource(source, parser, framework) {
|
|
3055
|
-
const tree =
|
|
3440
|
+
const tree = parseSource2(parser, source);
|
|
3056
3441
|
const prefixes = collectPythonRouterPrefixes(tree.rootNode);
|
|
3057
3442
|
const consts = collectStringConstants(tree.rootNode);
|
|
3058
3443
|
const mounts = collectMountPrefixes(tree.rootNode, consts);
|
|
@@ -3092,7 +3477,7 @@ function pythonRoutesFromSource(source, parser, framework) {
|
|
|
3092
3477
|
return out;
|
|
3093
3478
|
}
|
|
3094
3479
|
function djangoRoutesFromSource(source, parser) {
|
|
3095
|
-
const tree =
|
|
3480
|
+
const tree = parseSource2(parser, source);
|
|
3096
3481
|
const out = [];
|
|
3097
3482
|
walk(tree.rootNode, (node) => {
|
|
3098
3483
|
if (node.type !== "assignment") return;
|
|
@@ -3120,10 +3505,320 @@ function djangoRoutesFromSource(source, parser) {
|
|
|
3120
3505
|
});
|
|
3121
3506
|
return out;
|
|
3122
3507
|
}
|
|
3508
|
+
function namedArgs(argsNode) {
|
|
3509
|
+
const out = [];
|
|
3510
|
+
if (!argsNode) return out;
|
|
3511
|
+
for (let i = 0; i < argsNode.namedChildCount; i++) {
|
|
3512
|
+
const c = argsNode.namedChild(i);
|
|
3513
|
+
if (c && c.type !== "comment") out.push(c);
|
|
3514
|
+
}
|
|
3515
|
+
return out;
|
|
3516
|
+
}
|
|
3517
|
+
function parseUseMount(callNode) {
|
|
3518
|
+
const args = namedArgs(callNode.childForFieldName("arguments"));
|
|
3519
|
+
if (args.length === 0) return null;
|
|
3520
|
+
const first = args[0];
|
|
3521
|
+
const firstStr = first.type === "string" || first.type === "template_string" ? staticStringText(first) : null;
|
|
3522
|
+
if (firstStr !== null && firstStr.startsWith("/")) {
|
|
3523
|
+
const prefix = canonicalizeTemplate(firstStr);
|
|
3524
|
+
const second = args[1];
|
|
3525
|
+
const target = second && second.type === "identifier" ? second.text : null;
|
|
3526
|
+
return { prefix: prefix === "/" ? "" : prefix, target };
|
|
3527
|
+
}
|
|
3528
|
+
if (args.length === 1 && first.type === "identifier") return { prefix: "", target: first.text };
|
|
3529
|
+
return null;
|
|
3530
|
+
}
|
|
3531
|
+
function unwrapRouterExpr(node, expressLocals, routerCtors) {
|
|
3532
|
+
if (node.type === "identifier") return { base: { alias: node.text }, mounts: [] };
|
|
3533
|
+
if (node.type !== "call_expression") return null;
|
|
3534
|
+
const fn = node.childForFieldName("function");
|
|
3535
|
+
if (!fn) return null;
|
|
3536
|
+
if (fn.type === "member_expression") {
|
|
3537
|
+
const prop = fn.childForFieldName("property")?.text;
|
|
3538
|
+
const obj = fn.childForFieldName("object");
|
|
3539
|
+
if (!prop || !obj) return null;
|
|
3540
|
+
if (prop === "use") {
|
|
3541
|
+
const inner = unwrapRouterExpr(obj, expressLocals, routerCtors);
|
|
3542
|
+
if (!inner) return null;
|
|
3543
|
+
const mount = parseUseMount(node);
|
|
3544
|
+
return { base: inner.base, mounts: mount ? [...inner.mounts, mount] : inner.mounts };
|
|
3545
|
+
}
|
|
3546
|
+
if (prop === "Router" && obj.type === "identifier" && expressLocals.has(obj.text)) {
|
|
3547
|
+
return { base: "newRouter", mounts: [] };
|
|
3548
|
+
}
|
|
3549
|
+
return null;
|
|
3550
|
+
}
|
|
3551
|
+
if (fn.type === "identifier") {
|
|
3552
|
+
if (expressLocals.has(fn.text)) return { base: "app", mounts: [] };
|
|
3553
|
+
if (routerCtors.has(fn.text)) return { base: "newRouter", mounts: [] };
|
|
3554
|
+
}
|
|
3555
|
+
return null;
|
|
3556
|
+
}
|
|
3557
|
+
function collectExpressImports(root) {
|
|
3558
|
+
const expressLocals = /* @__PURE__ */ new Set();
|
|
3559
|
+
const routerCtors = /* @__PURE__ */ new Set();
|
|
3560
|
+
const bindings = [];
|
|
3561
|
+
const addFromExpress = (local, sel, exported) => {
|
|
3562
|
+
if (sel === "default" || sel === "namespace") expressLocals.add(local);
|
|
3563
|
+
else if (exported === "Router") routerCtors.add(local);
|
|
3564
|
+
};
|
|
3565
|
+
walk(root, (node) => {
|
|
3566
|
+
if (node.type === "import_statement") {
|
|
3567
|
+
const source = node.childForFieldName("source");
|
|
3568
|
+
const spec = source ? staticStringText(source) : null;
|
|
3569
|
+
if (!spec) return;
|
|
3570
|
+
let clause = null;
|
|
3571
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
3572
|
+
const c = node.namedChild(i);
|
|
3573
|
+
if (c?.type === "import_clause") clause = c;
|
|
3574
|
+
}
|
|
3575
|
+
if (!clause) return;
|
|
3576
|
+
for (let i = 0; i < clause.namedChildCount; i++) {
|
|
3577
|
+
const c = clause.namedChild(i);
|
|
3578
|
+
if (!c) continue;
|
|
3579
|
+
if (c.type === "identifier") {
|
|
3580
|
+
if (spec === "express") addFromExpress(c.text, "default", "default");
|
|
3581
|
+
else bindings.push({ local: c.text, specifier: spec, sel: "default" });
|
|
3582
|
+
} else if (c.type === "namespace_import") {
|
|
3583
|
+
const id = c.namedChild(0);
|
|
3584
|
+
if (id?.type === "identifier") {
|
|
3585
|
+
if (spec === "express") addFromExpress(id.text, "namespace", "namespace");
|
|
3586
|
+
else bindings.push({ local: id.text, specifier: spec, sel: "namespace" });
|
|
3587
|
+
}
|
|
3588
|
+
} else if (c.type === "named_imports") {
|
|
3589
|
+
for (let j = 0; j < c.namedChildCount; j++) {
|
|
3590
|
+
const s = c.namedChild(j);
|
|
3591
|
+
if (s?.type !== "import_specifier") continue;
|
|
3592
|
+
const name = s.childForFieldName("name")?.text;
|
|
3593
|
+
if (!name) continue;
|
|
3594
|
+
const local = s.childForFieldName("alias")?.text ?? name;
|
|
3595
|
+
if (spec === "express") addFromExpress(local, name, name);
|
|
3596
|
+
else bindings.push({ local, specifier: spec, sel: name });
|
|
3597
|
+
}
|
|
3598
|
+
}
|
|
3599
|
+
}
|
|
3600
|
+
return;
|
|
3601
|
+
}
|
|
3602
|
+
if (node.type === "variable_declarator") {
|
|
3603
|
+
const value = node.childForFieldName("value");
|
|
3604
|
+
if (value?.type !== "call_expression") return;
|
|
3605
|
+
const fn = value.childForFieldName("function");
|
|
3606
|
+
if (fn?.type !== "identifier" || fn.text !== "require") return;
|
|
3607
|
+
const arg = namedArgs(value.childForFieldName("arguments"))[0];
|
|
3608
|
+
const spec = arg ? staticStringText(arg) : null;
|
|
3609
|
+
if (!spec) return;
|
|
3610
|
+
const name = node.childForFieldName("name");
|
|
3611
|
+
if (name?.type === "identifier") {
|
|
3612
|
+
if (spec === "express") expressLocals.add(name.text);
|
|
3613
|
+
else bindings.push({ local: name.text, specifier: spec, sel: "default" });
|
|
3614
|
+
} else if (name?.type === "object_pattern") {
|
|
3615
|
+
for (let i = 0; i < name.namedChildCount; i++) {
|
|
3616
|
+
const el = name.namedChild(i);
|
|
3617
|
+
if (!el) continue;
|
|
3618
|
+
let local;
|
|
3619
|
+
let exported;
|
|
3620
|
+
if (el.type === "shorthand_property_identifier_pattern") {
|
|
3621
|
+
local = el.text;
|
|
3622
|
+
exported = el.text;
|
|
3623
|
+
} else if (el.type === "pair_pattern") {
|
|
3624
|
+
exported = el.childForFieldName("key")?.text;
|
|
3625
|
+
local = el.childForFieldName("value")?.text ?? exported;
|
|
3626
|
+
}
|
|
3627
|
+
if (!local || !exported) continue;
|
|
3628
|
+
if (spec === "express") addFromExpress(local, exported, exported);
|
|
3629
|
+
else bindings.push({ local, specifier: spec, sel: exported });
|
|
3630
|
+
}
|
|
3631
|
+
}
|
|
3632
|
+
}
|
|
3633
|
+
});
|
|
3634
|
+
return { expressLocals, routerCtors, bindings };
|
|
3635
|
+
}
|
|
3636
|
+
function analyzeExpressFile(root, dir) {
|
|
3637
|
+
const { expressLocals, routerCtors, bindings } = collectExpressImports(root);
|
|
3638
|
+
const routerVars = /* @__PURE__ */ new Map();
|
|
3639
|
+
const appVars = /* @__PURE__ */ new Set();
|
|
3640
|
+
const exportNamed = /* @__PURE__ */ new Map();
|
|
3641
|
+
let exportDefaultName = null;
|
|
3642
|
+
const getVar = (name) => {
|
|
3643
|
+
let rv = routerVars.get(name);
|
|
3644
|
+
if (!rv) {
|
|
3645
|
+
rv = { declares: false, mounts: [] };
|
|
3646
|
+
routerVars.set(name, rv);
|
|
3647
|
+
}
|
|
3648
|
+
return rv;
|
|
3649
|
+
};
|
|
3650
|
+
const refFromExpr = (expr, key) => {
|
|
3651
|
+
if (expr.type === "identifier") return expr.text;
|
|
3652
|
+
const u = unwrapRouterExpr(expr, expressLocals, routerCtors);
|
|
3653
|
+
if (!u) return null;
|
|
3654
|
+
const rv = getVar(key);
|
|
3655
|
+
for (const m of u.mounts) rv.mounts.push(m);
|
|
3656
|
+
if (typeof u.base === "object") rv.aliasOf = u.base.alias;
|
|
3657
|
+
return key;
|
|
3658
|
+
};
|
|
3659
|
+
const isExported = (declarator) => {
|
|
3660
|
+
const decl = declarator.parent;
|
|
3661
|
+
return decl?.parent?.type === "export_statement";
|
|
3662
|
+
};
|
|
3663
|
+
walk(root, (node) => {
|
|
3664
|
+
if (node.type === "variable_declarator") {
|
|
3665
|
+
const value = node.childForFieldName("value");
|
|
3666
|
+
const name = node.childForFieldName("name");
|
|
3667
|
+
if (name?.type !== "identifier" || !value) return;
|
|
3668
|
+
if (value.type === "call_expression") {
|
|
3669
|
+
const fn = value.childForFieldName("function");
|
|
3670
|
+
if (fn?.type === "identifier" && fn.text === "require") return;
|
|
3671
|
+
}
|
|
3672
|
+
const u = unwrapRouterExpr(value, expressLocals, routerCtors);
|
|
3673
|
+
if (!u) return;
|
|
3674
|
+
const rv = getVar(name.text);
|
|
3675
|
+
for (const m of u.mounts) rv.mounts.push(m);
|
|
3676
|
+
if (u.base === "app") appVars.add(name.text);
|
|
3677
|
+
else if (typeof u.base === "object") rv.aliasOf = u.base.alias;
|
|
3678
|
+
if (isExported(node)) exportNamed.set(name.text, name.text);
|
|
3679
|
+
return;
|
|
3680
|
+
}
|
|
3681
|
+
if (node.type === "call_expression") {
|
|
3682
|
+
const fn = node.childForFieldName("function");
|
|
3683
|
+
if (fn?.type !== "member_expression") return;
|
|
3684
|
+
const obj = fn.childForFieldName("object");
|
|
3685
|
+
const prop = fn.childForFieldName("property")?.text;
|
|
3686
|
+
if (obj?.type !== "identifier" || !prop) return;
|
|
3687
|
+
if (prop === "use") {
|
|
3688
|
+
const m = parseUseMount(node);
|
|
3689
|
+
if (m) getVar(obj.text).mounts.push(m);
|
|
3690
|
+
} else if (ROUTER_METHODS.has(prop.toLowerCase())) {
|
|
3691
|
+
const first = namedArgs(node.childForFieldName("arguments"))[0];
|
|
3692
|
+
const p = first ? staticStringText(first) : null;
|
|
3693
|
+
if (p !== null && p.startsWith("/")) getVar(obj.text).declares = true;
|
|
3694
|
+
}
|
|
3695
|
+
return;
|
|
3696
|
+
}
|
|
3697
|
+
if (node.type === "export_statement") {
|
|
3698
|
+
let clause = null;
|
|
3699
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
3700
|
+
const c = node.namedChild(i);
|
|
3701
|
+
if (c?.type === "export_clause") clause = c;
|
|
3702
|
+
}
|
|
3703
|
+
if (clause) {
|
|
3704
|
+
for (let i = 0; i < clause.namedChildCount; i++) {
|
|
3705
|
+
const spec = clause.namedChild(i);
|
|
3706
|
+
if (spec?.type !== "export_specifier") continue;
|
|
3707
|
+
const local = spec.childForFieldName("name")?.text;
|
|
3708
|
+
if (!local) continue;
|
|
3709
|
+
const exportedAs = spec.childForFieldName("alias")?.text ?? local;
|
|
3710
|
+
if (exportedAs === "default") exportDefaultName = local;
|
|
3711
|
+
else exportNamed.set(exportedAs, local);
|
|
3712
|
+
}
|
|
3713
|
+
return;
|
|
3714
|
+
}
|
|
3715
|
+
if (node.childForFieldName("declaration")) return;
|
|
3716
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
3717
|
+
const c = node.namedChild(i);
|
|
3718
|
+
if (c && c.type !== "export_clause") {
|
|
3719
|
+
exportDefaultName = refFromExpr(c, "#default");
|
|
3720
|
+
break;
|
|
3721
|
+
}
|
|
3722
|
+
}
|
|
3723
|
+
return;
|
|
3724
|
+
}
|
|
3725
|
+
if (node.type === "assignment_expression") {
|
|
3726
|
+
const left = node.childForFieldName("left");
|
|
3727
|
+
const right = node.childForFieldName("right");
|
|
3728
|
+
if (left?.type !== "member_expression" || !right) return;
|
|
3729
|
+
const lobj = left.childForFieldName("object")?.text;
|
|
3730
|
+
const lprop = left.childForFieldName("property")?.text;
|
|
3731
|
+
if (lobj === "module" && lprop === "exports") exportDefaultName = refFromExpr(right, "#default");
|
|
3732
|
+
else if (lobj === "exports" && lprop) {
|
|
3733
|
+
const key = refFromExpr(right, `#exp:${lprop}`);
|
|
3734
|
+
if (key) exportNamed.set(lprop, key);
|
|
3735
|
+
}
|
|
3736
|
+
}
|
|
3737
|
+
});
|
|
3738
|
+
return {
|
|
3739
|
+
dir,
|
|
3740
|
+
routerVars,
|
|
3741
|
+
appVars,
|
|
3742
|
+
exportDefaultName,
|
|
3743
|
+
exportNamed,
|
|
3744
|
+
rawBindings: bindings,
|
|
3745
|
+
importedRouters: /* @__PURE__ */ new Map()
|
|
3746
|
+
};
|
|
3747
|
+
}
|
|
3748
|
+
async function expressMountPrefixes(files, serviceDir, tsPaths) {
|
|
3749
|
+
const jsParser = makeJsParser2();
|
|
3750
|
+
const fileInfo = /* @__PURE__ */ new Map();
|
|
3751
|
+
for (const f of files) {
|
|
3752
|
+
if (!JS_ROUTE_EXTENSIONS.has(import_node_path7.default.extname(f.path))) continue;
|
|
3753
|
+
if (isTestPath(f.path)) continue;
|
|
3754
|
+
const rel = toPosix(import_node_path7.default.relative(serviceDir, f.path));
|
|
3755
|
+
try {
|
|
3756
|
+
const tree = parseSource2(jsParser, f.content);
|
|
3757
|
+
fileInfo.set(rel, analyzeExpressFile(tree.rootNode, import_node_path7.default.dirname(f.path)));
|
|
3758
|
+
} catch {
|
|
3759
|
+
}
|
|
3760
|
+
}
|
|
3761
|
+
if (fileInfo.size === 0) return /* @__PURE__ */ new Map();
|
|
3762
|
+
for (const info of fileInfo.values()) {
|
|
3763
|
+
for (const b of info.rawBindings) {
|
|
3764
|
+
const resolved = await resolveJsImport(b.specifier, info.dir, serviceDir, tsPaths);
|
|
3765
|
+
if (!resolved || !fileInfo.has(resolved)) continue;
|
|
3766
|
+
info.importedRouters.set(b.local, { file: resolved, sel: b.sel === "namespace" ? "default" : b.sel });
|
|
3767
|
+
}
|
|
3768
|
+
}
|
|
3769
|
+
const resolveTarget = (name, file) => {
|
|
3770
|
+
const info = fileInfo.get(file);
|
|
3771
|
+
if (!info) return null;
|
|
3772
|
+
if (info.routerVars.has(name)) return { file, name };
|
|
3773
|
+
const imp = info.importedRouters.get(name);
|
|
3774
|
+
if (!imp) return null;
|
|
3775
|
+
const target = fileInfo.get(imp.file);
|
|
3776
|
+
if (!target) return null;
|
|
3777
|
+
const key = imp.sel === "default" ? target.exportDefaultName : target.exportNamed.get(imp.sel);
|
|
3778
|
+
if (!key) return null;
|
|
3779
|
+
return { file: imp.file, name: key };
|
|
3780
|
+
};
|
|
3781
|
+
const filePrefix = /* @__PURE__ */ new Map();
|
|
3782
|
+
const conflicted = /* @__PURE__ */ new Set();
|
|
3783
|
+
const apply = (file, prefix) => {
|
|
3784
|
+
if (conflicted.has(file)) return;
|
|
3785
|
+
const existing = filePrefix.get(file);
|
|
3786
|
+
if (existing === void 0) filePrefix.set(file, prefix);
|
|
3787
|
+
else if (existing !== prefix) {
|
|
3788
|
+
filePrefix.delete(file);
|
|
3789
|
+
conflicted.add(file);
|
|
3790
|
+
}
|
|
3791
|
+
};
|
|
3792
|
+
const visited = /* @__PURE__ */ new Set();
|
|
3793
|
+
const collect = (file, name, accPrefix) => {
|
|
3794
|
+
const key = `${file}|${name}|${accPrefix}`;
|
|
3795
|
+
if (visited.has(key)) return;
|
|
3796
|
+
visited.add(key);
|
|
3797
|
+
const info = fileInfo.get(file);
|
|
3798
|
+
const rv = info?.routerVars.get(name);
|
|
3799
|
+
if (!info || !rv) return;
|
|
3800
|
+
if (rv.declares && info.appVars.size === 0) apply(file, accPrefix);
|
|
3801
|
+
for (const m of rv.mounts) {
|
|
3802
|
+
if (!m.target) continue;
|
|
3803
|
+
const t = resolveTarget(m.target, file);
|
|
3804
|
+
if (t) collect(t.file, t.name, accPrefix + m.prefix);
|
|
3805
|
+
}
|
|
3806
|
+
if (rv.aliasOf) {
|
|
3807
|
+
const t = resolveTarget(rv.aliasOf, file);
|
|
3808
|
+
if (t) collect(t.file, t.name, accPrefix);
|
|
3809
|
+
}
|
|
3810
|
+
};
|
|
3811
|
+
for (const [rel, info] of fileInfo) {
|
|
3812
|
+
for (const appVar of info.appVars) collect(rel, appVar, "");
|
|
3813
|
+
}
|
|
3814
|
+
const out = /* @__PURE__ */ new Map();
|
|
3815
|
+
for (const [file, prefix] of filePrefix) if (prefix && prefix !== "/") out.set(file, prefix);
|
|
3816
|
+
return out;
|
|
3817
|
+
}
|
|
3123
3818
|
async function addRoutes(graph, services) {
|
|
3124
|
-
const jsParser =
|
|
3125
|
-
const pyParser =
|
|
3126
|
-
const goParser =
|
|
3819
|
+
const jsParser = makeJsParser2();
|
|
3820
|
+
const pyParser = makePyParser2();
|
|
3821
|
+
const goParser = makeGoParser2();
|
|
3127
3822
|
let nodesAdded = 0;
|
|
3128
3823
|
let edgesAdded = 0;
|
|
3129
3824
|
for (const service of services) {
|
|
@@ -3143,13 +3838,14 @@ async function addRoutes(graph, services) {
|
|
|
3143
3838
|
if (!hasExpress && !hasFastify && !hasHono && !hasNext && !hasNestjs && !hasFastapi && !hasFlask && !hasDjango && !hasGin)
|
|
3144
3839
|
continue;
|
|
3145
3840
|
const files = await loadSourceFiles(service.dir);
|
|
3841
|
+
const mountPrefixes = hasExpress ? await expressMountPrefixes(files, service.dir, await loadTsPathConfig(service.dir)) : /* @__PURE__ */ new Map();
|
|
3146
3842
|
for (const file of files) {
|
|
3147
3843
|
if (isTestPath(file.path)) continue;
|
|
3148
|
-
const ext =
|
|
3844
|
+
const ext = import_node_path7.default.extname(file.path);
|
|
3149
3845
|
const isPy = ext === ".py";
|
|
3150
3846
|
const isGo = ext === ".go";
|
|
3151
3847
|
if (!JS_ROUTE_EXTENSIONS.has(ext) && !isPy && !isGo) continue;
|
|
3152
|
-
const relFile = toPosix(
|
|
3848
|
+
const relFile = toPosix(import_node_path7.default.relative(service.dir, file.path));
|
|
3153
3849
|
let routes;
|
|
3154
3850
|
try {
|
|
3155
3851
|
if (isGo) {
|
|
@@ -3171,16 +3867,18 @@ async function addRoutes(graph, services) {
|
|
|
3171
3867
|
continue;
|
|
3172
3868
|
}
|
|
3173
3869
|
if (routes.length === 0) continue;
|
|
3870
|
+
const mountPrefix = mountPrefixes.get(relFile);
|
|
3174
3871
|
for (const route of routes) {
|
|
3175
|
-
const
|
|
3872
|
+
const pathTemplate = mountPrefix ? canonicalizeTemplate(mountPrefix + route.pathTemplate) : route.pathTemplate;
|
|
3873
|
+
const rid = (0, import_types6.routeId)(service.pkg.name, route.method, pathTemplate);
|
|
3176
3874
|
if (!graph.hasNode(rid)) {
|
|
3177
3875
|
const node = {
|
|
3178
3876
|
id: rid,
|
|
3179
|
-
type:
|
|
3180
|
-
name: `${route.method} ${
|
|
3877
|
+
type: import_types6.NodeType.RouteNode,
|
|
3878
|
+
name: `${route.method} ${pathTemplate}`,
|
|
3181
3879
|
service: service.pkg.name,
|
|
3182
3880
|
method: route.method,
|
|
3183
|
-
pathTemplate
|
|
3881
|
+
pathTemplate,
|
|
3184
3882
|
path: relFile,
|
|
3185
3883
|
line: route.line,
|
|
3186
3884
|
framework: route.framework,
|
|
@@ -3189,15 +3887,15 @@ async function addRoutes(graph, services) {
|
|
|
3189
3887
|
graph.addNode(rid, node);
|
|
3190
3888
|
nodesAdded++;
|
|
3191
3889
|
}
|
|
3192
|
-
const containsId = (0,
|
|
3890
|
+
const containsId = (0, import_types6.extractedEdgeId)(service.node.id, rid, import_types6.EdgeType.CONTAINS);
|
|
3193
3891
|
if (!graph.hasEdge(containsId)) {
|
|
3194
3892
|
const edge = {
|
|
3195
3893
|
id: containsId,
|
|
3196
3894
|
source: service.node.id,
|
|
3197
3895
|
target: rid,
|
|
3198
|
-
type:
|
|
3199
|
-
provenance:
|
|
3200
|
-
confidence: (0,
|
|
3896
|
+
type: import_types6.EdgeType.CONTAINS,
|
|
3897
|
+
provenance: import_types6.Provenance.EXTRACTED,
|
|
3898
|
+
confidence: (0, import_types6.confidenceForExtracted)("structural"),
|
|
3201
3899
|
evidence: {
|
|
3202
3900
|
file: relFile,
|
|
3203
3901
|
line: route.line,
|
|
@@ -3215,7 +3913,7 @@ async function addRoutes(graph, services) {
|
|
|
3215
3913
|
|
|
3216
3914
|
// src/columns.ts
|
|
3217
3915
|
init_cjs_shims();
|
|
3218
|
-
var
|
|
3916
|
+
var import_types7 = require("@neat.is/types");
|
|
3219
3917
|
var OBSERVED_COLUMN_CONFIDENCE = 0.9;
|
|
3220
3918
|
function normalizeProvenances(provenances) {
|
|
3221
3919
|
return [...new Set(provenances)].sort();
|
|
@@ -3243,10 +3941,10 @@ function foldColumns(existing, names, provenance, confidence) {
|
|
|
3243
3941
|
return out;
|
|
3244
3942
|
}
|
|
3245
3943
|
function columnIsDeclared(col) {
|
|
3246
|
-
return col.provenances.includes(
|
|
3944
|
+
return col.provenances.includes(import_types7.Provenance.EXTRACTED);
|
|
3247
3945
|
}
|
|
3248
3946
|
function columnIsObserved(col) {
|
|
3249
|
-
return col.provenances.includes(
|
|
3947
|
+
return col.provenances.includes(import_types7.Provenance.OBSERVED);
|
|
3250
3948
|
}
|
|
3251
3949
|
|
|
3252
3950
|
// src/ingest.ts
|
|
@@ -3469,7 +4167,7 @@ function languageForExt(relPath) {
|
|
|
3469
4167
|
function relPathForRuntimeFile(filepath, serviceNode, scanPath) {
|
|
3470
4168
|
let p = toPosix2(filepath).replace(/^file:\/\//, "");
|
|
3471
4169
|
if (scanPath && scanPath.length > 0) {
|
|
3472
|
-
const absRoot = toPosix2(
|
|
4170
|
+
const absRoot = toPosix2(import_node_path8.default.resolve(scanPath, serviceNode?.repoPath ?? ""));
|
|
3473
4171
|
const anchor = absRoot.endsWith("/") ? absRoot : `${absRoot}/`;
|
|
3474
4172
|
if (p.startsWith(anchor)) return p.slice(anchor.length);
|
|
3475
4173
|
}
|
|
@@ -3497,10 +4195,10 @@ function resolveDistToSrc(absFilepath, line) {
|
|
|
3497
4195
|
entry = null;
|
|
3498
4196
|
const mapPath = `${absFilepath}.map`;
|
|
3499
4197
|
try {
|
|
3500
|
-
if ((0,
|
|
3501
|
-
const raw = JSON.parse((0,
|
|
4198
|
+
if ((0, import_node_fs7.existsSync)(mapPath)) {
|
|
4199
|
+
const raw = JSON.parse((0, import_node_fs7.readFileSync)(mapPath, "utf8"));
|
|
3502
4200
|
const consumer = new sourceMapJs.SourceMapConsumer(raw);
|
|
3503
|
-
entry = { consumer, dir:
|
|
4201
|
+
entry = { consumer, dir: import_node_path8.default.dirname(mapPath) };
|
|
3504
4202
|
}
|
|
3505
4203
|
} catch {
|
|
3506
4204
|
entry = null;
|
|
@@ -3515,7 +4213,7 @@ function resolveDistToSrc(absFilepath, line) {
|
|
|
3515
4213
|
});
|
|
3516
4214
|
if (!pos || !pos.source) return null;
|
|
3517
4215
|
const root = entry.consumer.sourceRoot ?? "";
|
|
3518
|
-
const resolved =
|
|
4216
|
+
const resolved = import_node_path8.default.resolve(entry.dir, root, pos.source);
|
|
3519
4217
|
return { filepath: resolved, ...pos.line ? { line: pos.line } : {} };
|
|
3520
4218
|
} catch {
|
|
3521
4219
|
return null;
|
|
@@ -3548,11 +4246,11 @@ function callSiteFromSpan(span, serviceNode, scanPath) {
|
|
|
3548
4246
|
};
|
|
3549
4247
|
}
|
|
3550
4248
|
function reconcileObservedRelPath(graph, serviceName, relPath) {
|
|
3551
|
-
if (graph.hasNode((0,
|
|
4249
|
+
if (graph.hasNode((0, import_types8.fileId)(serviceName, relPath))) return relPath;
|
|
3552
4250
|
let best = null;
|
|
3553
4251
|
graph.forEachNode((_id, attrs) => {
|
|
3554
4252
|
const a = attrs;
|
|
3555
|
-
if (a.type !==
|
|
4253
|
+
if (a.type !== import_types8.NodeType.FileNode || a.service !== serviceName) return;
|
|
3556
4254
|
if (a.discoveredVia === "otel") return;
|
|
3557
4255
|
const p = a.path;
|
|
3558
4256
|
if (!p) return;
|
|
@@ -3564,14 +4262,14 @@ function reconcileObservedRelPath(graph, serviceName, relPath) {
|
|
|
3564
4262
|
}
|
|
3565
4263
|
function ensureObservedFileNode(graph, serviceName, serviceNodeId, callSite) {
|
|
3566
4264
|
const svcAttrs = graph.hasNode(serviceNodeId) ? graph.getNodeAttributes(serviceNodeId) : void 0;
|
|
3567
|
-
const canonicalService = svcAttrs && svcAttrs.type ===
|
|
4265
|
+
const canonicalService = svcAttrs && svcAttrs.type === import_types8.NodeType.ServiceNode && typeof svcAttrs.name === "string" ? svcAttrs.name : serviceName;
|
|
3568
4266
|
const relPath = reconcileObservedRelPath(graph, canonicalService, callSite.relPath);
|
|
3569
|
-
const fileNodeId = (0,
|
|
4267
|
+
const fileNodeId = (0, import_types8.fileId)(canonicalService, relPath);
|
|
3570
4268
|
if (!graph.hasNode(fileNodeId)) {
|
|
3571
4269
|
const language = languageForExt(relPath);
|
|
3572
4270
|
const node = {
|
|
3573
4271
|
id: fileNodeId,
|
|
3574
|
-
type:
|
|
4272
|
+
type: import_types8.NodeType.FileNode,
|
|
3575
4273
|
service: canonicalService,
|
|
3576
4274
|
path: relPath,
|
|
3577
4275
|
...language ? { language } : {},
|
|
@@ -3580,14 +4278,14 @@ function ensureObservedFileNode(graph, serviceName, serviceNodeId, callSite) {
|
|
|
3580
4278
|
};
|
|
3581
4279
|
graph.addNode(fileNodeId, node);
|
|
3582
4280
|
}
|
|
3583
|
-
const containsId = makeObservedEdgeId(
|
|
4281
|
+
const containsId = makeObservedEdgeId(import_types8.EdgeType.CONTAINS, serviceNodeId, fileNodeId);
|
|
3584
4282
|
if (!graph.hasEdge(containsId)) {
|
|
3585
4283
|
const edge = {
|
|
3586
4284
|
id: containsId,
|
|
3587
4285
|
source: serviceNodeId,
|
|
3588
4286
|
target: fileNodeId,
|
|
3589
|
-
type:
|
|
3590
|
-
provenance:
|
|
4287
|
+
type: import_types8.EdgeType.CONTAINS,
|
|
4288
|
+
provenance: import_types8.Provenance.OBSERVED
|
|
3591
4289
|
};
|
|
3592
4290
|
graph.addEdgeWithKey(containsId, serviceNodeId, fileNodeId, edge);
|
|
3593
4291
|
}
|
|
@@ -3611,11 +4309,11 @@ function pickContainingSymbol(candidates, fn) {
|
|
|
3611
4309
|
return [...candidates].sort(bySpan)[0].id;
|
|
3612
4310
|
}
|
|
3613
4311
|
function ensureObservedSymbolNode(graph, fileNodeId, service, relPath, fn, line) {
|
|
3614
|
-
const sid = (0,
|
|
4312
|
+
const sid = (0, import_types8.symbolId)(service, relPath, fn);
|
|
3615
4313
|
if (!graph.hasNode(sid)) {
|
|
3616
4314
|
const node = {
|
|
3617
4315
|
id: sid,
|
|
3618
|
-
type:
|
|
4316
|
+
type: import_types8.NodeType.SymbolNode,
|
|
3619
4317
|
kind: "function",
|
|
3620
4318
|
qualname: fn,
|
|
3621
4319
|
span: { startLine: line, endLine: line },
|
|
@@ -3625,14 +4323,14 @@ function ensureObservedSymbolNode(graph, fileNodeId, service, relPath, fn, line)
|
|
|
3625
4323
|
};
|
|
3626
4324
|
graph.addNode(sid, node);
|
|
3627
4325
|
}
|
|
3628
|
-
const containsId = makeObservedEdgeId(
|
|
4326
|
+
const containsId = makeObservedEdgeId(import_types8.EdgeType.CONTAINS, fileNodeId, sid);
|
|
3629
4327
|
if (!graph.hasEdge(containsId)) {
|
|
3630
4328
|
const edge = {
|
|
3631
4329
|
id: containsId,
|
|
3632
4330
|
source: fileNodeId,
|
|
3633
4331
|
target: sid,
|
|
3634
|
-
type:
|
|
3635
|
-
provenance:
|
|
4332
|
+
type: import_types8.EdgeType.CONTAINS,
|
|
4333
|
+
provenance: import_types8.Provenance.OBSERVED
|
|
3636
4334
|
};
|
|
3637
4335
|
graph.addEdgeWithKey(containsId, fileNodeId, sid, edge);
|
|
3638
4336
|
}
|
|
@@ -3644,9 +4342,9 @@ function landObservedSymbol(graph, fileNodeId, service, relPath, callSite) {
|
|
|
3644
4342
|
let sawSymbol = false;
|
|
3645
4343
|
const candidates = [];
|
|
3646
4344
|
graph.forEachOutboundEdge(fileNodeId, (_edge, edgeAttrs, _source, target) => {
|
|
3647
|
-
if (edgeAttrs.type !==
|
|
4345
|
+
if (edgeAttrs.type !== import_types8.EdgeType.CONTAINS) return;
|
|
3648
4346
|
const t = graph.getNodeAttributes(target);
|
|
3649
|
-
if (t.type !==
|
|
4347
|
+
if (t.type !== import_types8.NodeType.SymbolNode) return;
|
|
3650
4348
|
sawSymbol = true;
|
|
3651
4349
|
if (line >= t.span.startLine && line <= t.span.endLine) {
|
|
3652
4350
|
candidates.push({ id: target, symbol: t });
|
|
@@ -3659,17 +4357,17 @@ function landObservedSymbol(graph, fileNodeId, service, relPath, callSite) {
|
|
|
3659
4357
|
return fileNodeId;
|
|
3660
4358
|
}
|
|
3661
4359
|
function makeObservedEdgeId(type, source, target) {
|
|
3662
|
-
return (0,
|
|
4360
|
+
return (0, import_types8.observedEdgeId)(source, target, type);
|
|
3663
4361
|
}
|
|
3664
4362
|
function makeInferredEdgeId(type, source, target) {
|
|
3665
|
-
return (0,
|
|
4363
|
+
return (0, import_types8.inferredEdgeId)(source, target, type);
|
|
3666
4364
|
}
|
|
3667
4365
|
var INFERRED_CONFIDENCE = 0.6;
|
|
3668
4366
|
var STITCH_MAX_DEPTH = 2;
|
|
3669
4367
|
var STITCH_EDGE_TYPES = /* @__PURE__ */ new Set([
|
|
3670
|
-
|
|
3671
|
-
|
|
3672
|
-
|
|
4368
|
+
import_types8.EdgeType.CALLS,
|
|
4369
|
+
import_types8.EdgeType.CONNECTS_TO,
|
|
4370
|
+
import_types8.EdgeType.DEPENDS_ON
|
|
3673
4371
|
]);
|
|
3674
4372
|
var WIRE_SPAN_KIND_CLIENT = 3;
|
|
3675
4373
|
var WIRE_SPAN_KIND_PRODUCER = 4;
|
|
@@ -3685,11 +4383,11 @@ function spanServesGraphqlOperation(kind) {
|
|
|
3685
4383
|
return kind !== WIRE_SPAN_KIND_CLIENT && kind !== WIRE_SPAN_KIND_PRODUCER && kind !== WIRE_SPAN_KIND_CONSUMER;
|
|
3686
4384
|
}
|
|
3687
4385
|
function ensureGraphqlOperationNode(graph, serviceName, operationType, operationName) {
|
|
3688
|
-
const id = (0,
|
|
4386
|
+
const id = (0, import_types8.graphqlOperationId)(serviceName, operationType, operationName);
|
|
3689
4387
|
if (graph.hasNode(id)) return id;
|
|
3690
4388
|
const node = {
|
|
3691
4389
|
id,
|
|
3692
|
-
type:
|
|
4390
|
+
type: import_types8.NodeType.GraphQLOperationNode,
|
|
3693
4391
|
name: operationName,
|
|
3694
4392
|
service: serviceName,
|
|
3695
4393
|
operationType: operationType.toLowerCase(),
|
|
@@ -3703,11 +4401,11 @@ function spanServesGrpcMethod(kind) {
|
|
|
3703
4401
|
return kind !== WIRE_SPAN_KIND_CLIENT && kind !== WIRE_SPAN_KIND_PRODUCER && kind !== WIRE_SPAN_KIND_CONSUMER;
|
|
3704
4402
|
}
|
|
3705
4403
|
function ensureGrpcMethodNode(graph, rpcService, rpcMethod) {
|
|
3706
|
-
const id = (0,
|
|
4404
|
+
const id = (0, import_types8.grpcMethodId)(rpcService, rpcMethod);
|
|
3707
4405
|
if (graph.hasNode(id)) return id;
|
|
3708
4406
|
const node = {
|
|
3709
4407
|
id,
|
|
3710
|
-
type:
|
|
4408
|
+
type: import_types8.NodeType.GrpcMethodNode,
|
|
3711
4409
|
name: `${rpcService}/${rpcMethod}`,
|
|
3712
4410
|
rpcService,
|
|
3713
4411
|
rpcMethod,
|
|
@@ -3720,11 +4418,11 @@ function spanServesWebsocketChannel(kind) {
|
|
|
3720
4418
|
return kind !== WIRE_SPAN_KIND_CLIENT && kind !== WIRE_SPAN_KIND_PRODUCER && kind !== WIRE_SPAN_KIND_CONSUMER;
|
|
3721
4419
|
}
|
|
3722
4420
|
function ensureWebsocketChannelNode(graph, serviceName, channel) {
|
|
3723
|
-
const id = (0,
|
|
4421
|
+
const id = (0, import_types8.websocketChannelId)(serviceName, channel);
|
|
3724
4422
|
if (graph.hasNode(id)) return id;
|
|
3725
4423
|
const node = {
|
|
3726
4424
|
id,
|
|
3727
|
-
type:
|
|
4425
|
+
type: import_types8.NodeType.WebSocketChannelNode,
|
|
3728
4426
|
name: channel,
|
|
3729
4427
|
service: serviceName,
|
|
3730
4428
|
channel,
|
|
@@ -3737,11 +4435,11 @@ function messagingDestinationKind(system) {
|
|
|
3737
4435
|
return `${system}-topic`;
|
|
3738
4436
|
}
|
|
3739
4437
|
function ensureMessagingDestinationNode(graph, system, destination) {
|
|
3740
|
-
const id = (0,
|
|
4438
|
+
const id = (0, import_types8.infraId)(messagingDestinationKind(system), destination);
|
|
3741
4439
|
if (graph.hasNode(id)) return id;
|
|
3742
4440
|
const node = {
|
|
3743
4441
|
id,
|
|
3744
|
-
type:
|
|
4442
|
+
type: import_types8.NodeType.InfraNode,
|
|
3745
4443
|
name: destination,
|
|
3746
4444
|
provider: "self",
|
|
3747
4445
|
kind: messagingDestinationKind(system)
|
|
@@ -3785,9 +4483,9 @@ function lookupParentSpan(traceId, parentSpanId, now) {
|
|
|
3785
4483
|
};
|
|
3786
4484
|
}
|
|
3787
4485
|
function resolveServiceId(graph, host, env) {
|
|
3788
|
-
const envTagged = (0,
|
|
4486
|
+
const envTagged = (0, import_types8.serviceId)(host, env);
|
|
3789
4487
|
if (graph.hasNode(envTagged)) return envTagged;
|
|
3790
|
-
const envLess = (0,
|
|
4488
|
+
const envLess = (0, import_types8.serviceId)(host);
|
|
3791
4489
|
if (envLess !== envTagged && graph.hasNode(envLess)) return envLess;
|
|
3792
4490
|
let sameEnv = null;
|
|
3793
4491
|
let envLessMatch = null;
|
|
@@ -3795,7 +4493,7 @@ function resolveServiceId(graph, host, env) {
|
|
|
3795
4493
|
graph.forEachNode((id, attrs) => {
|
|
3796
4494
|
if (sameEnv) return;
|
|
3797
4495
|
const a = attrs;
|
|
3798
|
-
if (a.type !==
|
|
4496
|
+
if (a.type !== import_types8.NodeType.ServiceNode) return;
|
|
3799
4497
|
const matchesByName = a.name === host;
|
|
3800
4498
|
const matchesByAlias = a.aliases ? a.aliases.includes(host) : false;
|
|
3801
4499
|
if (!matchesByName && !matchesByAlias) return;
|
|
@@ -3810,14 +4508,14 @@ function resolveServiceId(graph, host, env) {
|
|
|
3810
4508
|
return sameEnv ?? envLessMatch ?? anyMatch;
|
|
3811
4509
|
}
|
|
3812
4510
|
function frontierIdFor(host) {
|
|
3813
|
-
return (0,
|
|
4511
|
+
return (0, import_types8.frontierId)(host);
|
|
3814
4512
|
}
|
|
3815
4513
|
function ensureServiceNode(graph, serviceName, env) {
|
|
3816
|
-
const id = (0,
|
|
4514
|
+
const id = (0, import_types8.serviceId)(serviceName, env);
|
|
3817
4515
|
if (graph.hasNode(id)) return id;
|
|
3818
4516
|
const wanted = serviceName.toLowerCase();
|
|
3819
4517
|
const extractedId = graph.findNode((_nid, attrs) => {
|
|
3820
|
-
if (attrs.type !==
|
|
4518
|
+
if (attrs.type !== import_types8.NodeType.ServiceNode) return false;
|
|
3821
4519
|
const svc = attrs;
|
|
3822
4520
|
if (svc.discoveredVia === "otel") return false;
|
|
3823
4521
|
return typeof svc.name === "string" && svc.name.toLowerCase() === wanted;
|
|
@@ -3825,7 +4523,7 @@ function ensureServiceNode(graph, serviceName, env) {
|
|
|
3825
4523
|
if (extractedId) return extractedId;
|
|
3826
4524
|
const node = {
|
|
3827
4525
|
id,
|
|
3828
|
-
type:
|
|
4526
|
+
type: import_types8.NodeType.ServiceNode,
|
|
3829
4527
|
name: serviceName,
|
|
3830
4528
|
language: "unknown",
|
|
3831
4529
|
discoveredVia: "otel",
|
|
@@ -3835,11 +4533,11 @@ function ensureServiceNode(graph, serviceName, env) {
|
|
|
3835
4533
|
return id;
|
|
3836
4534
|
}
|
|
3837
4535
|
function ensureInfraNode(graph, kind, name, provider) {
|
|
3838
|
-
const id = (0,
|
|
4536
|
+
const id = (0, import_types8.infraId)(kind, name);
|
|
3839
4537
|
if (graph.hasNode(id)) return id;
|
|
3840
4538
|
const node = {
|
|
3841
4539
|
id,
|
|
3842
|
-
type:
|
|
4540
|
+
type: import_types8.NodeType.InfraNode,
|
|
3843
4541
|
name,
|
|
3844
4542
|
provider,
|
|
3845
4543
|
kind
|
|
@@ -3851,7 +4549,7 @@ var COLUMN_BEARING_INFRA_KINDS = /* @__PURE__ */ new Set(["sql-table", "supabase
|
|
|
3851
4549
|
function mergeColumnsAt(graph, tableNodeId, columns, provenance, confidence) {
|
|
3852
4550
|
if (!columns || columns.length === 0 || !graph.hasNode(tableNodeId)) return;
|
|
3853
4551
|
const node = graph.getNodeAttributes(tableNodeId);
|
|
3854
|
-
if (node.type !==
|
|
4552
|
+
if (node.type !== import_types8.NodeType.InfraNode || !node.kind || !COLUMN_BEARING_INFRA_KINDS.has(node.kind)) {
|
|
3855
4553
|
return;
|
|
3856
4554
|
}
|
|
3857
4555
|
graph.replaceNodeAttributes(tableNodeId, {
|
|
@@ -3860,14 +4558,14 @@ function mergeColumnsAt(graph, tableNodeId, columns, provenance, confidence) {
|
|
|
3860
4558
|
});
|
|
3861
4559
|
}
|
|
3862
4560
|
function mergeObservedColumns(graph, tableNodeId, columns) {
|
|
3863
|
-
mergeColumnsAt(graph, tableNodeId, columns,
|
|
4561
|
+
mergeColumnsAt(graph, tableNodeId, columns, import_types8.Provenance.OBSERVED, OBSERVED_COLUMN_CONFIDENCE);
|
|
3864
4562
|
}
|
|
3865
4563
|
function ensureDatabaseNode(graph, host, engine) {
|
|
3866
|
-
const id = (0,
|
|
4564
|
+
const id = (0, import_types8.databaseId)(host);
|
|
3867
4565
|
if (graph.hasNode(id)) return id;
|
|
3868
4566
|
const node = {
|
|
3869
4567
|
id,
|
|
3870
|
-
type:
|
|
4568
|
+
type: import_types8.NodeType.DatabaseNode,
|
|
3871
4569
|
name: host,
|
|
3872
4570
|
engine,
|
|
3873
4571
|
engineVersion: "unknown",
|
|
@@ -3879,11 +4577,11 @@ function ensureDatabaseNode(graph, host, engine) {
|
|
|
3879
4577
|
return id;
|
|
3880
4578
|
}
|
|
3881
4579
|
function ensureLocalDatabaseNode(graph, serviceName, name, engine) {
|
|
3882
|
-
const id = (0,
|
|
4580
|
+
const id = (0, import_types8.localDatabaseId)(serviceName, name);
|
|
3883
4581
|
if (graph.hasNode(id)) return id;
|
|
3884
4582
|
const node = {
|
|
3885
4583
|
id,
|
|
3886
|
-
type:
|
|
4584
|
+
type: import_types8.NodeType.DatabaseNode,
|
|
3887
4585
|
name,
|
|
3888
4586
|
engine,
|
|
3889
4587
|
engineVersion: "unknown",
|
|
@@ -3898,17 +4596,17 @@ function findDeclaredDatabaseForService(graph, serviceNodeId, engine) {
|
|
|
3898
4596
|
const sources = [serviceNodeId];
|
|
3899
4597
|
for (const edgeId of graph.outboundEdges(serviceNodeId)) {
|
|
3900
4598
|
const e = graph.getEdgeAttributes(edgeId);
|
|
3901
|
-
if (e.type ===
|
|
4599
|
+
if (e.type === import_types8.EdgeType.CONTAINS) sources.push(e.target);
|
|
3902
4600
|
}
|
|
3903
4601
|
const matches = /* @__PURE__ */ new Set();
|
|
3904
4602
|
for (const src of sources) {
|
|
3905
4603
|
if (!graph.hasNode(src)) continue;
|
|
3906
4604
|
for (const edgeId of graph.outboundEdges(src)) {
|
|
3907
4605
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
3908
|
-
if (edge.type !==
|
|
4606
|
+
if (edge.type !== import_types8.EdgeType.CONNECTS_TO || edge.provenance !== import_types8.Provenance.EXTRACTED) continue;
|
|
3909
4607
|
if (!graph.hasNode(edge.target)) continue;
|
|
3910
4608
|
const target = graph.getNodeAttributes(edge.target);
|
|
3911
|
-
if (target.type !==
|
|
4609
|
+
if (target.type !== import_types8.NodeType.DatabaseNode || target.engine !== engine) continue;
|
|
3912
4610
|
matches.add(edge.target);
|
|
3913
4611
|
}
|
|
3914
4612
|
}
|
|
@@ -3923,7 +4621,7 @@ function ensureFrontierNode(graph, host, ts) {
|
|
|
3923
4621
|
}
|
|
3924
4622
|
const node = {
|
|
3925
4623
|
id,
|
|
3926
|
-
type:
|
|
4624
|
+
type: import_types8.NodeType.FrontierNode,
|
|
3927
4625
|
name: host,
|
|
3928
4626
|
host,
|
|
3929
4627
|
firstObserved: ts,
|
|
@@ -3947,11 +4645,11 @@ function upsertObservedEdge(graph, type, source, target, ts, isError = false, ev
|
|
|
3947
4645
|
};
|
|
3948
4646
|
const updated = {
|
|
3949
4647
|
...existing,
|
|
3950
|
-
provenance:
|
|
4648
|
+
provenance: import_types8.Provenance.OBSERVED,
|
|
3951
4649
|
lastObserved: ts,
|
|
3952
4650
|
callCount: newSpanCount,
|
|
3953
4651
|
signal: newSignal,
|
|
3954
|
-
confidence: (0,
|
|
4652
|
+
confidence: (0, import_types8.confidenceForObservedSignal)(newSignal),
|
|
3955
4653
|
grain
|
|
3956
4654
|
// backfills legacy edges that predate ADR-142
|
|
3957
4655
|
};
|
|
@@ -3968,8 +4666,8 @@ function upsertObservedEdge(graph, type, source, target, ts, isError = false, ev
|
|
|
3968
4666
|
source,
|
|
3969
4667
|
target,
|
|
3970
4668
|
type,
|
|
3971
|
-
provenance:
|
|
3972
|
-
confidence: (0,
|
|
4669
|
+
provenance: import_types8.Provenance.OBSERVED,
|
|
4670
|
+
confidence: (0, import_types8.confidenceForObservedSignal)(signal),
|
|
3973
4671
|
lastObserved: ts,
|
|
3974
4672
|
callCount: 1,
|
|
3975
4673
|
signal,
|
|
@@ -3991,9 +4689,9 @@ function stitchTrace(graph, sourceServiceId, ts) {
|
|
|
3991
4689
|
const outbound = graph.outboundEdges(nodeId);
|
|
3992
4690
|
for (const edgeId of outbound) {
|
|
3993
4691
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
3994
|
-
if (edge.provenance !==
|
|
4692
|
+
if (edge.provenance !== import_types8.Provenance.EXTRACTED) continue;
|
|
3995
4693
|
if (!STITCH_EDGE_TYPES.has(edge.type)) continue;
|
|
3996
|
-
if (graph.hasEdge((0,
|
|
4694
|
+
if (graph.hasEdge((0, import_types8.observedEdgeId)(edge.source, edge.target, edge.type))) continue;
|
|
3997
4695
|
upsertInferredEdge(graph, edge.type, edge.source, edge.target, ts);
|
|
3998
4696
|
if (!visited.has(edge.target)) {
|
|
3999
4697
|
visited.add(edge.target);
|
|
@@ -4015,23 +4713,23 @@ function upsertInferredEdge(graph, type, source, target, ts) {
|
|
|
4015
4713
|
source,
|
|
4016
4714
|
target,
|
|
4017
4715
|
type,
|
|
4018
|
-
provenance:
|
|
4716
|
+
provenance: import_types8.Provenance.INFERRED,
|
|
4019
4717
|
confidence: INFERRED_CONFIDENCE,
|
|
4020
4718
|
lastObserved: ts
|
|
4021
4719
|
};
|
|
4022
4720
|
graph.addEdgeWithKey(id, source, target, edge);
|
|
4023
4721
|
}
|
|
4024
4722
|
async function appendErrorEvent(ctx, ev) {
|
|
4025
|
-
await
|
|
4026
|
-
await
|
|
4723
|
+
await import_node_fs7.promises.mkdir(import_node_path8.default.dirname(ctx.errorsPath), { recursive: true });
|
|
4724
|
+
await import_node_fs7.promises.appendFile(ctx.errorsPath, JSON.stringify(ev) + "\n", "utf8");
|
|
4027
4725
|
}
|
|
4028
4726
|
function incidentAffectedNode(span, graph, scanPath) {
|
|
4029
|
-
const sid = (0,
|
|
4727
|
+
const sid = (0, import_types8.serviceId)(span.service, span.env);
|
|
4030
4728
|
const serviceNode = graph && graph.hasNode(sid) ? graph.getNodeAttributes(sid) : void 0;
|
|
4031
4729
|
const callSite = callSiteFromSpan(span, serviceNode, scanPath);
|
|
4032
4730
|
if (callSite) {
|
|
4033
4731
|
const relPath = graph ? reconcileObservedRelPath(graph, span.service, callSite.relPath) : callSite.relPath;
|
|
4034
|
-
return (0,
|
|
4732
|
+
return (0, import_types8.fileId)(span.service, relPath);
|
|
4035
4733
|
}
|
|
4036
4734
|
return sid;
|
|
4037
4735
|
}
|
|
@@ -4064,8 +4762,8 @@ function makeErrorSpanWriter(errorsPath, graph, scanPath) {
|
|
|
4064
4762
|
return async (span) => {
|
|
4065
4763
|
const ev = buildErrorEventForReceiver(span, graph, scanPath);
|
|
4066
4764
|
if (!ev) return;
|
|
4067
|
-
await
|
|
4068
|
-
await
|
|
4765
|
+
await import_node_fs7.promises.mkdir(import_node_path8.default.dirname(errorsPath), { recursive: true });
|
|
4766
|
+
await import_node_fs7.promises.appendFile(errorsPath, JSON.stringify(ev) + "\n", "utf8");
|
|
4069
4767
|
};
|
|
4070
4768
|
}
|
|
4071
4769
|
async function recordFailingResponseIncident(ctx, span, affectedNode, timestamp, statusCode, count, firstTimestamp) {
|
|
@@ -4156,7 +4854,7 @@ function findRouteNodeByHttpRoute(graph, serviceName, method, httpRoute) {
|
|
|
4156
4854
|
graph.forEachNode((id, attrs) => {
|
|
4157
4855
|
if (found) return;
|
|
4158
4856
|
const a = attrs;
|
|
4159
|
-
if (a.type !==
|
|
4857
|
+
if (a.type !== import_types8.NodeType.RouteNode || a.service !== serviceName) return;
|
|
4160
4858
|
if (m && a.method !== "ALL" && a.method !== m) return;
|
|
4161
4859
|
if (normalizePathTemplate(a.pathTemplate) === target) found = id;
|
|
4162
4860
|
});
|
|
@@ -4187,7 +4885,7 @@ async function handleSpan(ctx, span) {
|
|
|
4187
4885
|
let targetId;
|
|
4188
4886
|
if (host) {
|
|
4189
4887
|
ensureDatabaseNode(ctx.graph, host, span.dbSystem);
|
|
4190
|
-
targetId = (0,
|
|
4888
|
+
targetId = (0, import_types8.databaseId)(host);
|
|
4191
4889
|
} else {
|
|
4192
4890
|
const declared = findDeclaredDatabaseForService(ctx.graph, sourceId, span.dbSystem);
|
|
4193
4891
|
if (declared) {
|
|
@@ -4204,7 +4902,7 @@ async function handleSpan(ctx, span) {
|
|
|
4204
4902
|
}
|
|
4205
4903
|
const result = upsertObservedEdge(
|
|
4206
4904
|
ctx.graph,
|
|
4207
|
-
|
|
4905
|
+
import_types8.EdgeType.CONNECTS_TO,
|
|
4208
4906
|
observedSource(),
|
|
4209
4907
|
targetId,
|
|
4210
4908
|
ts,
|
|
@@ -4216,7 +4914,7 @@ async function handleSpan(ctx, span) {
|
|
|
4216
4914
|
const collectionId = ensureInfraNode(ctx.graph, "mongodb-collection", span.dbCollection, "self");
|
|
4217
4915
|
upsertObservedEdge(
|
|
4218
4916
|
ctx.graph,
|
|
4219
|
-
|
|
4917
|
+
import_types8.EdgeType.CALLS,
|
|
4220
4918
|
observedSource(),
|
|
4221
4919
|
collectionId,
|
|
4222
4920
|
ts,
|
|
@@ -4228,7 +4926,7 @@ async function handleSpan(ctx, span) {
|
|
|
4228
4926
|
const tableId = ensureInfraNode(ctx.graph, "sql-table", span.dbTable, "self");
|
|
4229
4927
|
upsertObservedEdge(
|
|
4230
4928
|
ctx.graph,
|
|
4231
|
-
|
|
4929
|
+
import_types8.EdgeType.CALLS,
|
|
4232
4930
|
observedSource(),
|
|
4233
4931
|
tableId,
|
|
4234
4932
|
ts,
|
|
@@ -4244,7 +4942,7 @@ async function handleSpan(ctx, span) {
|
|
|
4244
4942
|
span.messagingSystem,
|
|
4245
4943
|
span.messagingDestination
|
|
4246
4944
|
);
|
|
4247
|
-
const edgeType = span.kind === WIRE_SPAN_KIND_CONSUMER ?
|
|
4945
|
+
const edgeType = span.kind === WIRE_SPAN_KIND_CONSUMER ? import_types8.EdgeType.CONSUMES_FROM : import_types8.EdgeType.PUBLISHES_TO;
|
|
4248
4946
|
const result = upsertObservedEdge(
|
|
4249
4947
|
ctx.graph,
|
|
4250
4948
|
edgeType,
|
|
@@ -4264,7 +4962,7 @@ async function handleSpan(ctx, span) {
|
|
|
4264
4962
|
);
|
|
4265
4963
|
const result = upsertObservedEdge(
|
|
4266
4964
|
ctx.graph,
|
|
4267
|
-
|
|
4965
|
+
import_types8.EdgeType.CONTAINS,
|
|
4268
4966
|
observedSource(),
|
|
4269
4967
|
targetId,
|
|
4270
4968
|
ts,
|
|
@@ -4276,7 +4974,7 @@ async function handleSpan(ctx, span) {
|
|
|
4276
4974
|
const targetId = ensureGrpcMethodNode(ctx.graph, span.rpcService, span.rpcMethod);
|
|
4277
4975
|
const result = upsertObservedEdge(
|
|
4278
4976
|
ctx.graph,
|
|
4279
|
-
|
|
4977
|
+
import_types8.EdgeType.CONTAINS,
|
|
4280
4978
|
observedSource(),
|
|
4281
4979
|
targetId,
|
|
4282
4980
|
ts,
|
|
@@ -4292,7 +4990,7 @@ async function handleSpan(ctx, span) {
|
|
|
4292
4990
|
);
|
|
4293
4991
|
const result = upsertObservedEdge(
|
|
4294
4992
|
ctx.graph,
|
|
4295
|
-
|
|
4993
|
+
import_types8.EdgeType.CONNECTS_TO,
|
|
4296
4994
|
observedSource(),
|
|
4297
4995
|
targetId,
|
|
4298
4996
|
ts,
|
|
@@ -4308,7 +5006,7 @@ async function handleSpan(ctx, span) {
|
|
|
4308
5006
|
if (targetId && targetId !== sourceId) {
|
|
4309
5007
|
upsertObservedEdge(
|
|
4310
5008
|
ctx.graph,
|
|
4311
|
-
|
|
5009
|
+
import_types8.EdgeType.CALLS,
|
|
4312
5010
|
observedSource(),
|
|
4313
5011
|
targetId,
|
|
4314
5012
|
ts,
|
|
@@ -4321,7 +5019,7 @@ async function handleSpan(ctx, span) {
|
|
|
4321
5019
|
const frontierNodeId = ensureFrontierNode(ctx.graph, host, ts);
|
|
4322
5020
|
upsertObservedEdge(
|
|
4323
5021
|
ctx.graph,
|
|
4324
|
-
|
|
5022
|
+
import_types8.EdgeType.CALLS,
|
|
4325
5023
|
observedSource(),
|
|
4326
5024
|
frontierNodeId,
|
|
4327
5025
|
ts,
|
|
@@ -4347,7 +5045,7 @@ async function handleSpan(ctx, span) {
|
|
|
4347
5045
|
} : void 0;
|
|
4348
5046
|
upsertObservedEdge(
|
|
4349
5047
|
ctx.graph,
|
|
4350
|
-
|
|
5048
|
+
import_types8.EdgeType.CALLS,
|
|
4351
5049
|
fallbackSource,
|
|
4352
5050
|
sourceId,
|
|
4353
5051
|
ts,
|
|
@@ -4366,7 +5064,7 @@ async function handleSpan(ctx, span) {
|
|
|
4366
5064
|
);
|
|
4367
5065
|
if (routeNodeId) {
|
|
4368
5066
|
const routeSvc = ctx.graph.getNodeAttributes(routeNodeId).service;
|
|
4369
|
-
upsertObservedEdge(ctx.graph,
|
|
5067
|
+
upsertObservedEdge(ctx.graph, import_types8.EdgeType.CONTAINS, (0, import_types8.serviceId)(routeSvc), routeNodeId, ts, isError);
|
|
4370
5068
|
}
|
|
4371
5069
|
}
|
|
4372
5070
|
if (span.statusCode === 2) {
|
|
@@ -4405,7 +5103,7 @@ function promoteFrontierNodes(graph, opts = {}) {
|
|
|
4405
5103
|
const aliasIndex = /* @__PURE__ */ new Map();
|
|
4406
5104
|
graph.forEachNode((id, attrs) => {
|
|
4407
5105
|
const a = attrs;
|
|
4408
|
-
if (a.type !==
|
|
5106
|
+
if (a.type !== import_types8.NodeType.ServiceNode) return;
|
|
4409
5107
|
aliasIndex.set(a.name, id);
|
|
4410
5108
|
if (a.aliases) {
|
|
4411
5109
|
for (const alias of a.aliases) aliasIndex.set(alias, id);
|
|
@@ -4414,7 +5112,7 @@ function promoteFrontierNodes(graph, opts = {}) {
|
|
|
4414
5112
|
const toPromote = [];
|
|
4415
5113
|
graph.forEachNode((id, attrs) => {
|
|
4416
5114
|
const a = attrs;
|
|
4417
|
-
if (a.type !==
|
|
5115
|
+
if (a.type !== import_types8.NodeType.FrontierNode) return;
|
|
4418
5116
|
const target = aliasIndex.get(a.host);
|
|
4419
5117
|
if (!target) return;
|
|
4420
5118
|
if (target === id) return;
|
|
@@ -4448,7 +5146,7 @@ function rewireFrontierEdges(graph, frontierId2, serviceId7) {
|
|
|
4448
5146
|
}
|
|
4449
5147
|
function rebuildEdge(graph, edge, newSource, newTarget, oldEdgeId) {
|
|
4450
5148
|
graph.dropEdge(oldEdgeId);
|
|
4451
|
-
const newId = edge.provenance ===
|
|
5149
|
+
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);
|
|
4452
5150
|
if (graph.hasEdge(newId)) {
|
|
4453
5151
|
const existing = graph.getEdgeAttributes(newId);
|
|
4454
5152
|
const merged = {
|
|
@@ -4482,12 +5180,12 @@ async function markStaleEdges(graph, options = {}) {
|
|
|
4482
5180
|
const project = options.project ?? DEFAULT_PROJECT;
|
|
4483
5181
|
graph.forEachEdge((id, attrs) => {
|
|
4484
5182
|
const e = attrs;
|
|
4485
|
-
if (e.provenance !==
|
|
5183
|
+
if (e.provenance !== import_types8.Provenance.OBSERVED) return;
|
|
4486
5184
|
if (!e.lastObserved) return;
|
|
4487
5185
|
const threshold = thresholdForEdgeType(e.type, thresholds);
|
|
4488
5186
|
const age = now - new Date(e.lastObserved).getTime();
|
|
4489
5187
|
if (age > threshold) {
|
|
4490
|
-
const updated = { ...e, provenance:
|
|
5188
|
+
const updated = { ...e, provenance: import_types8.Provenance.STALE, confidence: 0.3 };
|
|
4491
5189
|
graph.replaceEdgeAttributes(id, updated);
|
|
4492
5190
|
events.push({
|
|
4493
5191
|
edgeId: id,
|
|
@@ -4504,8 +5202,8 @@ async function markStaleEdges(graph, options = {}) {
|
|
|
4504
5202
|
project,
|
|
4505
5203
|
payload: {
|
|
4506
5204
|
edgeId: id,
|
|
4507
|
-
from:
|
|
4508
|
-
to:
|
|
5205
|
+
from: import_types8.Provenance.OBSERVED,
|
|
5206
|
+
to: import_types8.Provenance.STALE
|
|
4509
5207
|
}
|
|
4510
5208
|
});
|
|
4511
5209
|
}
|
|
@@ -4516,13 +5214,13 @@ async function markStaleEdges(graph, options = {}) {
|
|
|
4516
5214
|
return { count: events.length, events };
|
|
4517
5215
|
}
|
|
4518
5216
|
async function appendStaleEvents(staleEventsPath, events) {
|
|
4519
|
-
await
|
|
5217
|
+
await import_node_fs7.promises.mkdir(import_node_path8.default.dirname(staleEventsPath), { recursive: true });
|
|
4520
5218
|
const lines = events.map((e) => JSON.stringify(e)).join("\n") + "\n";
|
|
4521
|
-
await
|
|
5219
|
+
await import_node_fs7.promises.appendFile(staleEventsPath, lines, "utf8");
|
|
4522
5220
|
}
|
|
4523
5221
|
async function readStaleEvents(staleEventsPath) {
|
|
4524
5222
|
try {
|
|
4525
|
-
const raw = await
|
|
5223
|
+
const raw = await import_node_fs7.promises.readFile(staleEventsPath, "utf8");
|
|
4526
5224
|
return raw.split("\n").filter((line) => line.length > 0).map((line) => JSON.parse(line));
|
|
4527
5225
|
} catch (err) {
|
|
4528
5226
|
if (err.code === "ENOENT") return [];
|
|
@@ -4556,7 +5254,7 @@ function startStalenessLoop(graph, options = {}) {
|
|
|
4556
5254
|
}
|
|
4557
5255
|
async function readErrorEvents(errorsPath) {
|
|
4558
5256
|
try {
|
|
4559
|
-
const raw = await
|
|
5257
|
+
const raw = await import_node_fs7.promises.readFile(errorsPath, "utf8");
|
|
4560
5258
|
const events = raw.split("\n").filter((line) => line.length > 0).map((line) => JSON.parse(line));
|
|
4561
5259
|
return dedupeIncidents(events);
|
|
4562
5260
|
} catch (err) {
|
|
@@ -4614,7 +5312,7 @@ function mergeSnapshot(graph, snapshot) {
|
|
|
4614
5312
|
const validEdges = [];
|
|
4615
5313
|
for (const node of incomingNodes) {
|
|
4616
5314
|
if (node.attributes === void 0) continue;
|
|
4617
|
-
const parsed =
|
|
5315
|
+
const parsed = import_types8.GraphNodeSchema.safeParse(node.attributes);
|
|
4618
5316
|
if (!parsed.success) {
|
|
4619
5317
|
issues.push(`node "${node.key}": ${describeZodIssues(parsed.error)}`);
|
|
4620
5318
|
continue;
|
|
@@ -4623,7 +5321,7 @@ function mergeSnapshot(graph, snapshot) {
|
|
|
4623
5321
|
}
|
|
4624
5322
|
for (const edge of incomingEdges) {
|
|
4625
5323
|
if (edge.attributes === void 0) continue;
|
|
4626
|
-
const parsed =
|
|
5324
|
+
const parsed = import_types8.GraphEdgeSchema.safeParse(edge.attributes);
|
|
4627
5325
|
if (!parsed.success) {
|
|
4628
5326
|
const label = edge.key ?? `${edge.source}->${edge.target}`;
|
|
4629
5327
|
issues.push(`edge "${label}": ${describeZodIssues(parsed.error)}`);
|
|
@@ -4653,16 +5351,16 @@ function mergeSnapshot(graph, snapshot) {
|
|
|
4653
5351
|
|
|
4654
5352
|
// src/extract/services.ts
|
|
4655
5353
|
init_cjs_shims();
|
|
4656
|
-
var
|
|
4657
|
-
var
|
|
5354
|
+
var import_node_fs11 = require("fs");
|
|
5355
|
+
var import_node_path12 = __toESM(require("path"), 1);
|
|
4658
5356
|
var import_ignore = __toESM(require("ignore"), 1);
|
|
4659
5357
|
var import_minimatch2 = require("minimatch");
|
|
4660
|
-
var
|
|
5358
|
+
var import_types10 = require("@neat.is/types");
|
|
4661
5359
|
|
|
4662
5360
|
// src/extract/python.ts
|
|
4663
5361
|
init_cjs_shims();
|
|
4664
|
-
var
|
|
4665
|
-
var
|
|
5362
|
+
var import_node_fs8 = require("fs");
|
|
5363
|
+
var import_node_path9 = __toESM(require("path"), 1);
|
|
4666
5364
|
var import_smol_toml = require("smol-toml");
|
|
4667
5365
|
var REQUIREMENT_LINE = /^\s*([A-Za-z0-9_.-]+)(?:\[[^\]]*\])?\s*(?:(==)\s*([A-Za-z0-9_.+-]+))?/;
|
|
4668
5366
|
function parseRequirementsTxt(content) {
|
|
@@ -4695,25 +5393,25 @@ function depsFromPyProject(pyproject) {
|
|
|
4695
5393
|
return out;
|
|
4696
5394
|
}
|
|
4697
5395
|
async function discoverPythonService(serviceDir) {
|
|
4698
|
-
const pyprojectPath =
|
|
4699
|
-
const requirementsPath =
|
|
4700
|
-
const setupPath =
|
|
5396
|
+
const pyprojectPath = import_node_path9.default.join(serviceDir, "pyproject.toml");
|
|
5397
|
+
const requirementsPath = import_node_path9.default.join(serviceDir, "requirements.txt");
|
|
5398
|
+
const setupPath = import_node_path9.default.join(serviceDir, "setup.py");
|
|
4701
5399
|
const hasPyproject = await exists(pyprojectPath);
|
|
4702
5400
|
const hasRequirements = await exists(requirementsPath);
|
|
4703
5401
|
const hasSetup = await exists(setupPath);
|
|
4704
5402
|
if (!hasPyproject && !hasRequirements && !hasSetup) return null;
|
|
4705
|
-
let name =
|
|
5403
|
+
let name = import_node_path9.default.basename(serviceDir);
|
|
4706
5404
|
let version;
|
|
4707
5405
|
const dependencies = {};
|
|
4708
5406
|
if (hasPyproject) {
|
|
4709
|
-
const raw = await
|
|
5407
|
+
const raw = await import_node_fs8.promises.readFile(pyprojectPath, "utf8");
|
|
4710
5408
|
const pyproject = (0, import_smol_toml.parse)(raw);
|
|
4711
5409
|
name = pyproject.project?.name ?? pyproject.tool?.poetry?.name ?? name;
|
|
4712
5410
|
version = pyproject.project?.version ?? pyproject.tool?.poetry?.version ?? void 0;
|
|
4713
5411
|
Object.assign(dependencies, depsFromPyProject(pyproject));
|
|
4714
5412
|
}
|
|
4715
5413
|
if (hasRequirements) {
|
|
4716
|
-
const raw = await
|
|
5414
|
+
const raw = await import_node_fs8.promises.readFile(requirementsPath, "utf8");
|
|
4717
5415
|
Object.assign(dependencies, parseRequirementsTxt(raw));
|
|
4718
5416
|
}
|
|
4719
5417
|
return { name, version, dependencies };
|
|
@@ -4728,9 +5426,9 @@ function pythonToPackage(service) {
|
|
|
4728
5426
|
|
|
4729
5427
|
// src/extract/go.ts
|
|
4730
5428
|
init_cjs_shims();
|
|
4731
|
-
var
|
|
4732
|
-
var
|
|
4733
|
-
var
|
|
5429
|
+
var import_node_fs9 = require("fs");
|
|
5430
|
+
var import_node_path10 = __toESM(require("path"), 1);
|
|
5431
|
+
var import_types9 = require("@neat.is/types");
|
|
4734
5432
|
function parseGoMod(source) {
|
|
4735
5433
|
const module2 = source.match(/^\s*module\s+(\S+)\s*$/m)?.[1];
|
|
4736
5434
|
if (!module2) return null;
|
|
@@ -4749,7 +5447,7 @@ function parseGoMod(source) {
|
|
|
4749
5447
|
async function discoverGoService(scanPath, dir) {
|
|
4750
5448
|
let raw;
|
|
4751
5449
|
try {
|
|
4752
|
-
raw = await
|
|
5450
|
+
raw = await import_node_fs9.promises.readFile(import_node_path10.default.join(dir, "go.mod"), "utf8");
|
|
4753
5451
|
} catch {
|
|
4754
5452
|
return null;
|
|
4755
5453
|
}
|
|
@@ -4758,12 +5456,12 @@ async function discoverGoService(scanPath, dir) {
|
|
|
4758
5456
|
const name = mod.module.split("/").filter(Boolean).pop() ?? mod.module;
|
|
4759
5457
|
const pkg = { name, dependencies: mod.dependencies };
|
|
4760
5458
|
const node = {
|
|
4761
|
-
id: (0,
|
|
4762
|
-
type:
|
|
5459
|
+
id: (0, import_types9.serviceId)(name),
|
|
5460
|
+
type: import_types9.NodeType.ServiceNode,
|
|
4763
5461
|
name,
|
|
4764
5462
|
language: "go",
|
|
4765
5463
|
dependencies: mod.dependencies,
|
|
4766
|
-
repoPath:
|
|
5464
|
+
repoPath: import_node_path10.default.relative(scanPath, dir),
|
|
4767
5465
|
...mod.dependencies["github.com/gin-gonic/gin"] ? { framework: "gin" } : {}
|
|
4768
5466
|
};
|
|
4769
5467
|
return { pkg, dir, node };
|
|
@@ -4771,17 +5469,17 @@ async function discoverGoService(scanPath, dir) {
|
|
|
4771
5469
|
|
|
4772
5470
|
// src/extract/owners.ts
|
|
4773
5471
|
init_cjs_shims();
|
|
4774
|
-
var
|
|
4775
|
-
var
|
|
5472
|
+
var import_node_fs10 = require("fs");
|
|
5473
|
+
var import_node_path11 = __toESM(require("path"), 1);
|
|
4776
5474
|
var import_minimatch = require("minimatch");
|
|
4777
5475
|
async function loadCodeowners(scanPath) {
|
|
4778
5476
|
const candidates = [
|
|
4779
|
-
|
|
4780
|
-
|
|
5477
|
+
import_node_path11.default.join(scanPath, "CODEOWNERS"),
|
|
5478
|
+
import_node_path11.default.join(scanPath, ".github", "CODEOWNERS")
|
|
4781
5479
|
];
|
|
4782
5480
|
for (const file of candidates) {
|
|
4783
5481
|
if (await exists(file)) {
|
|
4784
|
-
const raw = await
|
|
5482
|
+
const raw = await import_node_fs10.promises.readFile(file, "utf8");
|
|
4785
5483
|
return parseCodeowners(raw);
|
|
4786
5484
|
}
|
|
4787
5485
|
}
|
|
@@ -4799,7 +5497,7 @@ function parseCodeowners(raw) {
|
|
|
4799
5497
|
return { rules };
|
|
4800
5498
|
}
|
|
4801
5499
|
function matchOwner(file, repoPath) {
|
|
4802
|
-
const normalized = repoPath.split(
|
|
5500
|
+
const normalized = repoPath.split(import_node_path11.default.sep).join("/");
|
|
4803
5501
|
for (const rule of file.rules) {
|
|
4804
5502
|
if (matchesPattern(rule.pattern, normalized)) return rule.owners;
|
|
4805
5503
|
}
|
|
@@ -4815,7 +5513,7 @@ function matchesPattern(rawPattern, repoPath) {
|
|
|
4815
5513
|
return false;
|
|
4816
5514
|
}
|
|
4817
5515
|
async function readPackageJsonAuthor(serviceDir) {
|
|
4818
|
-
const pkgPath =
|
|
5516
|
+
const pkgPath = import_node_path11.default.join(serviceDir, "package.json");
|
|
4819
5517
|
if (!await exists(pkgPath)) return null;
|
|
4820
5518
|
try {
|
|
4821
5519
|
const pkg = await readJson(pkgPath);
|
|
@@ -4852,27 +5550,27 @@ function workspaceGlobs(pkg) {
|
|
|
4852
5550
|
return null;
|
|
4853
5551
|
}
|
|
4854
5552
|
async function hasPythonManifest(dir) {
|
|
4855
|
-
return await exists(
|
|
5553
|
+
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"));
|
|
4856
5554
|
}
|
|
4857
5555
|
async function hasGoManifest(dir) {
|
|
4858
|
-
return exists(
|
|
5556
|
+
return exists(import_node_path12.default.join(dir, "go.mod"));
|
|
4859
5557
|
}
|
|
4860
5558
|
async function loadGitignore(scanPath) {
|
|
4861
|
-
const gitignorePath =
|
|
5559
|
+
const gitignorePath = import_node_path12.default.join(scanPath, ".gitignore");
|
|
4862
5560
|
if (!await exists(gitignorePath)) return null;
|
|
4863
|
-
const raw = await
|
|
5561
|
+
const raw = await import_node_fs11.promises.readFile(gitignorePath, "utf8");
|
|
4864
5562
|
return (0, import_ignore.default)().add(raw);
|
|
4865
5563
|
}
|
|
4866
5564
|
async function walkDirs(start, scanPath, options, visit) {
|
|
4867
5565
|
async function recurse(current, depth) {
|
|
4868
5566
|
if (depth > options.maxDepth) return;
|
|
4869
|
-
const entries = await
|
|
5567
|
+
const entries = await import_node_fs11.promises.readdir(current, { withFileTypes: true }).catch(() => []);
|
|
4870
5568
|
for (const entry of entries) {
|
|
4871
5569
|
if (!entry.isDirectory()) continue;
|
|
4872
5570
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
4873
|
-
const child =
|
|
5571
|
+
const child = import_node_path12.default.join(current, entry.name);
|
|
4874
5572
|
if (options.ig) {
|
|
4875
|
-
const rel =
|
|
5573
|
+
const rel = import_node_path12.default.relative(scanPath, child).split(import_node_path12.default.sep).join("/");
|
|
4876
5574
|
if (rel && options.ig.ignores(rel + "/")) continue;
|
|
4877
5575
|
}
|
|
4878
5576
|
if (await isPythonVenvDir(child)) continue;
|
|
@@ -4888,8 +5586,8 @@ async function expandWorkspaceGlobs(scanPath, globs) {
|
|
|
4888
5586
|
for (const raw of globs) {
|
|
4889
5587
|
const pattern = raw.replace(/^\.\//, "");
|
|
4890
5588
|
if (!pattern.includes("*")) {
|
|
4891
|
-
const candidate =
|
|
4892
|
-
if (await exists(
|
|
5589
|
+
const candidate = import_node_path12.default.join(scanPath, pattern);
|
|
5590
|
+
if (await exists(import_node_path12.default.join(candidate, "package.json"))) found.add(candidate);
|
|
4893
5591
|
continue;
|
|
4894
5592
|
}
|
|
4895
5593
|
const segments = pattern.split("/");
|
|
@@ -4898,13 +5596,13 @@ async function expandWorkspaceGlobs(scanPath, globs) {
|
|
|
4898
5596
|
if (seg.includes("*")) break;
|
|
4899
5597
|
staticSegments.push(seg);
|
|
4900
5598
|
}
|
|
4901
|
-
const start =
|
|
5599
|
+
const start = import_node_path12.default.join(scanPath, ...staticSegments);
|
|
4902
5600
|
if (!await exists(start)) continue;
|
|
4903
5601
|
const hasDoubleStar = pattern.includes("**");
|
|
4904
5602
|
const walkDepth = hasDoubleStar ? scanDepth : Math.max(0, segments.length - staticSegments.length - 1);
|
|
4905
5603
|
await walkDirs(start, scanPath, { maxDepth: walkDepth, ig: null }, async (dir) => {
|
|
4906
|
-
const rel =
|
|
4907
|
-
if ((0, import_minimatch2.minimatch)(rel, pattern) && await exists(
|
|
5604
|
+
const rel = import_node_path12.default.relative(scanPath, dir).split(import_node_path12.default.sep).join("/");
|
|
5605
|
+
if ((0, import_minimatch2.minimatch)(rel, pattern) && await exists(import_node_path12.default.join(dir, "package.json"))) {
|
|
4908
5606
|
found.add(dir);
|
|
4909
5607
|
}
|
|
4910
5608
|
});
|
|
@@ -4927,31 +5625,31 @@ function detectJsFramework(pkg) {
|
|
|
4927
5625
|
async function detectJsServiceLanguage(dir, pkg) {
|
|
4928
5626
|
const deps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
4929
5627
|
if (deps["typescript"] !== void 0) return "typescript";
|
|
4930
|
-
const entries = await
|
|
5628
|
+
const entries = await import_node_fs11.promises.readdir(dir).catch(() => []);
|
|
4931
5629
|
if (entries.some((name) => /^tsconfig(\..+)?\.json$/.test(name))) return "typescript";
|
|
4932
5630
|
return "javascript";
|
|
4933
5631
|
}
|
|
4934
5632
|
async function discoverNodeService(scanPath, dir) {
|
|
4935
|
-
const pkgPath =
|
|
5633
|
+
const pkgPath = import_node_path12.default.join(dir, "package.json");
|
|
4936
5634
|
if (!await exists(pkgPath)) return null;
|
|
4937
5635
|
let pkg;
|
|
4938
5636
|
try {
|
|
4939
5637
|
pkg = await readJson(pkgPath);
|
|
4940
5638
|
} catch (err) {
|
|
4941
|
-
recordExtractionError("services",
|
|
5639
|
+
recordExtractionError("services", import_node_path12.default.relative(scanPath, pkgPath), err);
|
|
4942
5640
|
return null;
|
|
4943
5641
|
}
|
|
4944
5642
|
if (!pkg.name) return null;
|
|
4945
5643
|
const framework = detectJsFramework(pkg);
|
|
4946
5644
|
const language = await detectJsServiceLanguage(dir, pkg);
|
|
4947
5645
|
const node = {
|
|
4948
|
-
id: (0,
|
|
4949
|
-
type:
|
|
5646
|
+
id: (0, import_types10.serviceId)(pkg.name),
|
|
5647
|
+
type: import_types10.NodeType.ServiceNode,
|
|
4950
5648
|
name: pkg.name,
|
|
4951
5649
|
language,
|
|
4952
5650
|
version: pkg.version,
|
|
4953
5651
|
dependencies: pkg.dependencies ?? {},
|
|
4954
|
-
repoPath:
|
|
5652
|
+
repoPath: import_node_path12.default.relative(scanPath, dir),
|
|
4955
5653
|
...pkg.engines?.node ? { nodeEngine: pkg.engines.node } : {},
|
|
4956
5654
|
...framework ? { framework } : {}
|
|
4957
5655
|
};
|
|
@@ -4962,18 +5660,18 @@ async function discoverPyService(scanPath, dir) {
|
|
|
4962
5660
|
if (!py) return null;
|
|
4963
5661
|
const pkg = pythonToPackage(py);
|
|
4964
5662
|
const node = {
|
|
4965
|
-
id: (0,
|
|
4966
|
-
type:
|
|
5663
|
+
id: (0, import_types10.serviceId)(py.name),
|
|
5664
|
+
type: import_types10.NodeType.ServiceNode,
|
|
4967
5665
|
name: py.name,
|
|
4968
5666
|
language: "python",
|
|
4969
5667
|
version: py.version,
|
|
4970
5668
|
dependencies: py.dependencies,
|
|
4971
|
-
repoPath:
|
|
5669
|
+
repoPath: import_node_path12.default.relative(scanPath, dir)
|
|
4972
5670
|
};
|
|
4973
5671
|
return { pkg, dir, node };
|
|
4974
5672
|
}
|
|
4975
5673
|
async function discoverServices(scanPath) {
|
|
4976
|
-
const rootPkgPath =
|
|
5674
|
+
const rootPkgPath = import_node_path12.default.join(scanPath, "package.json");
|
|
4977
5675
|
let rootPkg = null;
|
|
4978
5676
|
if (await exists(rootPkgPath)) {
|
|
4979
5677
|
try {
|
|
@@ -4981,7 +5679,7 @@ async function discoverServices(scanPath) {
|
|
|
4981
5679
|
} catch (err) {
|
|
4982
5680
|
recordExtractionError(
|
|
4983
5681
|
"services workspaces",
|
|
4984
|
-
|
|
5682
|
+
import_node_path12.default.relative(scanPath, rootPkgPath),
|
|
4985
5683
|
err
|
|
4986
5684
|
);
|
|
4987
5685
|
}
|
|
@@ -5002,7 +5700,7 @@ async function discoverServices(scanPath) {
|
|
|
5002
5700
|
scanPath,
|
|
5003
5701
|
{ maxDepth: parseScanDepth(), ig },
|
|
5004
5702
|
async (dir) => {
|
|
5005
|
-
if (await exists(
|
|
5703
|
+
if (await exists(import_node_path12.default.join(dir, "package.json"))) {
|
|
5006
5704
|
candidateDirs.push(dir);
|
|
5007
5705
|
} else if (await hasPythonManifest(dir) || await hasGoManifest(dir)) {
|
|
5008
5706
|
candidateDirs.push(dir);
|
|
@@ -5018,8 +5716,8 @@ async function discoverServices(scanPath) {
|
|
|
5018
5716
|
if (!service) continue;
|
|
5019
5717
|
const existingDir = seen.get(service.node.name);
|
|
5020
5718
|
if (existingDir !== void 0) {
|
|
5021
|
-
const a =
|
|
5022
|
-
const b =
|
|
5719
|
+
const a = import_node_path12.default.relative(scanPath, existingDir) || ".";
|
|
5720
|
+
const b = import_node_path12.default.relative(scanPath, dir) || ".";
|
|
5023
5721
|
console.warn(
|
|
5024
5722
|
`[neat] duplicate package name "${service.node.name}" \u2014 keeping ${a}, ignoring ${b}`
|
|
5025
5723
|
);
|
|
@@ -5056,10 +5754,10 @@ function addServiceNodes(graph, services) {
|
|
|
5056
5754
|
|
|
5057
5755
|
// src/extract/aliases.ts
|
|
5058
5756
|
init_cjs_shims();
|
|
5059
|
-
var
|
|
5060
|
-
var
|
|
5757
|
+
var import_node_path13 = __toESM(require("path"), 1);
|
|
5758
|
+
var import_node_fs12 = require("fs");
|
|
5061
5759
|
var import_yaml2 = require("yaml");
|
|
5062
|
-
var
|
|
5760
|
+
var import_types11 = require("@neat.is/types");
|
|
5063
5761
|
var K8S_KINDS_WITH_HOSTNAMES = /* @__PURE__ */ new Set([
|
|
5064
5762
|
"Service",
|
|
5065
5763
|
"Deployment",
|
|
@@ -5069,7 +5767,7 @@ var K8S_KINDS_WITH_HOSTNAMES = /* @__PURE__ */ new Set([
|
|
|
5069
5767
|
function addAliases(graph, serviceId7, candidates) {
|
|
5070
5768
|
if (!graph.hasNode(serviceId7)) return;
|
|
5071
5769
|
const node = graph.getNodeAttributes(serviceId7);
|
|
5072
|
-
if (node.type !==
|
|
5770
|
+
if (node.type !== import_types11.NodeType.ServiceNode) return;
|
|
5073
5771
|
const set = new Set(node.aliases ?? []);
|
|
5074
5772
|
for (const c of candidates) {
|
|
5075
5773
|
if (!c) continue;
|
|
@@ -5084,14 +5782,14 @@ function indexServicesByName(services) {
|
|
|
5084
5782
|
const map = /* @__PURE__ */ new Map();
|
|
5085
5783
|
for (const s of services) {
|
|
5086
5784
|
map.set(s.node.name, s.node.id);
|
|
5087
|
-
map.set(
|
|
5785
|
+
map.set(import_node_path13.default.basename(s.dir), s.node.id);
|
|
5088
5786
|
}
|
|
5089
5787
|
return map;
|
|
5090
5788
|
}
|
|
5091
5789
|
async function collectComposeAliases(graph, scanPath, serviceIndex) {
|
|
5092
5790
|
let composePath = null;
|
|
5093
5791
|
for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
|
|
5094
|
-
const abs =
|
|
5792
|
+
const abs = import_node_path13.default.join(scanPath, name);
|
|
5095
5793
|
if (await exists(abs)) {
|
|
5096
5794
|
composePath = abs;
|
|
5097
5795
|
break;
|
|
@@ -5104,7 +5802,7 @@ async function collectComposeAliases(graph, scanPath, serviceIndex) {
|
|
|
5104
5802
|
} catch (err) {
|
|
5105
5803
|
recordExtractionError(
|
|
5106
5804
|
"aliases compose",
|
|
5107
|
-
|
|
5805
|
+
import_node_path13.default.relative(scanPath, composePath),
|
|
5108
5806
|
err
|
|
5109
5807
|
);
|
|
5110
5808
|
return;
|
|
@@ -5147,11 +5845,11 @@ function parseDockerfileLabels(content) {
|
|
|
5147
5845
|
}
|
|
5148
5846
|
async function collectDockerfileAliases(graph, services) {
|
|
5149
5847
|
for (const service of services) {
|
|
5150
|
-
const dockerfilePath =
|
|
5848
|
+
const dockerfilePath = import_node_path13.default.join(service.dir, "Dockerfile");
|
|
5151
5849
|
if (!await exists(dockerfilePath)) continue;
|
|
5152
5850
|
let content;
|
|
5153
5851
|
try {
|
|
5154
|
-
content = await
|
|
5852
|
+
content = await import_node_fs12.promises.readFile(dockerfilePath, "utf8");
|
|
5155
5853
|
} catch (err) {
|
|
5156
5854
|
recordExtractionError("aliases dockerfile", dockerfilePath, err);
|
|
5157
5855
|
continue;
|
|
@@ -5163,15 +5861,15 @@ async function collectDockerfileAliases(graph, services) {
|
|
|
5163
5861
|
async function walkYamlFiles(start, depth = 0, max = 5) {
|
|
5164
5862
|
if (depth > max) return [];
|
|
5165
5863
|
const out = [];
|
|
5166
|
-
const entries = await
|
|
5864
|
+
const entries = await import_node_fs12.promises.readdir(start, { withFileTypes: true }).catch(() => []);
|
|
5167
5865
|
for (const entry of entries) {
|
|
5168
5866
|
if (entry.isDirectory()) {
|
|
5169
5867
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
5170
|
-
const child =
|
|
5868
|
+
const child = import_node_path13.default.join(start, entry.name);
|
|
5171
5869
|
if (await isPythonVenvDir(child)) continue;
|
|
5172
5870
|
out.push(...await walkYamlFiles(child, depth + 1, max));
|
|
5173
|
-
} else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(
|
|
5174
|
-
out.push(
|
|
5871
|
+
} else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path13.default.extname(entry.name))) {
|
|
5872
|
+
out.push(import_node_path13.default.join(start, entry.name));
|
|
5175
5873
|
}
|
|
5176
5874
|
}
|
|
5177
5875
|
return out;
|
|
@@ -5198,614 +5896,230 @@ function k8sServiceTarget(doc, byName) {
|
|
|
5198
5896
|
async function collectK8sAliases(graph, scanPath, serviceIndex) {
|
|
5199
5897
|
const files = await walkYamlFiles(scanPath);
|
|
5200
5898
|
for (const file of files) {
|
|
5201
|
-
const content = await
|
|
5899
|
+
const content = await import_node_fs12.promises.readFile(file, "utf8");
|
|
5202
5900
|
let docs;
|
|
5203
5901
|
try {
|
|
5204
|
-
docs = (0, import_yaml2.parseAllDocuments)(content).map((d) => d.toJSON());
|
|
5205
|
-
} catch {
|
|
5206
|
-
continue;
|
|
5207
|
-
}
|
|
5208
|
-
for (const doc of docs) {
|
|
5209
|
-
if (!doc?.kind || !doc.metadata?.name) continue;
|
|
5210
|
-
if (!K8S_KINDS_WITH_HOSTNAMES.has(doc.kind)) continue;
|
|
5211
|
-
const target = k8sServiceTarget(doc, serviceIndex);
|
|
5212
|
-
if (!target) continue;
|
|
5213
|
-
addAliases(graph, target, k8sHostnames(doc.metadata.name, doc.metadata.namespace));
|
|
5214
|
-
}
|
|
5215
|
-
}
|
|
5216
|
-
}
|
|
5217
|
-
async function addServiceAliases(graph, scanPath, services) {
|
|
5218
|
-
const byName = indexServicesByName(services);
|
|
5219
|
-
await collectComposeAliases(graph, scanPath, byName);
|
|
5220
|
-
await collectDockerfileAliases(graph, services);
|
|
5221
|
-
await collectK8sAliases(graph, scanPath, byName);
|
|
5222
|
-
}
|
|
5223
|
-
|
|
5224
|
-
// src/extract/files.ts
|
|
5225
|
-
init_cjs_shims();
|
|
5226
|
-
var import_node_path13 = __toESM(require("path"), 1);
|
|
5227
|
-
async function addFiles(graph, services) {
|
|
5228
|
-
let nodesAdded = 0;
|
|
5229
|
-
let edgesAdded = 0;
|
|
5230
|
-
for (const service of services) {
|
|
5231
|
-
const filePaths = await walkSourceFiles(service.dir);
|
|
5232
|
-
for (const filePath of filePaths) {
|
|
5233
|
-
const relPath = toPosix(import_node_path13.default.relative(service.dir, filePath));
|
|
5234
|
-
const { nodesAdded: n, edgesAdded: e } = ensureFileNode(
|
|
5235
|
-
graph,
|
|
5236
|
-
service.pkg.name,
|
|
5237
|
-
service.node.id,
|
|
5238
|
-
relPath
|
|
5239
|
-
);
|
|
5240
|
-
nodesAdded += n;
|
|
5241
|
-
edgesAdded += e;
|
|
5242
|
-
}
|
|
5243
|
-
}
|
|
5244
|
-
return { nodesAdded, edgesAdded };
|
|
5245
|
-
}
|
|
5246
|
-
|
|
5247
|
-
// src/extract/symbols.ts
|
|
5248
|
-
init_cjs_shims();
|
|
5249
|
-
var import_node_path14 = __toESM(require("path"), 1);
|
|
5250
|
-
var import_tree_sitter2 = __toESM(require("tree-sitter"), 1);
|
|
5251
|
-
var import_tree_sitter_javascript2 = __toESM(require("tree-sitter-javascript"), 1);
|
|
5252
|
-
var import_tree_sitter_typescript = __toESM(require("tree-sitter-typescript"), 1);
|
|
5253
|
-
var import_types11 = require("@neat.is/types");
|
|
5254
|
-
var PARSE_CHUNK2 = 16384;
|
|
5255
|
-
var GRAMMAR_BY_EXT = {
|
|
5256
|
-
".ts": import_tree_sitter_typescript.default.typescript,
|
|
5257
|
-
".tsx": import_tree_sitter_typescript.default.tsx,
|
|
5258
|
-
".js": import_tree_sitter_javascript2.default,
|
|
5259
|
-
".jsx": import_tree_sitter_javascript2.default,
|
|
5260
|
-
".mjs": import_tree_sitter_javascript2.default,
|
|
5261
|
-
".cjs": import_tree_sitter_javascript2.default
|
|
5262
|
-
};
|
|
5263
|
-
function parseSource2(parser, source) {
|
|
5264
|
-
return parser.parse(
|
|
5265
|
-
(index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK2)
|
|
5266
|
-
);
|
|
5267
|
-
}
|
|
5268
|
-
function methodName(node) {
|
|
5269
|
-
const name = node.childForFieldName("name");
|
|
5270
|
-
return name ? name.text : null;
|
|
5271
|
-
}
|
|
5272
|
-
function collectSymbolDefs(root) {
|
|
5273
|
-
const out = [];
|
|
5274
|
-
const push = (kind, qualname, node) => {
|
|
5275
|
-
out.push({
|
|
5276
|
-
kind,
|
|
5277
|
-
qualname,
|
|
5278
|
-
startLine: node.startPosition.row + 1,
|
|
5279
|
-
endLine: node.endPosition.row + 1
|
|
5280
|
-
});
|
|
5281
|
-
};
|
|
5282
|
-
const visit = (node, classCtx) => {
|
|
5283
|
-
switch (node.type) {
|
|
5284
|
-
case "function_declaration":
|
|
5285
|
-
case "generator_function_declaration": {
|
|
5286
|
-
const name = node.childForFieldName("name")?.text;
|
|
5287
|
-
if (name) push("function", name, node);
|
|
5288
|
-
break;
|
|
5289
|
-
}
|
|
5290
|
-
case "class_declaration":
|
|
5291
|
-
case "abstract_class_declaration":
|
|
5292
|
-
case "class": {
|
|
5293
|
-
const name = node.childForFieldName("name")?.text;
|
|
5294
|
-
if (name) push("class", name, node);
|
|
5295
|
-
const body = node.childForFieldName("body");
|
|
5296
|
-
if (body) {
|
|
5297
|
-
for (let i = 0; i < body.namedChildCount; i++) {
|
|
5298
|
-
const child = body.namedChild(i);
|
|
5299
|
-
if (child) visit(child, name ?? classCtx);
|
|
5300
|
-
}
|
|
5301
|
-
}
|
|
5302
|
-
return;
|
|
5303
|
-
}
|
|
5304
|
-
case "method_definition": {
|
|
5305
|
-
const name = methodName(node);
|
|
5306
|
-
if (name) {
|
|
5307
|
-
const kind = name === "constructor" ? "constructor" : "method";
|
|
5308
|
-
push(kind, classCtx ? `${classCtx}.${name}` : name, node);
|
|
5309
|
-
}
|
|
5310
|
-
break;
|
|
5311
|
-
}
|
|
5312
|
-
case "variable_declarator": {
|
|
5313
|
-
const value = node.childForFieldName("value");
|
|
5314
|
-
if (value && (value.type === "arrow_function" || value.type === "function" || value.type === "function_expression" || value.type === "generator_function")) {
|
|
5315
|
-
const nameNode = node.childForFieldName("name");
|
|
5316
|
-
if (nameNode && nameNode.type === "identifier") {
|
|
5317
|
-
push("function", nameNode.text, node);
|
|
5318
|
-
}
|
|
5319
|
-
}
|
|
5320
|
-
break;
|
|
5321
|
-
}
|
|
5322
|
-
}
|
|
5323
|
-
for (let i = 0; i < node.namedChildCount; i++) {
|
|
5324
|
-
const child = node.namedChild(i);
|
|
5325
|
-
if (child) visit(child, classCtx);
|
|
5326
|
-
}
|
|
5327
|
-
};
|
|
5328
|
-
visit(root, void 0);
|
|
5329
|
-
return out;
|
|
5330
|
-
}
|
|
5331
|
-
function disambiguate(defs) {
|
|
5332
|
-
const counts = /* @__PURE__ */ new Map();
|
|
5333
|
-
for (const def of defs) counts.set(def.qualname, (counts.get(def.qualname) ?? 0) + 1);
|
|
5334
|
-
const seen = /* @__PURE__ */ new Map();
|
|
5335
|
-
return defs.map((def) => {
|
|
5336
|
-
if ((counts.get(def.qualname) ?? 0) <= 1) return { def };
|
|
5337
|
-
const ordinal = seen.get(def.qualname) ?? 0;
|
|
5338
|
-
seen.set(def.qualname, ordinal + 1);
|
|
5339
|
-
return { def, disambiguator: ordinal };
|
|
5340
|
-
});
|
|
5341
|
-
}
|
|
5342
|
-
async function addSymbols(graph, services) {
|
|
5343
|
-
const parsers = /* @__PURE__ */ new Map();
|
|
5344
|
-
const parserForExt2 = (ext) => {
|
|
5345
|
-
const grammar = GRAMMAR_BY_EXT[ext];
|
|
5346
|
-
if (!grammar) return null;
|
|
5347
|
-
let parser = parsers.get(ext);
|
|
5348
|
-
if (!parser) {
|
|
5349
|
-
parser = new import_tree_sitter2.default();
|
|
5350
|
-
parser.setLanguage(grammar);
|
|
5351
|
-
parsers.set(ext, parser);
|
|
5902
|
+
docs = (0, import_yaml2.parseAllDocuments)(content).map((d) => d.toJSON());
|
|
5903
|
+
} catch {
|
|
5904
|
+
continue;
|
|
5352
5905
|
}
|
|
5353
|
-
|
|
5354
|
-
|
|
5906
|
+
for (const doc of docs) {
|
|
5907
|
+
if (!doc?.kind || !doc.metadata?.name) continue;
|
|
5908
|
+
if (!K8S_KINDS_WITH_HOSTNAMES.has(doc.kind)) continue;
|
|
5909
|
+
const target = k8sServiceTarget(doc, serviceIndex);
|
|
5910
|
+
if (!target) continue;
|
|
5911
|
+
addAliases(graph, target, k8sHostnames(doc.metadata.name, doc.metadata.namespace));
|
|
5912
|
+
}
|
|
5913
|
+
}
|
|
5914
|
+
}
|
|
5915
|
+
async function addServiceAliases(graph, scanPath, services) {
|
|
5916
|
+
const byName = indexServicesByName(services);
|
|
5917
|
+
await collectComposeAliases(graph, scanPath, byName);
|
|
5918
|
+
await collectDockerfileAliases(graph, services);
|
|
5919
|
+
await collectK8sAliases(graph, scanPath, byName);
|
|
5920
|
+
}
|
|
5921
|
+
|
|
5922
|
+
// src/extract/files.ts
|
|
5923
|
+
init_cjs_shims();
|
|
5924
|
+
var import_node_path14 = __toESM(require("path"), 1);
|
|
5925
|
+
async function addFiles(graph, services) {
|
|
5355
5926
|
let nodesAdded = 0;
|
|
5356
5927
|
let edgesAdded = 0;
|
|
5357
5928
|
for (const service of services) {
|
|
5358
|
-
const
|
|
5359
|
-
for (const
|
|
5360
|
-
const
|
|
5361
|
-
|
|
5362
|
-
const relPath = toPosix(import_node_path14.default.relative(service.dir, file.path));
|
|
5363
|
-
let defs;
|
|
5364
|
-
try {
|
|
5365
|
-
const tree = parseSource2(parser, file.content);
|
|
5366
|
-
defs = collectSymbolDefs(tree.rootNode);
|
|
5367
|
-
} catch (err) {
|
|
5368
|
-
recordExtractionError("symbol extraction", file.path, err);
|
|
5369
|
-
continue;
|
|
5370
|
-
}
|
|
5371
|
-
if (defs.length === 0) continue;
|
|
5372
|
-
const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
|
|
5929
|
+
const filePaths = await walkSourceFiles(service.dir);
|
|
5930
|
+
for (const filePath of filePaths) {
|
|
5931
|
+
const relPath = toPosix(import_node_path14.default.relative(service.dir, filePath));
|
|
5932
|
+
const { nodesAdded: n, edgesAdded: e } = ensureFileNode(
|
|
5373
5933
|
graph,
|
|
5374
5934
|
service.pkg.name,
|
|
5375
5935
|
service.node.id,
|
|
5376
5936
|
relPath
|
|
5377
5937
|
);
|
|
5378
|
-
nodesAdded +=
|
|
5379
|
-
edgesAdded +=
|
|
5380
|
-
for (const { def, disambiguator } of disambiguate(defs)) {
|
|
5381
|
-
const sid = (0, import_types11.symbolId)(service.pkg.name, relPath, def.qualname, disambiguator);
|
|
5382
|
-
if (!graph.hasNode(sid)) {
|
|
5383
|
-
const node = {
|
|
5384
|
-
id: sid,
|
|
5385
|
-
type: import_types11.NodeType.SymbolNode,
|
|
5386
|
-
kind: def.kind,
|
|
5387
|
-
qualname: def.qualname,
|
|
5388
|
-
span: { startLine: def.startLine, endLine: def.endLine },
|
|
5389
|
-
service: service.pkg.name,
|
|
5390
|
-
relPath,
|
|
5391
|
-
discoveredVia: "static"
|
|
5392
|
-
};
|
|
5393
|
-
graph.addNode(sid, node);
|
|
5394
|
-
nodesAdded++;
|
|
5395
|
-
}
|
|
5396
|
-
const containsId = (0, import_types11.extractedEdgeId)(fileNodeId, sid, import_types11.EdgeType.CONTAINS);
|
|
5397
|
-
if (!graph.hasEdge(containsId)) {
|
|
5398
|
-
const edge = {
|
|
5399
|
-
id: containsId,
|
|
5400
|
-
source: fileNodeId,
|
|
5401
|
-
target: sid,
|
|
5402
|
-
type: import_types11.EdgeType.CONTAINS,
|
|
5403
|
-
provenance: import_types11.Provenance.EXTRACTED,
|
|
5404
|
-
confidence: (0, import_types11.confidenceForExtracted)("structural"),
|
|
5405
|
-
evidence: {
|
|
5406
|
-
file: relPath,
|
|
5407
|
-
line: def.startLine,
|
|
5408
|
-
snippet: snippet(file.content, def.startLine)
|
|
5409
|
-
}
|
|
5410
|
-
};
|
|
5411
|
-
graph.addEdgeWithKey(containsId, fileNodeId, sid, edge);
|
|
5412
|
-
edgesAdded++;
|
|
5413
|
-
}
|
|
5414
|
-
}
|
|
5938
|
+
nodesAdded += n;
|
|
5939
|
+
edgesAdded += e;
|
|
5415
5940
|
}
|
|
5416
5941
|
}
|
|
5417
5942
|
return { nodesAdded, edgesAdded };
|
|
5418
5943
|
}
|
|
5419
5944
|
|
|
5420
|
-
// src/extract/
|
|
5421
|
-
init_cjs_shims();
|
|
5422
|
-
var import_node_path16 = __toESM(require("path"), 1);
|
|
5423
|
-
var import_tree_sitter4 = __toESM(require("tree-sitter"), 1);
|
|
5424
|
-
var import_types13 = require("@neat.is/types");
|
|
5425
|
-
|
|
5426
|
-
// src/extract/imports.ts
|
|
5945
|
+
// src/extract/symbols.ts
|
|
5427
5946
|
init_cjs_shims();
|
|
5428
5947
|
var import_node_path15 = __toESM(require("path"), 1);
|
|
5429
|
-
var import_node_fs12 = require("fs");
|
|
5430
5948
|
var import_tree_sitter3 = __toESM(require("tree-sitter"), 1);
|
|
5431
5949
|
var import_tree_sitter_javascript3 = __toESM(require("tree-sitter-javascript"), 1);
|
|
5432
|
-
var
|
|
5433
|
-
var import_tree_sitter_go2 = __toESM(require("tree-sitter-go"), 1);
|
|
5950
|
+
var import_tree_sitter_typescript = __toESM(require("tree-sitter-typescript"), 1);
|
|
5434
5951
|
var import_types12 = require("@neat.is/types");
|
|
5435
5952
|
var PARSE_CHUNK3 = 16384;
|
|
5953
|
+
var GRAMMAR_BY_EXT = {
|
|
5954
|
+
".ts": import_tree_sitter_typescript.default.typescript,
|
|
5955
|
+
".tsx": import_tree_sitter_typescript.default.tsx,
|
|
5956
|
+
".js": import_tree_sitter_javascript3.default,
|
|
5957
|
+
".jsx": import_tree_sitter_javascript3.default,
|
|
5958
|
+
".mjs": import_tree_sitter_javascript3.default,
|
|
5959
|
+
".cjs": import_tree_sitter_javascript3.default
|
|
5960
|
+
};
|
|
5436
5961
|
function parseSource3(parser, source) {
|
|
5437
5962
|
return parser.parse(
|
|
5438
5963
|
(index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK3)
|
|
5439
|
-
);
|
|
5440
|
-
}
|
|
5441
|
-
function makeJsParser2() {
|
|
5442
|
-
const p = new import_tree_sitter3.default();
|
|
5443
|
-
p.setLanguage(import_tree_sitter_javascript3.default);
|
|
5444
|
-
return p;
|
|
5445
|
-
}
|
|
5446
|
-
function makePyParser2() {
|
|
5447
|
-
const p = new import_tree_sitter3.default();
|
|
5448
|
-
p.setLanguage(import_tree_sitter_python2.default);
|
|
5449
|
-
return p;
|
|
5450
|
-
}
|
|
5451
|
-
function makeGoParser2() {
|
|
5452
|
-
const p = new import_tree_sitter3.default();
|
|
5453
|
-
p.setLanguage(import_tree_sitter_go2.default);
|
|
5454
|
-
return p;
|
|
5455
|
-
}
|
|
5456
|
-
function stringLiteralText(node) {
|
|
5457
|
-
for (let i = 0; i < node.childCount; i++) {
|
|
5458
|
-
const child = node.child(i);
|
|
5459
|
-
if (child?.type === "string_fragment") return child.text;
|
|
5460
|
-
}
|
|
5461
|
-
const raw = node.text;
|
|
5462
|
-
if (raw.length >= 2) return raw.slice(1, -1);
|
|
5463
|
-
return raw.length === 0 ? null : "";
|
|
5464
|
-
}
|
|
5465
|
-
function clipSnippet(text) {
|
|
5466
|
-
const oneLine = text.split("\n")[0] ?? text;
|
|
5467
|
-
return oneLine.length > 120 ? oneLine.slice(0, 120) : oneLine;
|
|
5468
|
-
}
|
|
5469
|
-
function collectGoImports(node, out) {
|
|
5470
|
-
if (node.type === "import_spec") {
|
|
5471
|
-
const pathNode = node.childForFieldName("path");
|
|
5472
|
-
if (pathNode) {
|
|
5473
|
-
const specifier = pathNode.text.replace(/^`|`$/g, "").replace(/^"|"$/g, "");
|
|
5474
|
-
if (specifier) out.push({ specifier, line: node.startPosition.row + 1, snippet: clipSnippet(node.text) });
|
|
5475
|
-
}
|
|
5476
|
-
return;
|
|
5477
|
-
}
|
|
5478
|
-
for (let i = 0; i < node.namedChildCount; i++) {
|
|
5479
|
-
const child = node.namedChild(i);
|
|
5480
|
-
if (child) collectGoImports(child, out);
|
|
5481
|
-
}
|
|
5482
|
-
}
|
|
5483
|
-
function collectJsImports(node, out) {
|
|
5484
|
-
if (node.type === "import_statement") {
|
|
5485
|
-
const source = node.childForFieldName("source");
|
|
5486
|
-
if (source) {
|
|
5487
|
-
const specifier = stringLiteralText(source);
|
|
5488
|
-
if (specifier) {
|
|
5489
|
-
out.push({ specifier, line: node.startPosition.row + 1, snippet: clipSnippet(node.text) });
|
|
5490
|
-
}
|
|
5491
|
-
}
|
|
5492
|
-
return;
|
|
5493
|
-
}
|
|
5494
|
-
if (node.type === "call_expression") {
|
|
5495
|
-
const fn = node.childForFieldName("function");
|
|
5496
|
-
if (fn?.type === "identifier" && fn.text === "require") {
|
|
5497
|
-
const args = node.childForFieldName("arguments");
|
|
5498
|
-
const firstArg = args?.namedChild(0);
|
|
5499
|
-
if (firstArg?.type === "string") {
|
|
5500
|
-
const specifier = stringLiteralText(firstArg);
|
|
5501
|
-
if (specifier) {
|
|
5502
|
-
out.push({ specifier, line: node.startPosition.row + 1, snippet: clipSnippet(node.text) });
|
|
5503
|
-
}
|
|
5504
|
-
}
|
|
5505
|
-
}
|
|
5506
|
-
}
|
|
5507
|
-
for (let i = 0; i < node.namedChildCount; i++) {
|
|
5508
|
-
const child = node.namedChild(i);
|
|
5509
|
-
if (child) collectJsImports(child, out);
|
|
5510
|
-
}
|
|
5511
|
-
}
|
|
5512
|
-
function collectImportedNames(node, out) {
|
|
5513
|
-
if (node.type === "aliased_import") {
|
|
5514
|
-
const nameNode = node.childForFieldName("name");
|
|
5515
|
-
if (nameNode) out.push(nameNode.text);
|
|
5516
|
-
return;
|
|
5517
|
-
}
|
|
5518
|
-
if (node.type === "dotted_name") {
|
|
5519
|
-
out.push(node.text);
|
|
5520
|
-
return;
|
|
5521
|
-
}
|
|
5522
|
-
for (let i = 0; i < node.namedChildCount; i++) {
|
|
5523
|
-
const child = node.namedChild(i);
|
|
5524
|
-
if (child) collectImportedNames(child, out);
|
|
5525
|
-
}
|
|
5526
|
-
}
|
|
5527
|
-
function collectPyImports(node, out) {
|
|
5528
|
-
if (node.type === "import_from_statement") {
|
|
5529
|
-
let level = 0;
|
|
5530
|
-
let modulePath = "";
|
|
5531
|
-
const names = [];
|
|
5532
|
-
let pastFrom = false;
|
|
5533
|
-
let pastImport = false;
|
|
5534
|
-
for (let i = 0; i < node.childCount; i++) {
|
|
5535
|
-
const child = node.child(i);
|
|
5536
|
-
if (!child) continue;
|
|
5537
|
-
if (!pastFrom) {
|
|
5538
|
-
if (child.type === "from") pastFrom = true;
|
|
5539
|
-
continue;
|
|
5540
|
-
}
|
|
5541
|
-
if (!pastImport) {
|
|
5542
|
-
if (child.type === "import") {
|
|
5543
|
-
pastImport = true;
|
|
5544
|
-
continue;
|
|
5545
|
-
}
|
|
5546
|
-
if (child.type === "relative_import") {
|
|
5547
|
-
for (let j = 0; j < child.childCount; j++) {
|
|
5548
|
-
const rc = child.child(j);
|
|
5549
|
-
if (!rc) continue;
|
|
5550
|
-
if (rc.type === "import_prefix") {
|
|
5551
|
-
for (let k = 0; k < rc.childCount; k++) {
|
|
5552
|
-
if (rc.child(k)?.type === ".") level++;
|
|
5553
|
-
}
|
|
5554
|
-
} else if (rc.type === "dotted_name") modulePath = rc.text;
|
|
5555
|
-
}
|
|
5556
|
-
} else if (child.type === "dotted_name") {
|
|
5557
|
-
modulePath = child.text;
|
|
5558
|
-
}
|
|
5559
|
-
continue;
|
|
5560
|
-
}
|
|
5561
|
-
collectImportedNames(child, names);
|
|
5562
|
-
}
|
|
5563
|
-
if (level > 0 || modulePath) {
|
|
5564
|
-
out.push({ modulePath, level, names, line: node.startPosition.row + 1, snippet: clipSnippet(node.text) });
|
|
5565
|
-
}
|
|
5566
|
-
}
|
|
5567
|
-
for (let i = 0; i < node.namedChildCount; i++) {
|
|
5568
|
-
const child = node.namedChild(i);
|
|
5569
|
-
if (child) collectPyImports(child, out);
|
|
5570
|
-
}
|
|
5571
|
-
}
|
|
5572
|
-
async function fileExists(p) {
|
|
5573
|
-
try {
|
|
5574
|
-
await import_node_fs12.promises.access(p);
|
|
5575
|
-
return true;
|
|
5576
|
-
} catch {
|
|
5577
|
-
return false;
|
|
5578
|
-
}
|
|
5579
|
-
}
|
|
5580
|
-
function isWithinServiceDir(candidate, serviceDir) {
|
|
5581
|
-
const rel = import_node_path15.default.relative(serviceDir, candidate);
|
|
5582
|
-
return rel !== "" && !rel.startsWith("..") && !import_node_path15.default.isAbsolute(rel);
|
|
5583
|
-
}
|
|
5584
|
-
var JS_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"];
|
|
5585
|
-
var JS_INDEX_FILES = JS_EXTENSIONS.map((ext) => `index${ext}`);
|
|
5586
|
-
async function firstExistingCandidate(base, serviceDir) {
|
|
5587
|
-
for (const ext of JS_EXTENSIONS) {
|
|
5588
|
-
const candidate = base + ext;
|
|
5589
|
-
if (isWithinServiceDir(candidate, serviceDir) && await fileExists(candidate)) {
|
|
5590
|
-
return toPosix(import_node_path15.default.relative(serviceDir, candidate));
|
|
5591
|
-
}
|
|
5592
|
-
}
|
|
5593
|
-
for (const indexFile of JS_INDEX_FILES) {
|
|
5594
|
-
const candidate = import_node_path15.default.join(base, indexFile);
|
|
5595
|
-
if (isWithinServiceDir(candidate, serviceDir) && await fileExists(candidate)) {
|
|
5596
|
-
return toPosix(import_node_path15.default.relative(serviceDir, candidate));
|
|
5597
|
-
}
|
|
5598
|
-
}
|
|
5599
|
-
return null;
|
|
5600
|
-
}
|
|
5601
|
-
async function loadTsPathConfig(serviceDir) {
|
|
5602
|
-
const tsconfigPath = import_node_path15.default.join(serviceDir, "tsconfig.json");
|
|
5603
|
-
let raw;
|
|
5604
|
-
try {
|
|
5605
|
-
raw = await import_node_fs12.promises.readFile(tsconfigPath, "utf8");
|
|
5606
|
-
} catch {
|
|
5607
|
-
return null;
|
|
5608
|
-
}
|
|
5609
|
-
try {
|
|
5610
|
-
const parsed = JSON.parse(raw);
|
|
5611
|
-
const paths = parsed.compilerOptions?.paths;
|
|
5612
|
-
if (!paths || Object.keys(paths).length === 0) return null;
|
|
5613
|
-
const baseUrl = parsed.compilerOptions?.baseUrl;
|
|
5614
|
-
return { paths, baseDir: baseUrl ? import_node_path15.default.resolve(serviceDir, baseUrl) : serviceDir };
|
|
5615
|
-
} catch (err) {
|
|
5616
|
-
recordExtractionError("import alias resolution", tsconfigPath, err);
|
|
5617
|
-
return null;
|
|
5618
|
-
}
|
|
5964
|
+
);
|
|
5619
5965
|
}
|
|
5620
|
-
|
|
5621
|
-
|
|
5622
|
-
|
|
5623
|
-
if (pattern === specifier) {
|
|
5624
|
-
suffix = "";
|
|
5625
|
-
} else if (pattern.endsWith("/*")) {
|
|
5626
|
-
const prefix = pattern.slice(0, -1);
|
|
5627
|
-
if (specifier.startsWith(prefix)) suffix = specifier.slice(prefix.length);
|
|
5628
|
-
}
|
|
5629
|
-
if (suffix === null) continue;
|
|
5630
|
-
for (const target of targets) {
|
|
5631
|
-
const targetBase = target.endsWith("/*") ? target.slice(0, -2) : target.replace(/\*$/, "");
|
|
5632
|
-
const resolvedBase = import_node_path15.default.resolve(config.baseDir, targetBase, suffix);
|
|
5633
|
-
const hit = await firstExistingCandidate(resolvedBase, serviceDir);
|
|
5634
|
-
if (hit) return hit;
|
|
5635
|
-
if (isWithinServiceDir(resolvedBase, serviceDir) && await fileExists(resolvedBase)) {
|
|
5636
|
-
return toPosix(import_node_path15.default.relative(serviceDir, resolvedBase));
|
|
5637
|
-
}
|
|
5638
|
-
}
|
|
5639
|
-
}
|
|
5640
|
-
return null;
|
|
5966
|
+
function methodName(node) {
|
|
5967
|
+
const name = node.childForFieldName("name");
|
|
5968
|
+
return name ? name.text : null;
|
|
5641
5969
|
}
|
|
5642
|
-
|
|
5643
|
-
|
|
5644
|
-
|
|
5645
|
-
|
|
5646
|
-
|
|
5647
|
-
|
|
5648
|
-
|
|
5649
|
-
|
|
5650
|
-
|
|
5651
|
-
|
|
5652
|
-
|
|
5970
|
+
function collectSymbolDefs(root) {
|
|
5971
|
+
const out = [];
|
|
5972
|
+
const push = (kind, qualname, node) => {
|
|
5973
|
+
out.push({
|
|
5974
|
+
kind,
|
|
5975
|
+
qualname,
|
|
5976
|
+
startLine: node.startPosition.row + 1,
|
|
5977
|
+
endLine: node.endPosition.row + 1
|
|
5978
|
+
});
|
|
5979
|
+
};
|
|
5980
|
+
const visit = (node, classCtx) => {
|
|
5981
|
+
switch (node.type) {
|
|
5982
|
+
case "function_declaration":
|
|
5983
|
+
case "generator_function_declaration": {
|
|
5984
|
+
const name = node.childForFieldName("name")?.text;
|
|
5985
|
+
if (name) push("function", name, node);
|
|
5986
|
+
break;
|
|
5987
|
+
}
|
|
5988
|
+
case "class_declaration":
|
|
5989
|
+
case "abstract_class_declaration":
|
|
5990
|
+
case "class": {
|
|
5991
|
+
const name = node.childForFieldName("name")?.text;
|
|
5992
|
+
if (name) push("class", name, node);
|
|
5993
|
+
const body = node.childForFieldName("body");
|
|
5994
|
+
if (body) {
|
|
5995
|
+
for (let i = 0; i < body.namedChildCount; i++) {
|
|
5996
|
+
const child = body.namedChild(i);
|
|
5997
|
+
if (child) visit(child, name ?? classCtx);
|
|
5998
|
+
}
|
|
5653
5999
|
}
|
|
6000
|
+
return;
|
|
5654
6001
|
}
|
|
5655
|
-
|
|
5656
|
-
|
|
6002
|
+
case "method_definition": {
|
|
6003
|
+
const name = methodName(node);
|
|
6004
|
+
if (name) {
|
|
6005
|
+
const kind = name === "constructor" ? "constructor" : "method";
|
|
6006
|
+
push(kind, classCtx ? `${classCtx}.${name}` : name, node);
|
|
6007
|
+
}
|
|
6008
|
+
break;
|
|
5657
6009
|
}
|
|
5658
|
-
|
|
5659
|
-
|
|
5660
|
-
|
|
5661
|
-
|
|
5662
|
-
|
|
5663
|
-
|
|
5664
|
-
}
|
|
5665
|
-
|
|
5666
|
-
let baseDir;
|
|
5667
|
-
if (imp.level > 0) {
|
|
5668
|
-
baseDir = import_node_path15.default.dirname(importerPath);
|
|
5669
|
-
for (let i = 1; i < imp.level; i++) baseDir = import_node_path15.default.dirname(baseDir);
|
|
5670
|
-
} else {
|
|
5671
|
-
baseDir = serviceDir;
|
|
5672
|
-
}
|
|
5673
|
-
const moduleBase = imp.modulePath ? import_node_path15.default.join(baseDir, imp.modulePath.split(".").join("/")) : baseDir;
|
|
5674
|
-
const resolved = /* @__PURE__ */ new Set();
|
|
5675
|
-
let needModuleFile = imp.names.length === 0;
|
|
5676
|
-
for (const name of imp.names) {
|
|
5677
|
-
const submoduleFile = import_node_path15.default.join(moduleBase, `${name}.py`);
|
|
5678
|
-
const subpackageInit = import_node_path15.default.join(moduleBase, name, "__init__.py");
|
|
5679
|
-
if (isWithinServiceDir(submoduleFile, serviceDir) && await fileExists(submoduleFile)) {
|
|
5680
|
-
resolved.add(toPosix(import_node_path15.default.relative(serviceDir, submoduleFile)));
|
|
5681
|
-
} else if (isWithinServiceDir(subpackageInit, serviceDir) && await fileExists(subpackageInit)) {
|
|
5682
|
-
resolved.add(toPosix(import_node_path15.default.relative(serviceDir, subpackageInit)));
|
|
5683
|
-
} else {
|
|
5684
|
-
needModuleFile = true;
|
|
5685
|
-
}
|
|
5686
|
-
}
|
|
5687
|
-
if (needModuleFile) {
|
|
5688
|
-
const moduleFileCandidates = imp.modulePath ? [`${moduleBase}.py`, import_node_path15.default.join(moduleBase, "__init__.py")] : [import_node_path15.default.join(moduleBase, "__init__.py")];
|
|
5689
|
-
for (const candidate of moduleFileCandidates) {
|
|
5690
|
-
if (isWithinServiceDir(candidate, serviceDir) && await fileExists(candidate)) {
|
|
5691
|
-
resolved.add(toPosix(import_node_path15.default.relative(serviceDir, candidate)));
|
|
6010
|
+
case "variable_declarator": {
|
|
6011
|
+
const value = node.childForFieldName("value");
|
|
6012
|
+
if (value && (value.type === "arrow_function" || value.type === "function" || value.type === "function_expression" || value.type === "generator_function")) {
|
|
6013
|
+
const nameNode = node.childForFieldName("name");
|
|
6014
|
+
if (nameNode && nameNode.type === "identifier") {
|
|
6015
|
+
push("function", nameNode.text, node);
|
|
6016
|
+
}
|
|
6017
|
+
}
|
|
5692
6018
|
break;
|
|
5693
6019
|
}
|
|
5694
6020
|
}
|
|
5695
|
-
|
|
5696
|
-
|
|
6021
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
6022
|
+
const child = node.namedChild(i);
|
|
6023
|
+
if (child) visit(child, classCtx);
|
|
6024
|
+
}
|
|
6025
|
+
};
|
|
6026
|
+
visit(root, void 0);
|
|
6027
|
+
return out;
|
|
5697
6028
|
}
|
|
5698
|
-
|
|
5699
|
-
|
|
5700
|
-
const
|
|
5701
|
-
const
|
|
5702
|
-
|
|
5703
|
-
|
|
5704
|
-
|
|
5705
|
-
|
|
6029
|
+
function disambiguate(defs) {
|
|
6030
|
+
const counts = /* @__PURE__ */ new Map();
|
|
6031
|
+
for (const def of defs) counts.set(def.qualname, (counts.get(def.qualname) ?? 0) + 1);
|
|
6032
|
+
const seen = /* @__PURE__ */ new Map();
|
|
6033
|
+
return defs.map((def) => {
|
|
6034
|
+
if ((counts.get(def.qualname) ?? 0) <= 1) return { def };
|
|
6035
|
+
const ordinal = seen.get(def.qualname) ?? 0;
|
|
6036
|
+
seen.set(def.qualname, ordinal + 1);
|
|
6037
|
+
return { def, disambiguator: ordinal };
|
|
6038
|
+
});
|
|
5706
6039
|
}
|
|
5707
|
-
function
|
|
5708
|
-
const
|
|
5709
|
-
|
|
5710
|
-
|
|
5711
|
-
|
|
5712
|
-
|
|
5713
|
-
|
|
5714
|
-
|
|
5715
|
-
|
|
5716
|
-
|
|
5717
|
-
|
|
5718
|
-
|
|
5719
|
-
evidence: { file: importerRelPath, line, snippet: snippet2 }
|
|
6040
|
+
async function addSymbols(graph, services) {
|
|
6041
|
+
const parsers = /* @__PURE__ */ new Map();
|
|
6042
|
+
const parserForExt2 = (ext) => {
|
|
6043
|
+
const grammar = GRAMMAR_BY_EXT[ext];
|
|
6044
|
+
if (!grammar) return null;
|
|
6045
|
+
let parser = parsers.get(ext);
|
|
6046
|
+
if (!parser) {
|
|
6047
|
+
parser = new import_tree_sitter3.default();
|
|
6048
|
+
parser.setLanguage(grammar);
|
|
6049
|
+
parsers.set(ext, parser);
|
|
6050
|
+
}
|
|
6051
|
+
return parser;
|
|
5720
6052
|
};
|
|
5721
|
-
|
|
5722
|
-
return 1;
|
|
5723
|
-
}
|
|
5724
|
-
async function addImports(graph, services) {
|
|
5725
|
-
const jsParser = makeJsParser2();
|
|
5726
|
-
const pyParser = makePyParser2();
|
|
5727
|
-
const goParser = makeGoParser2();
|
|
6053
|
+
let nodesAdded = 0;
|
|
5728
6054
|
let edgesAdded = 0;
|
|
5729
6055
|
for (const service of services) {
|
|
5730
|
-
const tsPaths = await loadTsPathConfig(service.dir);
|
|
5731
6056
|
const files = await loadSourceFiles(service.dir);
|
|
5732
6057
|
for (const file of files) {
|
|
5733
|
-
|
|
5734
|
-
|
|
5735
|
-
const
|
|
5736
|
-
|
|
5737
|
-
const isGo = import_node_path15.default.extname(file.path) === ".go";
|
|
5738
|
-
if (isGo) {
|
|
5739
|
-
let goImports = [];
|
|
5740
|
-
try {
|
|
5741
|
-
const tree = parseSource3(goParser, file.content);
|
|
5742
|
-
collectGoImports(tree.rootNode, goImports);
|
|
5743
|
-
} catch (err) {
|
|
5744
|
-
recordExtractionError("import extraction", file.path, err);
|
|
5745
|
-
continue;
|
|
5746
|
-
}
|
|
5747
|
-
const goMod = await import_node_fs12.promises.readFile(import_node_path15.default.join(service.dir, "go.mod"), "utf8").catch(() => "");
|
|
5748
|
-
const modulePath = goMod.match(/^\s*module\s+(\S+)\s*$/m)?.[1];
|
|
5749
|
-
if (!modulePath) continue;
|
|
5750
|
-
for (const imp of goImports) {
|
|
5751
|
-
const resolved = await resolveGoImport(imp.specifier, modulePath, service.dir);
|
|
5752
|
-
if (!resolved) continue;
|
|
5753
|
-
edgesAdded += emitImportEdge(graph, service.pkg.name, importerFileId, relFile, resolved, imp.line, imp.snippet);
|
|
5754
|
-
}
|
|
5755
|
-
continue;
|
|
5756
|
-
}
|
|
5757
|
-
if (isPython) {
|
|
5758
|
-
let pyImports = [];
|
|
5759
|
-
try {
|
|
5760
|
-
const tree = parseSource3(pyParser, file.content);
|
|
5761
|
-
collectPyImports(tree.rootNode, pyImports);
|
|
5762
|
-
} catch (err) {
|
|
5763
|
-
recordExtractionError("import extraction", file.path, err);
|
|
5764
|
-
continue;
|
|
5765
|
-
}
|
|
5766
|
-
for (const imp of pyImports) {
|
|
5767
|
-
const resolvedPaths = await resolvePyImport(imp, file.path, service.dir);
|
|
5768
|
-
for (const resolved of resolvedPaths) {
|
|
5769
|
-
edgesAdded += emitImportEdge(
|
|
5770
|
-
graph,
|
|
5771
|
-
service.pkg.name,
|
|
5772
|
-
importerFileId,
|
|
5773
|
-
relFile,
|
|
5774
|
-
resolved,
|
|
5775
|
-
imp.line,
|
|
5776
|
-
imp.snippet
|
|
5777
|
-
);
|
|
5778
|
-
}
|
|
5779
|
-
}
|
|
5780
|
-
continue;
|
|
5781
|
-
}
|
|
5782
|
-
let jsImports = [];
|
|
6058
|
+
const parser = parserForExt2(import_node_path15.default.extname(file.path));
|
|
6059
|
+
if (!parser) continue;
|
|
6060
|
+
const relPath = toPosix(import_node_path15.default.relative(service.dir, file.path));
|
|
6061
|
+
let defs;
|
|
5783
6062
|
try {
|
|
5784
|
-
const tree = parseSource3(
|
|
5785
|
-
|
|
6063
|
+
const tree = parseSource3(parser, file.content);
|
|
6064
|
+
defs = collectSymbolDefs(tree.rootNode);
|
|
5786
6065
|
} catch (err) {
|
|
5787
|
-
recordExtractionError("
|
|
6066
|
+
recordExtractionError("symbol extraction", file.path, err);
|
|
5788
6067
|
continue;
|
|
5789
6068
|
}
|
|
5790
|
-
|
|
5791
|
-
|
|
5792
|
-
|
|
5793
|
-
|
|
5794
|
-
|
|
5795
|
-
|
|
5796
|
-
|
|
5797
|
-
|
|
5798
|
-
|
|
5799
|
-
|
|
5800
|
-
|
|
5801
|
-
)
|
|
6069
|
+
if (defs.length === 0) continue;
|
|
6070
|
+
const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
|
|
6071
|
+
graph,
|
|
6072
|
+
service.pkg.name,
|
|
6073
|
+
service.node.id,
|
|
6074
|
+
relPath
|
|
6075
|
+
);
|
|
6076
|
+
nodesAdded += fn;
|
|
6077
|
+
edgesAdded += fe;
|
|
6078
|
+
for (const { def, disambiguator } of disambiguate(defs)) {
|
|
6079
|
+
const sid = (0, import_types12.symbolId)(service.pkg.name, relPath, def.qualname, disambiguator);
|
|
6080
|
+
if (!graph.hasNode(sid)) {
|
|
6081
|
+
const node = {
|
|
6082
|
+
id: sid,
|
|
6083
|
+
type: import_types12.NodeType.SymbolNode,
|
|
6084
|
+
kind: def.kind,
|
|
6085
|
+
qualname: def.qualname,
|
|
6086
|
+
span: { startLine: def.startLine, endLine: def.endLine },
|
|
6087
|
+
service: service.pkg.name,
|
|
6088
|
+
relPath,
|
|
6089
|
+
discoveredVia: "static"
|
|
6090
|
+
};
|
|
6091
|
+
graph.addNode(sid, node);
|
|
6092
|
+
nodesAdded++;
|
|
6093
|
+
}
|
|
6094
|
+
const containsId = (0, import_types12.extractedEdgeId)(fileNodeId, sid, import_types12.EdgeType.CONTAINS);
|
|
6095
|
+
if (!graph.hasEdge(containsId)) {
|
|
6096
|
+
const edge = {
|
|
6097
|
+
id: containsId,
|
|
6098
|
+
source: fileNodeId,
|
|
6099
|
+
target: sid,
|
|
6100
|
+
type: import_types12.EdgeType.CONTAINS,
|
|
6101
|
+
provenance: import_types12.Provenance.EXTRACTED,
|
|
6102
|
+
confidence: (0, import_types12.confidenceForExtracted)("structural"),
|
|
6103
|
+
evidence: {
|
|
6104
|
+
file: relPath,
|
|
6105
|
+
line: def.startLine,
|
|
6106
|
+
snippet: snippet(file.content, def.startLine)
|
|
6107
|
+
}
|
|
6108
|
+
};
|
|
6109
|
+
graph.addEdgeWithKey(containsId, fileNodeId, sid, edge);
|
|
6110
|
+
edgesAdded++;
|
|
6111
|
+
}
|
|
5802
6112
|
}
|
|
5803
6113
|
}
|
|
5804
6114
|
}
|
|
5805
|
-
return { nodesAdded
|
|
6115
|
+
return { nodesAdded, edgesAdded };
|
|
5806
6116
|
}
|
|
5807
6117
|
|
|
5808
6118
|
// src/extract/symbol-edges.ts
|
|
6119
|
+
init_cjs_shims();
|
|
6120
|
+
var import_node_path16 = __toESM(require("path"), 1);
|
|
6121
|
+
var import_tree_sitter4 = __toESM(require("tree-sitter"), 1);
|
|
6122
|
+
var import_types13 = require("@neat.is/types");
|
|
5809
6123
|
function extendsInfo(classHeritage) {
|
|
5810
6124
|
for (let i = 0; i < classHeritage.namedChildCount; i++) {
|
|
5811
6125
|
const child = classHeritage.namedChild(i);
|
|
@@ -5916,7 +6230,7 @@ async function addSymbolEdges(graph, services) {
|
|
|
5916
6230
|
const fileDir = import_node_path16.default.dirname(file.path);
|
|
5917
6231
|
let root;
|
|
5918
6232
|
try {
|
|
5919
|
-
root =
|
|
6233
|
+
root = parseSource3(parser, file.content).rootNode;
|
|
5920
6234
|
} catch (err) {
|
|
5921
6235
|
recordExtractionError("symbol edge extraction", file.path, err);
|
|
5922
6236
|
continue;
|
|
@@ -8368,7 +8682,7 @@ function columnsFromObject(obj) {
|
|
|
8368
8682
|
}
|
|
8369
8683
|
function drizzleEndpointsFromFile(file, serviceDir) {
|
|
8370
8684
|
if (!DRIZZLE_IMPORT_RE.test(file.content)) return [];
|
|
8371
|
-
const tree =
|
|
8685
|
+
const tree = parseSource3(parserForExt(import_node_path37.default.extname(file.path)), file.content);
|
|
8372
8686
|
const out = [];
|
|
8373
8687
|
const seen = /* @__PURE__ */ new Set();
|
|
8374
8688
|
const walk6 = (node) => {
|