@neat.is/core 0.4.12 → 0.4.14
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-HVF4S7J3.js → chunk-6CO7C4IU.js} +6 -3
- package/dist/{chunk-HVF4S7J3.js.map → chunk-6CO7C4IU.js.map} +1 -1
- package/dist/{chunk-3QCRUEQD.js → chunk-GHPHVXYM.js} +5 -3
- package/dist/chunk-GHPHVXYM.js.map +1 -0
- package/dist/{chunk-7JY6F7BY.js → chunk-QZRDQ7WX.js} +3 -3
- package/dist/{chunk-WDG4QEFO.js → chunk-XS4CGNRO.js} +558 -233
- package/dist/chunk-XS4CGNRO.js.map +1 -0
- package/dist/cli.cjs +801 -458
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +21 -4
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +613 -286
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +4 -4
- package/dist/neatd.cjs +620 -293
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +3 -3
- package/dist/{otel-grpc-VSPMP3OS.js → otel-grpc-QAISVAY5.js} +3 -3
- package/dist/server.cjs +538 -211
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +3 -3
- package/package.json +6 -2
- package/dist/chunk-3QCRUEQD.js.map +0 -1
- package/dist/chunk-WDG4QEFO.js.map +0 -1
- /package/dist/{chunk-7JY6F7BY.js.map → chunk-QZRDQ7WX.js.map} +0 -0
- /package/dist/{otel-grpc-VSPMP3OS.js.map → otel-grpc-QAISVAY5.js.map} +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
mountBearerAuth,
|
|
3
3
|
readAuthEnv
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-6CO7C4IU.js";
|
|
5
5
|
|
|
6
6
|
// src/graph.ts
|
|
7
7
|
import GraphDefault from "graphology";
|
|
@@ -454,19 +454,19 @@ function confidenceFromMix(edges, now = Date.now()) {
|
|
|
454
454
|
function longestIncomingWalk(graph, start, maxDepth) {
|
|
455
455
|
let best = { path: [start], edges: [] };
|
|
456
456
|
const visited = /* @__PURE__ */ new Set([start]);
|
|
457
|
-
function step(node,
|
|
458
|
-
if (
|
|
459
|
-
best = { path: [...
|
|
457
|
+
function step(node, path38, edges) {
|
|
458
|
+
if (path38.length > best.path.length) {
|
|
459
|
+
best = { path: [...path38], edges: [...edges] };
|
|
460
460
|
}
|
|
461
|
-
if (
|
|
461
|
+
if (path38.length - 1 >= maxDepth) return;
|
|
462
462
|
const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
|
|
463
463
|
for (const [srcId, edge] of incoming) {
|
|
464
464
|
if (visited.has(srcId)) continue;
|
|
465
465
|
visited.add(srcId);
|
|
466
|
-
|
|
466
|
+
path38.push(srcId);
|
|
467
467
|
edges.push(edge);
|
|
468
|
-
step(srcId,
|
|
469
|
-
|
|
468
|
+
step(srcId, path38, edges);
|
|
469
|
+
path38.pop();
|
|
470
470
|
edges.pop();
|
|
471
471
|
visited.delete(srcId);
|
|
472
472
|
}
|
|
@@ -2519,16 +2519,6 @@ async function addServiceAliases(graph, scanPath, services) {
|
|
|
2519
2519
|
await collectK8sAliases(graph, scanPath, byName);
|
|
2520
2520
|
}
|
|
2521
2521
|
|
|
2522
|
-
// src/extract/databases/index.ts
|
|
2523
|
-
import path18 from "path";
|
|
2524
|
-
import {
|
|
2525
|
-
EdgeType as EdgeType5,
|
|
2526
|
-
NodeType as NodeType7,
|
|
2527
|
-
Provenance as Provenance3,
|
|
2528
|
-
databaseId as databaseId2,
|
|
2529
|
-
confidenceForExtracted as confidenceForExtracted2
|
|
2530
|
-
} from "@neat.is/types";
|
|
2531
|
-
|
|
2532
2522
|
// src/extract/calls/shared.ts
|
|
2533
2523
|
import { promises as fs10 } from "fs";
|
|
2534
2524
|
import path10 from "path";
|
|
@@ -2632,10 +2622,342 @@ function ensureFileNode(graph, serviceName, serviceNodeId, relPath) {
|
|
|
2632
2622
|
return { fileNodeId, nodesAdded, edgesAdded };
|
|
2633
2623
|
}
|
|
2634
2624
|
|
|
2635
|
-
// src/extract/
|
|
2625
|
+
// src/extract/files.ts
|
|
2636
2626
|
import path11 from "path";
|
|
2627
|
+
async function addFiles(graph, services) {
|
|
2628
|
+
let nodesAdded = 0;
|
|
2629
|
+
let edgesAdded = 0;
|
|
2630
|
+
for (const service of services) {
|
|
2631
|
+
const filePaths = await walkSourceFiles(service.dir);
|
|
2632
|
+
for (const filePath of filePaths) {
|
|
2633
|
+
const relPath = toPosix2(path11.relative(service.dir, filePath));
|
|
2634
|
+
const { nodesAdded: n, edgesAdded: e } = ensureFileNode(
|
|
2635
|
+
graph,
|
|
2636
|
+
service.pkg.name,
|
|
2637
|
+
service.node.id,
|
|
2638
|
+
relPath
|
|
2639
|
+
);
|
|
2640
|
+
nodesAdded += n;
|
|
2641
|
+
edgesAdded += e;
|
|
2642
|
+
}
|
|
2643
|
+
}
|
|
2644
|
+
return { nodesAdded, edgesAdded };
|
|
2645
|
+
}
|
|
2646
|
+
|
|
2647
|
+
// src/extract/imports.ts
|
|
2648
|
+
import path12 from "path";
|
|
2649
|
+
import { promises as fs11 } from "fs";
|
|
2650
|
+
import Parser from "tree-sitter";
|
|
2651
|
+
import JavaScript from "tree-sitter-javascript";
|
|
2652
|
+
import Python from "tree-sitter-python";
|
|
2653
|
+
import {
|
|
2654
|
+
EdgeType as EdgeType5,
|
|
2655
|
+
Provenance as Provenance3,
|
|
2656
|
+
confidenceForExtracted as confidenceForExtracted2,
|
|
2657
|
+
extractedEdgeId as extractedEdgeId4,
|
|
2658
|
+
fileId as fileId3
|
|
2659
|
+
} from "@neat.is/types";
|
|
2660
|
+
var PARSE_CHUNK = 16384;
|
|
2661
|
+
function parseSource(parser, source) {
|
|
2662
|
+
return parser.parse(
|
|
2663
|
+
(index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK)
|
|
2664
|
+
);
|
|
2665
|
+
}
|
|
2666
|
+
function makeJsParser() {
|
|
2667
|
+
const p = new Parser();
|
|
2668
|
+
p.setLanguage(JavaScript);
|
|
2669
|
+
return p;
|
|
2670
|
+
}
|
|
2671
|
+
function makePyParser() {
|
|
2672
|
+
const p = new Parser();
|
|
2673
|
+
p.setLanguage(Python);
|
|
2674
|
+
return p;
|
|
2675
|
+
}
|
|
2676
|
+
function stringLiteralText(node) {
|
|
2677
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
2678
|
+
const child = node.child(i);
|
|
2679
|
+
if (child?.type === "string_fragment") return child.text;
|
|
2680
|
+
}
|
|
2681
|
+
const raw = node.text;
|
|
2682
|
+
if (raw.length >= 2) return raw.slice(1, -1);
|
|
2683
|
+
return raw.length === 0 ? null : "";
|
|
2684
|
+
}
|
|
2685
|
+
function clipSnippet(text) {
|
|
2686
|
+
const oneLine = text.split("\n")[0] ?? text;
|
|
2687
|
+
return oneLine.length > 120 ? oneLine.slice(0, 120) : oneLine;
|
|
2688
|
+
}
|
|
2689
|
+
function collectJsImports(node, out) {
|
|
2690
|
+
if (node.type === "import_statement") {
|
|
2691
|
+
const source = node.childForFieldName("source");
|
|
2692
|
+
if (source) {
|
|
2693
|
+
const specifier = stringLiteralText(source);
|
|
2694
|
+
if (specifier) {
|
|
2695
|
+
out.push({ specifier, line: node.startPosition.row + 1, snippet: clipSnippet(node.text) });
|
|
2696
|
+
}
|
|
2697
|
+
}
|
|
2698
|
+
return;
|
|
2699
|
+
}
|
|
2700
|
+
if (node.type === "call_expression") {
|
|
2701
|
+
const fn = node.childForFieldName("function");
|
|
2702
|
+
if (fn?.type === "identifier" && fn.text === "require") {
|
|
2703
|
+
const args = node.childForFieldName("arguments");
|
|
2704
|
+
const firstArg = args?.namedChild(0);
|
|
2705
|
+
if (firstArg?.type === "string") {
|
|
2706
|
+
const specifier = stringLiteralText(firstArg);
|
|
2707
|
+
if (specifier) {
|
|
2708
|
+
out.push({ specifier, line: node.startPosition.row + 1, snippet: clipSnippet(node.text) });
|
|
2709
|
+
}
|
|
2710
|
+
}
|
|
2711
|
+
}
|
|
2712
|
+
}
|
|
2713
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
2714
|
+
const child = node.namedChild(i);
|
|
2715
|
+
if (child) collectJsImports(child, out);
|
|
2716
|
+
}
|
|
2717
|
+
}
|
|
2718
|
+
function collectPyImports(node, out) {
|
|
2719
|
+
if (node.type === "import_from_statement") {
|
|
2720
|
+
let level = 0;
|
|
2721
|
+
let modulePath = "";
|
|
2722
|
+
let pastFrom = false;
|
|
2723
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
2724
|
+
const child = node.child(i);
|
|
2725
|
+
if (!child) continue;
|
|
2726
|
+
if (!pastFrom) {
|
|
2727
|
+
if (child.type === "from") pastFrom = true;
|
|
2728
|
+
continue;
|
|
2729
|
+
}
|
|
2730
|
+
if (child.type === "import") break;
|
|
2731
|
+
if (child.type === "relative_import") {
|
|
2732
|
+
for (let j = 0; j < child.childCount; j++) {
|
|
2733
|
+
const rc = child.child(j);
|
|
2734
|
+
if (!rc) continue;
|
|
2735
|
+
if (rc.type === "import_prefix") {
|
|
2736
|
+
for (let k = 0; k < rc.childCount; k++) {
|
|
2737
|
+
if (rc.child(k)?.type === ".") level++;
|
|
2738
|
+
}
|
|
2739
|
+
} else if (rc.type === "dotted_name") modulePath = rc.text;
|
|
2740
|
+
}
|
|
2741
|
+
break;
|
|
2742
|
+
}
|
|
2743
|
+
if (child.type === "dotted_name") {
|
|
2744
|
+
modulePath = child.text;
|
|
2745
|
+
break;
|
|
2746
|
+
}
|
|
2747
|
+
}
|
|
2748
|
+
if (level > 0 || modulePath) {
|
|
2749
|
+
out.push({ modulePath, level, line: node.startPosition.row + 1, snippet: clipSnippet(node.text) });
|
|
2750
|
+
}
|
|
2751
|
+
}
|
|
2752
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
2753
|
+
const child = node.namedChild(i);
|
|
2754
|
+
if (child) collectPyImports(child, out);
|
|
2755
|
+
}
|
|
2756
|
+
}
|
|
2757
|
+
async function fileExists(p) {
|
|
2758
|
+
try {
|
|
2759
|
+
await fs11.access(p);
|
|
2760
|
+
return true;
|
|
2761
|
+
} catch {
|
|
2762
|
+
return false;
|
|
2763
|
+
}
|
|
2764
|
+
}
|
|
2765
|
+
function isWithinServiceDir(candidate, serviceDir) {
|
|
2766
|
+
const rel = path12.relative(serviceDir, candidate);
|
|
2767
|
+
return rel !== "" && !rel.startsWith("..") && !path12.isAbsolute(rel);
|
|
2768
|
+
}
|
|
2769
|
+
var JS_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"];
|
|
2770
|
+
var JS_INDEX_FILES = JS_EXTENSIONS.map((ext) => `index${ext}`);
|
|
2771
|
+
async function firstExistingCandidate(base, serviceDir) {
|
|
2772
|
+
for (const ext of JS_EXTENSIONS) {
|
|
2773
|
+
const candidate = base + ext;
|
|
2774
|
+
if (isWithinServiceDir(candidate, serviceDir) && await fileExists(candidate)) {
|
|
2775
|
+
return toPosix2(path12.relative(serviceDir, candidate));
|
|
2776
|
+
}
|
|
2777
|
+
}
|
|
2778
|
+
for (const indexFile of JS_INDEX_FILES) {
|
|
2779
|
+
const candidate = path12.join(base, indexFile);
|
|
2780
|
+
if (isWithinServiceDir(candidate, serviceDir) && await fileExists(candidate)) {
|
|
2781
|
+
return toPosix2(path12.relative(serviceDir, candidate));
|
|
2782
|
+
}
|
|
2783
|
+
}
|
|
2784
|
+
return null;
|
|
2785
|
+
}
|
|
2786
|
+
async function loadTsPathConfig(serviceDir) {
|
|
2787
|
+
const tsconfigPath = path12.join(serviceDir, "tsconfig.json");
|
|
2788
|
+
let raw;
|
|
2789
|
+
try {
|
|
2790
|
+
raw = await fs11.readFile(tsconfigPath, "utf8");
|
|
2791
|
+
} catch {
|
|
2792
|
+
return null;
|
|
2793
|
+
}
|
|
2794
|
+
try {
|
|
2795
|
+
const parsed = JSON.parse(raw);
|
|
2796
|
+
const paths = parsed.compilerOptions?.paths;
|
|
2797
|
+
if (!paths || Object.keys(paths).length === 0) return null;
|
|
2798
|
+
const baseUrl = parsed.compilerOptions?.baseUrl;
|
|
2799
|
+
return { paths, baseDir: baseUrl ? path12.resolve(serviceDir, baseUrl) : serviceDir };
|
|
2800
|
+
} catch (err) {
|
|
2801
|
+
recordExtractionError("import alias resolution", tsconfigPath, err);
|
|
2802
|
+
return null;
|
|
2803
|
+
}
|
|
2804
|
+
}
|
|
2805
|
+
async function resolveTsAlias(specifier, config, serviceDir) {
|
|
2806
|
+
for (const [pattern, targets] of Object.entries(config.paths)) {
|
|
2807
|
+
let suffix = null;
|
|
2808
|
+
if (pattern === specifier) {
|
|
2809
|
+
suffix = "";
|
|
2810
|
+
} else if (pattern.endsWith("/*")) {
|
|
2811
|
+
const prefix = pattern.slice(0, -1);
|
|
2812
|
+
if (specifier.startsWith(prefix)) suffix = specifier.slice(prefix.length);
|
|
2813
|
+
}
|
|
2814
|
+
if (suffix === null) continue;
|
|
2815
|
+
for (const target of targets) {
|
|
2816
|
+
const targetBase = target.endsWith("/*") ? target.slice(0, -2) : target.replace(/\*$/, "");
|
|
2817
|
+
const resolvedBase = path12.resolve(config.baseDir, targetBase, suffix);
|
|
2818
|
+
const hit = await firstExistingCandidate(resolvedBase, serviceDir);
|
|
2819
|
+
if (hit) return hit;
|
|
2820
|
+
if (isWithinServiceDir(resolvedBase, serviceDir) && await fileExists(resolvedBase)) {
|
|
2821
|
+
return toPosix2(path12.relative(serviceDir, resolvedBase));
|
|
2822
|
+
}
|
|
2823
|
+
}
|
|
2824
|
+
}
|
|
2825
|
+
return null;
|
|
2826
|
+
}
|
|
2827
|
+
async function resolveJsImport(specifier, importerDir, serviceDir, tsPaths) {
|
|
2828
|
+
if (!specifier) return null;
|
|
2829
|
+
if (specifier.startsWith("./") || specifier.startsWith("../")) {
|
|
2830
|
+
const base = path12.resolve(importerDir, specifier);
|
|
2831
|
+
const ext = path12.extname(specifier);
|
|
2832
|
+
if (ext) {
|
|
2833
|
+
if (ext === ".js" || ext === ".jsx") {
|
|
2834
|
+
const tsExt = ext === ".jsx" ? ".tsx" : ".ts";
|
|
2835
|
+
const tsSibling = base.slice(0, -ext.length) + tsExt;
|
|
2836
|
+
if (isWithinServiceDir(tsSibling, serviceDir) && await fileExists(tsSibling)) {
|
|
2837
|
+
return toPosix2(path12.relative(serviceDir, tsSibling));
|
|
2838
|
+
}
|
|
2839
|
+
}
|
|
2840
|
+
if (isWithinServiceDir(base, serviceDir) && await fileExists(base)) {
|
|
2841
|
+
return toPosix2(path12.relative(serviceDir, base));
|
|
2842
|
+
}
|
|
2843
|
+
return null;
|
|
2844
|
+
}
|
|
2845
|
+
return firstExistingCandidate(base, serviceDir);
|
|
2846
|
+
}
|
|
2847
|
+
if (tsPaths) return resolveTsAlias(specifier, tsPaths, serviceDir);
|
|
2848
|
+
return null;
|
|
2849
|
+
}
|
|
2850
|
+
async function resolvePyImport(imp, importerPath, serviceDir) {
|
|
2851
|
+
if (!imp.modulePath) return null;
|
|
2852
|
+
const relPath = imp.modulePath.split(".").join("/");
|
|
2853
|
+
let baseDir;
|
|
2854
|
+
if (imp.level > 0) {
|
|
2855
|
+
baseDir = path12.dirname(importerPath);
|
|
2856
|
+
for (let i = 1; i < imp.level; i++) baseDir = path12.dirname(baseDir);
|
|
2857
|
+
} else {
|
|
2858
|
+
baseDir = serviceDir;
|
|
2859
|
+
}
|
|
2860
|
+
const candidates = [path12.join(baseDir, `${relPath}.py`), path12.join(baseDir, relPath, "__init__.py")];
|
|
2861
|
+
for (const candidate of candidates) {
|
|
2862
|
+
if (isWithinServiceDir(candidate, serviceDir) && await fileExists(candidate)) {
|
|
2863
|
+
return toPosix2(path12.relative(serviceDir, candidate));
|
|
2864
|
+
}
|
|
2865
|
+
}
|
|
2866
|
+
return null;
|
|
2867
|
+
}
|
|
2868
|
+
function emitImportEdge(graph, serviceName, importerFileId, importerRelPath, importeeRelPath, line, snippet2) {
|
|
2869
|
+
const importeeFileId = fileId3(serviceName, importeeRelPath);
|
|
2870
|
+
if (!graph.hasNode(importeeFileId)) return 0;
|
|
2871
|
+
const edgeId = extractedEdgeId4(importerFileId, importeeFileId, EdgeType5.IMPORTS);
|
|
2872
|
+
if (graph.hasEdge(edgeId)) return 0;
|
|
2873
|
+
const edge = {
|
|
2874
|
+
id: edgeId,
|
|
2875
|
+
source: importerFileId,
|
|
2876
|
+
target: importeeFileId,
|
|
2877
|
+
type: EdgeType5.IMPORTS,
|
|
2878
|
+
provenance: Provenance3.EXTRACTED,
|
|
2879
|
+
confidence: confidenceForExtracted2("structural"),
|
|
2880
|
+
evidence: { file: importerRelPath, line, snippet: snippet2 }
|
|
2881
|
+
};
|
|
2882
|
+
graph.addEdgeWithKey(edgeId, importerFileId, importeeFileId, edge);
|
|
2883
|
+
return 1;
|
|
2884
|
+
}
|
|
2885
|
+
async function addImports(graph, services) {
|
|
2886
|
+
const jsParser = makeJsParser();
|
|
2887
|
+
const pyParser = makePyParser();
|
|
2888
|
+
let edgesAdded = 0;
|
|
2889
|
+
for (const service of services) {
|
|
2890
|
+
const tsPaths = await loadTsPathConfig(service.dir);
|
|
2891
|
+
const files = await loadSourceFiles(service.dir);
|
|
2892
|
+
for (const file of files) {
|
|
2893
|
+
if (isTestPath(file.path)) continue;
|
|
2894
|
+
const relFile = toPosix2(path12.relative(service.dir, file.path));
|
|
2895
|
+
const importerFileId = fileId3(service.pkg.name, relFile);
|
|
2896
|
+
const isPython = path12.extname(file.path) === ".py";
|
|
2897
|
+
if (isPython) {
|
|
2898
|
+
let pyImports = [];
|
|
2899
|
+
try {
|
|
2900
|
+
const tree = parseSource(pyParser, file.content);
|
|
2901
|
+
collectPyImports(tree.rootNode, pyImports);
|
|
2902
|
+
} catch (err) {
|
|
2903
|
+
recordExtractionError("import extraction", file.path, err);
|
|
2904
|
+
continue;
|
|
2905
|
+
}
|
|
2906
|
+
for (const imp of pyImports) {
|
|
2907
|
+
const resolved = await resolvePyImport(imp, file.path, service.dir);
|
|
2908
|
+
if (!resolved) continue;
|
|
2909
|
+
edgesAdded += emitImportEdge(
|
|
2910
|
+
graph,
|
|
2911
|
+
service.pkg.name,
|
|
2912
|
+
importerFileId,
|
|
2913
|
+
relFile,
|
|
2914
|
+
resolved,
|
|
2915
|
+
imp.line,
|
|
2916
|
+
imp.snippet
|
|
2917
|
+
);
|
|
2918
|
+
}
|
|
2919
|
+
continue;
|
|
2920
|
+
}
|
|
2921
|
+
let jsImports = [];
|
|
2922
|
+
try {
|
|
2923
|
+
const tree = parseSource(jsParser, file.content);
|
|
2924
|
+
collectJsImports(tree.rootNode, jsImports);
|
|
2925
|
+
} catch (err) {
|
|
2926
|
+
recordExtractionError("import extraction", file.path, err);
|
|
2927
|
+
continue;
|
|
2928
|
+
}
|
|
2929
|
+
for (const imp of jsImports) {
|
|
2930
|
+
const resolved = await resolveJsImport(imp.specifier, path12.dirname(file.path), service.dir, tsPaths);
|
|
2931
|
+
if (!resolved) continue;
|
|
2932
|
+
edgesAdded += emitImportEdge(
|
|
2933
|
+
graph,
|
|
2934
|
+
service.pkg.name,
|
|
2935
|
+
importerFileId,
|
|
2936
|
+
relFile,
|
|
2937
|
+
resolved,
|
|
2938
|
+
imp.line,
|
|
2939
|
+
imp.snippet
|
|
2940
|
+
);
|
|
2941
|
+
}
|
|
2942
|
+
}
|
|
2943
|
+
}
|
|
2944
|
+
return { nodesAdded: 0, edgesAdded };
|
|
2945
|
+
}
|
|
2946
|
+
|
|
2947
|
+
// src/extract/databases/index.ts
|
|
2948
|
+
import path20 from "path";
|
|
2949
|
+
import {
|
|
2950
|
+
EdgeType as EdgeType6,
|
|
2951
|
+
NodeType as NodeType7,
|
|
2952
|
+
Provenance as Provenance4,
|
|
2953
|
+
databaseId as databaseId2,
|
|
2954
|
+
confidenceForExtracted as confidenceForExtracted3
|
|
2955
|
+
} from "@neat.is/types";
|
|
2956
|
+
|
|
2957
|
+
// src/extract/databases/db-config-yaml.ts
|
|
2958
|
+
import path13 from "path";
|
|
2637
2959
|
async function parse(serviceDir) {
|
|
2638
|
-
const yamlPath =
|
|
2960
|
+
const yamlPath = path13.join(serviceDir, "db-config.yaml");
|
|
2639
2961
|
if (!await exists(yamlPath)) return [];
|
|
2640
2962
|
const raw = await readYaml(yamlPath);
|
|
2641
2963
|
return [
|
|
@@ -2652,12 +2974,12 @@ async function parse(serviceDir) {
|
|
|
2652
2974
|
var dbConfigYamlParser = { name: "db-config.yaml", parse };
|
|
2653
2975
|
|
|
2654
2976
|
// src/extract/databases/dotenv.ts
|
|
2655
|
-
import { promises as
|
|
2656
|
-
import
|
|
2977
|
+
import { promises as fs13 } from "fs";
|
|
2978
|
+
import path15 from "path";
|
|
2657
2979
|
|
|
2658
2980
|
// src/extract/databases/shared.ts
|
|
2659
|
-
import { promises as
|
|
2660
|
-
import
|
|
2981
|
+
import { promises as fs12 } from "fs";
|
|
2982
|
+
import path14 from "path";
|
|
2661
2983
|
function schemeToEngine(scheme) {
|
|
2662
2984
|
const s = scheme.toLowerCase().split("+")[0];
|
|
2663
2985
|
switch (s) {
|
|
@@ -2696,14 +3018,14 @@ function parseConnectionString(url) {
|
|
|
2696
3018
|
}
|
|
2697
3019
|
async function readIfExists(filePath) {
|
|
2698
3020
|
try {
|
|
2699
|
-
return await
|
|
3021
|
+
return await fs12.readFile(filePath, "utf8");
|
|
2700
3022
|
} catch {
|
|
2701
3023
|
return null;
|
|
2702
3024
|
}
|
|
2703
3025
|
}
|
|
2704
3026
|
async function findFirst(serviceDir, candidates) {
|
|
2705
3027
|
for (const rel of candidates) {
|
|
2706
|
-
const abs =
|
|
3028
|
+
const abs = path14.join(serviceDir, rel);
|
|
2707
3029
|
const content = await readIfExists(abs);
|
|
2708
3030
|
if (content !== null) return abs;
|
|
2709
3031
|
}
|
|
@@ -2754,15 +3076,15 @@ function parseDotenvLine(line) {
|
|
|
2754
3076
|
return { key, value };
|
|
2755
3077
|
}
|
|
2756
3078
|
async function parse2(serviceDir) {
|
|
2757
|
-
const entries = await
|
|
3079
|
+
const entries = await fs13.readdir(serviceDir, { withFileTypes: true }).catch(() => []);
|
|
2758
3080
|
const configs = [];
|
|
2759
3081
|
const seen = /* @__PURE__ */ new Set();
|
|
2760
3082
|
for (const entry of entries) {
|
|
2761
3083
|
if (!entry.isFile()) continue;
|
|
2762
3084
|
const match = isConfigFile(entry.name);
|
|
2763
3085
|
if (!match.match || match.fileType !== "env") continue;
|
|
2764
|
-
const filePath =
|
|
2765
|
-
const content = await
|
|
3086
|
+
const filePath = path15.join(serviceDir, entry.name);
|
|
3087
|
+
const content = await fs13.readFile(filePath, "utf8");
|
|
2766
3088
|
for (const line of content.split("\n")) {
|
|
2767
3089
|
const parsed = parseDotenvLine(line);
|
|
2768
3090
|
if (!parsed) continue;
|
|
@@ -2780,9 +3102,9 @@ async function parse2(serviceDir) {
|
|
|
2780
3102
|
var dotenvParser = { name: ".env", parse: parse2 };
|
|
2781
3103
|
|
|
2782
3104
|
// src/extract/databases/prisma.ts
|
|
2783
|
-
import
|
|
3105
|
+
import path16 from "path";
|
|
2784
3106
|
async function parse3(serviceDir) {
|
|
2785
|
-
const schemaPath =
|
|
3107
|
+
const schemaPath = path16.join(serviceDir, "prisma", "schema.prisma");
|
|
2786
3108
|
const content = await readIfExists(schemaPath);
|
|
2787
3109
|
if (!content) return [];
|
|
2788
3110
|
const block = content.match(/datasource\s+\w+\s*\{([^}]*)\}/s);
|
|
@@ -2911,10 +3233,10 @@ async function parse5(serviceDir) {
|
|
|
2911
3233
|
var knexParser = { name: "knex", parse: parse5 };
|
|
2912
3234
|
|
|
2913
3235
|
// src/extract/databases/ormconfig.ts
|
|
2914
|
-
import
|
|
3236
|
+
import path17 from "path";
|
|
2915
3237
|
async function parse6(serviceDir) {
|
|
2916
3238
|
for (const candidate of ["ormconfig.json", "ormconfig.yaml", "ormconfig.yml"]) {
|
|
2917
|
-
const abs =
|
|
3239
|
+
const abs = path17.join(serviceDir, candidate);
|
|
2918
3240
|
if (!await exists(abs)) continue;
|
|
2919
3241
|
const raw = candidate.endsWith(".json") ? await readJson(abs) : await readYaml(abs);
|
|
2920
3242
|
const entries = Array.isArray(raw) ? raw : [raw];
|
|
@@ -2972,9 +3294,9 @@ async function parse7(serviceDir) {
|
|
|
2972
3294
|
var typeormParser = { name: "typeorm", parse: parse7 };
|
|
2973
3295
|
|
|
2974
3296
|
// src/extract/databases/sequelize.ts
|
|
2975
|
-
import
|
|
3297
|
+
import path18 from "path";
|
|
2976
3298
|
async function parse8(serviceDir) {
|
|
2977
|
-
const configPath =
|
|
3299
|
+
const configPath = path18.join(serviceDir, "config", "config.json");
|
|
2978
3300
|
if (!await exists(configPath)) return [];
|
|
2979
3301
|
const raw = await readJson(configPath);
|
|
2980
3302
|
const out = [];
|
|
@@ -3000,7 +3322,7 @@ async function parse8(serviceDir) {
|
|
|
3000
3322
|
var sequelizeParser = { name: "sequelize", parse: parse8 };
|
|
3001
3323
|
|
|
3002
3324
|
// src/extract/databases/docker-compose.ts
|
|
3003
|
-
import
|
|
3325
|
+
import path19 from "path";
|
|
3004
3326
|
function portFromService(svc) {
|
|
3005
3327
|
for (const raw of svc.ports ?? []) {
|
|
3006
3328
|
const str = String(raw);
|
|
@@ -3027,7 +3349,7 @@ function databaseFromEnv(svc) {
|
|
|
3027
3349
|
}
|
|
3028
3350
|
async function parse9(serviceDir) {
|
|
3029
3351
|
for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
|
|
3030
|
-
const abs =
|
|
3352
|
+
const abs = path19.join(serviceDir, name);
|
|
3031
3353
|
if (!await exists(abs)) continue;
|
|
3032
3354
|
const raw = await readYaml(abs);
|
|
3033
3355
|
if (!raw?.services) return [];
|
|
@@ -3199,7 +3521,7 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
|
|
|
3199
3521
|
discoveredVia: mergedDiscoveredVia
|
|
3200
3522
|
});
|
|
3201
3523
|
}
|
|
3202
|
-
const relConfigFile = toPosix2(
|
|
3524
|
+
const relConfigFile = toPosix2(path20.relative(service.dir, config.sourceFile));
|
|
3203
3525
|
const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
|
|
3204
3526
|
graph,
|
|
3205
3527
|
service.pkg.name,
|
|
@@ -3208,14 +3530,14 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
|
|
|
3208
3530
|
);
|
|
3209
3531
|
nodesAdded += fn;
|
|
3210
3532
|
edgesAdded += fe;
|
|
3211
|
-
const evidenceFile = toPosix2(
|
|
3533
|
+
const evidenceFile = toPosix2(path20.relative(scanPath, config.sourceFile));
|
|
3212
3534
|
const edge = {
|
|
3213
|
-
id: extractedEdgeId2(fileNodeId, dbNode.id,
|
|
3535
|
+
id: extractedEdgeId2(fileNodeId, dbNode.id, EdgeType6.CONNECTS_TO),
|
|
3214
3536
|
source: fileNodeId,
|
|
3215
3537
|
target: dbNode.id,
|
|
3216
|
-
type:
|
|
3217
|
-
provenance:
|
|
3218
|
-
confidence:
|
|
3538
|
+
type: EdgeType6.CONNECTS_TO,
|
|
3539
|
+
provenance: Provenance4.EXTRACTED,
|
|
3540
|
+
confidence: confidenceForExtracted3("structural"),
|
|
3219
3541
|
evidence: { file: evidenceFile }
|
|
3220
3542
|
};
|
|
3221
3543
|
if (!graph.hasEdge(edge.id)) {
|
|
@@ -3241,21 +3563,21 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
|
|
|
3241
3563
|
}
|
|
3242
3564
|
|
|
3243
3565
|
// src/extract/configs.ts
|
|
3244
|
-
import { promises as
|
|
3245
|
-
import
|
|
3566
|
+
import { promises as fs14 } from "fs";
|
|
3567
|
+
import path21 from "path";
|
|
3246
3568
|
import {
|
|
3247
|
-
EdgeType as
|
|
3569
|
+
EdgeType as EdgeType7,
|
|
3248
3570
|
NodeType as NodeType8,
|
|
3249
|
-
Provenance as
|
|
3571
|
+
Provenance as Provenance5,
|
|
3250
3572
|
configId,
|
|
3251
|
-
confidenceForExtracted as
|
|
3573
|
+
confidenceForExtracted as confidenceForExtracted4
|
|
3252
3574
|
} from "@neat.is/types";
|
|
3253
3575
|
async function walkConfigFiles(dir) {
|
|
3254
3576
|
const out = [];
|
|
3255
3577
|
async function walk(current) {
|
|
3256
|
-
const entries = await
|
|
3578
|
+
const entries = await fs14.readdir(current, { withFileTypes: true });
|
|
3257
3579
|
for (const entry of entries) {
|
|
3258
|
-
const full =
|
|
3580
|
+
const full = path21.join(current, entry.name);
|
|
3259
3581
|
if (entry.isDirectory()) {
|
|
3260
3582
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
3261
3583
|
if (await isPythonVenvDir(full)) continue;
|
|
@@ -3274,19 +3596,19 @@ async function addConfigNodes(graph, services, scanPath) {
|
|
|
3274
3596
|
for (const service of services) {
|
|
3275
3597
|
const configFiles = await walkConfigFiles(service.dir);
|
|
3276
3598
|
for (const file of configFiles) {
|
|
3277
|
-
const relPath =
|
|
3599
|
+
const relPath = path21.relative(scanPath, file);
|
|
3278
3600
|
const node = {
|
|
3279
3601
|
id: configId(relPath),
|
|
3280
3602
|
type: NodeType8.ConfigNode,
|
|
3281
|
-
name:
|
|
3603
|
+
name: path21.basename(file),
|
|
3282
3604
|
path: relPath,
|
|
3283
|
-
fileType: isConfigFile(
|
|
3605
|
+
fileType: isConfigFile(path21.basename(file)).fileType
|
|
3284
3606
|
};
|
|
3285
3607
|
if (!graph.hasNode(node.id)) {
|
|
3286
3608
|
graph.addNode(node.id, node);
|
|
3287
3609
|
nodesAdded++;
|
|
3288
3610
|
}
|
|
3289
|
-
const relToService = toPosix2(
|
|
3611
|
+
const relToService = toPosix2(path21.relative(service.dir, file));
|
|
3290
3612
|
const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
|
|
3291
3613
|
graph,
|
|
3292
3614
|
service.pkg.name,
|
|
@@ -3296,13 +3618,13 @@ async function addConfigNodes(graph, services, scanPath) {
|
|
|
3296
3618
|
nodesAdded += fn;
|
|
3297
3619
|
edgesAdded += fe;
|
|
3298
3620
|
const edge = {
|
|
3299
|
-
id: extractedEdgeId2(fileNodeId, node.id,
|
|
3621
|
+
id: extractedEdgeId2(fileNodeId, node.id, EdgeType7.CONFIGURED_BY),
|
|
3300
3622
|
source: fileNodeId,
|
|
3301
3623
|
target: node.id,
|
|
3302
|
-
type:
|
|
3303
|
-
provenance:
|
|
3304
|
-
confidence:
|
|
3305
|
-
evidence: { file: relPath.split(
|
|
3624
|
+
type: EdgeType7.CONFIGURED_BY,
|
|
3625
|
+
provenance: Provenance5.EXTRACTED,
|
|
3626
|
+
confidence: confidenceForExtracted4("structural"),
|
|
3627
|
+
evidence: { file: relPath.split(path21.sep).join("/") }
|
|
3306
3628
|
};
|
|
3307
3629
|
if (!graph.hasEdge(edge.id)) {
|
|
3308
3630
|
graph.addEdgeWithKey(edge.id, edge.source, edge.target, edge);
|
|
@@ -3315,22 +3637,22 @@ async function addConfigNodes(graph, services, scanPath) {
|
|
|
3315
3637
|
|
|
3316
3638
|
// src/extract/calls/index.ts
|
|
3317
3639
|
import {
|
|
3318
|
-
EdgeType as
|
|
3640
|
+
EdgeType as EdgeType9,
|
|
3319
3641
|
NodeType as NodeType9,
|
|
3320
|
-
Provenance as
|
|
3321
|
-
confidenceForExtracted as
|
|
3642
|
+
Provenance as Provenance7,
|
|
3643
|
+
confidenceForExtracted as confidenceForExtracted6,
|
|
3322
3644
|
passesExtractedFloor as passesExtractedFloor2
|
|
3323
3645
|
} from "@neat.is/types";
|
|
3324
3646
|
|
|
3325
3647
|
// src/extract/calls/http.ts
|
|
3326
|
-
import
|
|
3327
|
-
import
|
|
3328
|
-
import
|
|
3329
|
-
import
|
|
3648
|
+
import path22 from "path";
|
|
3649
|
+
import Parser2 from "tree-sitter";
|
|
3650
|
+
import JavaScript2 from "tree-sitter-javascript";
|
|
3651
|
+
import Python2 from "tree-sitter-python";
|
|
3330
3652
|
import {
|
|
3331
|
-
EdgeType as
|
|
3332
|
-
Provenance as
|
|
3333
|
-
confidenceForExtracted as
|
|
3653
|
+
EdgeType as EdgeType8,
|
|
3654
|
+
Provenance as Provenance6,
|
|
3655
|
+
confidenceForExtracted as confidenceForExtracted5,
|
|
3334
3656
|
passesExtractedFloor
|
|
3335
3657
|
} from "@neat.is/types";
|
|
3336
3658
|
var STRING_LITERAL_NODE_TYPES = /* @__PURE__ */ new Set(["string_fragment", "string_content"]);
|
|
@@ -3360,14 +3682,14 @@ function collectStringLiterals(node, out) {
|
|
|
3360
3682
|
if (child) collectStringLiterals(child, out);
|
|
3361
3683
|
}
|
|
3362
3684
|
}
|
|
3363
|
-
var
|
|
3364
|
-
function
|
|
3685
|
+
var PARSE_CHUNK2 = 16384;
|
|
3686
|
+
function parseSource2(parser, source) {
|
|
3365
3687
|
return parser.parse(
|
|
3366
|
-
(index) => index >= source.length ? "" : source.slice(index, index +
|
|
3688
|
+
(index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK2)
|
|
3367
3689
|
);
|
|
3368
3690
|
}
|
|
3369
3691
|
function callsFromSource(source, parser, knownHosts) {
|
|
3370
|
-
const tree =
|
|
3692
|
+
const tree = parseSource2(parser, source);
|
|
3371
3693
|
const literals = [];
|
|
3372
3694
|
collectStringLiterals(tree.rootNode, literals);
|
|
3373
3695
|
const out = [];
|
|
@@ -3381,25 +3703,25 @@ function callsFromSource(source, parser, knownHosts) {
|
|
|
3381
3703
|
}
|
|
3382
3704
|
return out;
|
|
3383
3705
|
}
|
|
3384
|
-
function
|
|
3385
|
-
const p = new
|
|
3386
|
-
p.setLanguage(
|
|
3706
|
+
function makeJsParser2() {
|
|
3707
|
+
const p = new Parser2();
|
|
3708
|
+
p.setLanguage(JavaScript2);
|
|
3387
3709
|
return p;
|
|
3388
3710
|
}
|
|
3389
|
-
function
|
|
3390
|
-
const p = new
|
|
3391
|
-
p.setLanguage(
|
|
3711
|
+
function makePyParser2() {
|
|
3712
|
+
const p = new Parser2();
|
|
3713
|
+
p.setLanguage(Python2);
|
|
3392
3714
|
return p;
|
|
3393
3715
|
}
|
|
3394
3716
|
async function addHttpCallEdges(graph, services) {
|
|
3395
|
-
const jsParser =
|
|
3396
|
-
const pyParser =
|
|
3717
|
+
const jsParser = makeJsParser2();
|
|
3718
|
+
const pyParser = makePyParser2();
|
|
3397
3719
|
const knownHosts = /* @__PURE__ */ new Set();
|
|
3398
3720
|
const hostToNodeId = /* @__PURE__ */ new Map();
|
|
3399
3721
|
for (const service of services) {
|
|
3400
|
-
knownHosts.add(
|
|
3722
|
+
knownHosts.add(path22.basename(service.dir));
|
|
3401
3723
|
knownHosts.add(service.pkg.name);
|
|
3402
|
-
hostToNodeId.set(
|
|
3724
|
+
hostToNodeId.set(path22.basename(service.dir), service.node.id);
|
|
3403
3725
|
hostToNodeId.set(service.pkg.name, service.node.id);
|
|
3404
3726
|
}
|
|
3405
3727
|
let nodesAdded = 0;
|
|
@@ -3409,7 +3731,7 @@ async function addHttpCallEdges(graph, services) {
|
|
|
3409
3731
|
const seen = /* @__PURE__ */ new Set();
|
|
3410
3732
|
for (const file of files) {
|
|
3411
3733
|
if (isTestPath(file.path)) continue;
|
|
3412
|
-
const parser =
|
|
3734
|
+
const parser = path22.extname(file.path) === ".py" ? pyParser : jsParser;
|
|
3413
3735
|
let sites;
|
|
3414
3736
|
try {
|
|
3415
3737
|
sites = callsFromSource(file.content, parser, knownHosts);
|
|
@@ -3418,14 +3740,14 @@ async function addHttpCallEdges(graph, services) {
|
|
|
3418
3740
|
continue;
|
|
3419
3741
|
}
|
|
3420
3742
|
if (sites.length === 0) continue;
|
|
3421
|
-
const relFile = toPosix2(
|
|
3743
|
+
const relFile = toPosix2(path22.relative(service.dir, file.path));
|
|
3422
3744
|
for (const site of sites) {
|
|
3423
3745
|
const targetId = hostToNodeId.get(site.host);
|
|
3424
3746
|
if (!targetId || targetId === service.node.id) continue;
|
|
3425
3747
|
const dedupKey = `${relFile}|${targetId}`;
|
|
3426
3748
|
if (seen.has(dedupKey)) continue;
|
|
3427
3749
|
seen.add(dedupKey);
|
|
3428
|
-
const confidence =
|
|
3750
|
+
const confidence = confidenceForExtracted5("hostname-shape-match");
|
|
3429
3751
|
const ev = {
|
|
3430
3752
|
file: relFile,
|
|
3431
3753
|
line: site.line,
|
|
@@ -3443,21 +3765,21 @@ async function addHttpCallEdges(graph, services) {
|
|
|
3443
3765
|
noteExtractedDropped({
|
|
3444
3766
|
source: fileNodeId,
|
|
3445
3767
|
target: targetId,
|
|
3446
|
-
type:
|
|
3768
|
+
type: EdgeType8.CALLS,
|
|
3447
3769
|
confidence,
|
|
3448
3770
|
confidenceKind: "hostname-shape-match",
|
|
3449
3771
|
evidence: ev
|
|
3450
3772
|
});
|
|
3451
3773
|
continue;
|
|
3452
3774
|
}
|
|
3453
|
-
const edgeId = extractedEdgeId2(fileNodeId, targetId,
|
|
3775
|
+
const edgeId = extractedEdgeId2(fileNodeId, targetId, EdgeType8.CALLS);
|
|
3454
3776
|
if (!graph.hasEdge(edgeId)) {
|
|
3455
3777
|
const edge = {
|
|
3456
3778
|
id: edgeId,
|
|
3457
3779
|
source: fileNodeId,
|
|
3458
3780
|
target: targetId,
|
|
3459
|
-
type:
|
|
3460
|
-
provenance:
|
|
3781
|
+
type: EdgeType8.CALLS,
|
|
3782
|
+
provenance: Provenance6.EXTRACTED,
|
|
3461
3783
|
confidence,
|
|
3462
3784
|
evidence: ev
|
|
3463
3785
|
};
|
|
@@ -3471,7 +3793,7 @@ async function addHttpCallEdges(graph, services) {
|
|
|
3471
3793
|
}
|
|
3472
3794
|
|
|
3473
3795
|
// src/extract/calls/kafka.ts
|
|
3474
|
-
import
|
|
3796
|
+
import path23 from "path";
|
|
3475
3797
|
import { infraId } from "@neat.is/types";
|
|
3476
3798
|
var PRODUCER_TOPIC_RE = /(?:producer|kafkaProducer)[\s\S]{0,40}?\.send\s*\(\s*\{[\s\S]{0,200}?topic\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
3477
3799
|
var CONSUMER_TOPIC_RE = /(?:consumer|kafkaConsumer)[\s\S]{0,40}?\.(?:subscribe|run)\s*\(\s*\{[\s\S]{0,200}?topic[s]?\s*:\s*(?:\[\s*)?['"`]([^'"`]+)['"`]/g;
|
|
@@ -3502,7 +3824,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
|
|
|
3502
3824
|
// tier (ADR-066).
|
|
3503
3825
|
confidenceKind: "verified-call-site",
|
|
3504
3826
|
evidence: {
|
|
3505
|
-
file:
|
|
3827
|
+
file: path23.relative(serviceDir, file.path),
|
|
3506
3828
|
line,
|
|
3507
3829
|
snippet: snippet(file.content, line)
|
|
3508
3830
|
}
|
|
@@ -3514,7 +3836,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
|
|
|
3514
3836
|
}
|
|
3515
3837
|
|
|
3516
3838
|
// src/extract/calls/redis.ts
|
|
3517
|
-
import
|
|
3839
|
+
import path24 from "path";
|
|
3518
3840
|
import { infraId as infraId2 } from "@neat.is/types";
|
|
3519
3841
|
var REDIS_URL_RE = /redis(?:s)?:\/\/(?:[^@'"`\s]+@)?([^:/'"`\s]+)(?::(\d+))?/g;
|
|
3520
3842
|
function redisEndpointsFromFile(file, serviceDir) {
|
|
@@ -3537,7 +3859,7 @@ function redisEndpointsFromFile(file, serviceDir) {
|
|
|
3537
3859
|
// support tier (ADR-066).
|
|
3538
3860
|
confidenceKind: "url-with-structural-support",
|
|
3539
3861
|
evidence: {
|
|
3540
|
-
file:
|
|
3862
|
+
file: path24.relative(serviceDir, file.path),
|
|
3541
3863
|
line,
|
|
3542
3864
|
snippet: snippet(file.content, line)
|
|
3543
3865
|
}
|
|
@@ -3547,7 +3869,7 @@ function redisEndpointsFromFile(file, serviceDir) {
|
|
|
3547
3869
|
}
|
|
3548
3870
|
|
|
3549
3871
|
// src/extract/calls/aws.ts
|
|
3550
|
-
import
|
|
3872
|
+
import path25 from "path";
|
|
3551
3873
|
import { infraId as infraId3 } from "@neat.is/types";
|
|
3552
3874
|
var S3_BUCKET_RE = /Bucket\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
3553
3875
|
var DYNAMO_TABLE_RE = /TableName\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
@@ -3581,7 +3903,7 @@ function awsEndpointsFromFile(file, serviceDir) {
|
|
|
3581
3903
|
// (ADR-066).
|
|
3582
3904
|
confidenceKind: "verified-call-site",
|
|
3583
3905
|
evidence: {
|
|
3584
|
-
file:
|
|
3906
|
+
file: path25.relative(serviceDir, file.path),
|
|
3585
3907
|
line,
|
|
3586
3908
|
snippet: snippet(file.content, line)
|
|
3587
3909
|
}
|
|
@@ -3605,7 +3927,7 @@ function awsEndpointsFromFile(file, serviceDir) {
|
|
|
3605
3927
|
}
|
|
3606
3928
|
|
|
3607
3929
|
// src/extract/calls/grpc.ts
|
|
3608
|
-
import
|
|
3930
|
+
import path26 from "path";
|
|
3609
3931
|
import { infraId as infraId4 } from "@neat.is/types";
|
|
3610
3932
|
var GRPC_CLIENT_RE = /new\s+([A-Z][A-Za-z0-9_]*)Client\s*\(\s*['"`]?([^,'"`)]+)?/g;
|
|
3611
3933
|
var AWS_SDK_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@aws-sdk\/client-([a-z0-9-]+)['"`]/g;
|
|
@@ -3664,7 +3986,7 @@ function grpcEndpointsFromFile(file, serviceDir) {
|
|
|
3664
3986
|
// tier (ADR-066).
|
|
3665
3987
|
confidenceKind: "verified-call-site",
|
|
3666
3988
|
evidence: {
|
|
3667
|
-
file:
|
|
3989
|
+
file: path26.relative(serviceDir, file.path),
|
|
3668
3990
|
line,
|
|
3669
3991
|
snippet: snippet(file.content, line)
|
|
3670
3992
|
}
|
|
@@ -3677,11 +3999,11 @@ function grpcEndpointsFromFile(file, serviceDir) {
|
|
|
3677
3999
|
function edgeTypeFromEndpoint(ep) {
|
|
3678
4000
|
switch (ep.edgeType) {
|
|
3679
4001
|
case "PUBLISHES_TO":
|
|
3680
|
-
return
|
|
4002
|
+
return EdgeType9.PUBLISHES_TO;
|
|
3681
4003
|
case "CONSUMES_FROM":
|
|
3682
|
-
return
|
|
4004
|
+
return EdgeType9.CONSUMES_FROM;
|
|
3683
4005
|
default:
|
|
3684
|
-
return
|
|
4006
|
+
return EdgeType9.CALLS;
|
|
3685
4007
|
}
|
|
3686
4008
|
}
|
|
3687
4009
|
function isAwsKind(kind) {
|
|
@@ -3720,7 +4042,7 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
3720
4042
|
nodesAdded++;
|
|
3721
4043
|
}
|
|
3722
4044
|
const edgeType = edgeTypeFromEndpoint(ep);
|
|
3723
|
-
const confidence =
|
|
4045
|
+
const confidence = confidenceForExtracted6(ep.confidenceKind);
|
|
3724
4046
|
const relFile = toPosix2(ep.evidence.file);
|
|
3725
4047
|
const { fileNodeId, nodesAdded: n, edgesAdded: e } = ensureFileNode(
|
|
3726
4048
|
graph,
|
|
@@ -3750,7 +4072,7 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
3750
4072
|
source: fileNodeId,
|
|
3751
4073
|
target: ep.infraId,
|
|
3752
4074
|
type: edgeType,
|
|
3753
|
-
provenance:
|
|
4075
|
+
provenance: Provenance7.EXTRACTED,
|
|
3754
4076
|
confidence,
|
|
3755
4077
|
evidence: ep.evidence
|
|
3756
4078
|
};
|
|
@@ -3771,8 +4093,8 @@ async function addCallEdges(graph, services) {
|
|
|
3771
4093
|
}
|
|
3772
4094
|
|
|
3773
4095
|
// src/extract/infra/docker-compose.ts
|
|
3774
|
-
import
|
|
3775
|
-
import { EdgeType as
|
|
4096
|
+
import path27 from "path";
|
|
4097
|
+
import { EdgeType as EdgeType10, Provenance as Provenance8, confidenceForExtracted as confidenceForExtracted7 } from "@neat.is/types";
|
|
3776
4098
|
|
|
3777
4099
|
// src/extract/infra/shared.ts
|
|
3778
4100
|
import { NodeType as NodeType10, infraId as infraId5 } from "@neat.is/types";
|
|
@@ -3808,7 +4130,7 @@ function dependsOnList(value) {
|
|
|
3808
4130
|
}
|
|
3809
4131
|
function serviceNameToServiceNode(name, services) {
|
|
3810
4132
|
for (const s of services) {
|
|
3811
|
-
if (s.node.name === name ||
|
|
4133
|
+
if (s.node.name === name || path27.basename(s.dir) === name) return s.node.id;
|
|
3812
4134
|
}
|
|
3813
4135
|
return null;
|
|
3814
4136
|
}
|
|
@@ -3817,7 +4139,7 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
3817
4139
|
let edgesAdded = 0;
|
|
3818
4140
|
let composePath = null;
|
|
3819
4141
|
for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
|
|
3820
|
-
const abs =
|
|
4142
|
+
const abs = path27.join(scanPath, name);
|
|
3821
4143
|
if (await exists(abs)) {
|
|
3822
4144
|
composePath = abs;
|
|
3823
4145
|
break;
|
|
@@ -3830,13 +4152,13 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
3830
4152
|
} catch (err) {
|
|
3831
4153
|
recordExtractionError(
|
|
3832
4154
|
"infra docker-compose",
|
|
3833
|
-
|
|
4155
|
+
path27.relative(scanPath, composePath),
|
|
3834
4156
|
err
|
|
3835
4157
|
);
|
|
3836
4158
|
return { nodesAdded, edgesAdded };
|
|
3837
4159
|
}
|
|
3838
4160
|
if (!compose?.services) return { nodesAdded, edgesAdded };
|
|
3839
|
-
const evidenceFile =
|
|
4161
|
+
const evidenceFile = path27.relative(scanPath, composePath).split(path27.sep).join("/");
|
|
3840
4162
|
const composeNameToNodeId = /* @__PURE__ */ new Map();
|
|
3841
4163
|
for (const [composeName, svc] of Object.entries(compose.services)) {
|
|
3842
4164
|
const matchedServiceId = serviceNameToServiceNode(composeName, services);
|
|
@@ -3858,15 +4180,15 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
3858
4180
|
for (const dep of dependsOnList(svc.depends_on)) {
|
|
3859
4181
|
const targetId = composeNameToNodeId.get(dep);
|
|
3860
4182
|
if (!targetId) continue;
|
|
3861
|
-
const edgeId = extractedEdgeId2(sourceId, targetId,
|
|
4183
|
+
const edgeId = extractedEdgeId2(sourceId, targetId, EdgeType10.DEPENDS_ON);
|
|
3862
4184
|
if (graph.hasEdge(edgeId)) continue;
|
|
3863
4185
|
const edge = {
|
|
3864
4186
|
id: edgeId,
|
|
3865
4187
|
source: sourceId,
|
|
3866
4188
|
target: targetId,
|
|
3867
|
-
type:
|
|
3868
|
-
provenance:
|
|
3869
|
-
confidence:
|
|
4189
|
+
type: EdgeType10.DEPENDS_ON,
|
|
4190
|
+
provenance: Provenance8.EXTRACTED,
|
|
4191
|
+
confidence: confidenceForExtracted7("structural"),
|
|
3870
4192
|
evidence: { file: evidenceFile }
|
|
3871
4193
|
};
|
|
3872
4194
|
graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
|
|
@@ -3877,9 +4199,9 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
3877
4199
|
}
|
|
3878
4200
|
|
|
3879
4201
|
// src/extract/infra/dockerfile.ts
|
|
3880
|
-
import
|
|
3881
|
-
import { promises as
|
|
3882
|
-
import { EdgeType as
|
|
4202
|
+
import path28 from "path";
|
|
4203
|
+
import { promises as fs15 } from "fs";
|
|
4204
|
+
import { EdgeType as EdgeType11, Provenance as Provenance9, confidenceForExtracted as confidenceForExtracted8 } from "@neat.is/types";
|
|
3883
4205
|
function runtimeImage(content) {
|
|
3884
4206
|
const lines = content.split("\n");
|
|
3885
4207
|
let last = null;
|
|
@@ -3898,15 +4220,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
3898
4220
|
let nodesAdded = 0;
|
|
3899
4221
|
let edgesAdded = 0;
|
|
3900
4222
|
for (const service of services) {
|
|
3901
|
-
const dockerfilePath =
|
|
4223
|
+
const dockerfilePath = path28.join(service.dir, "Dockerfile");
|
|
3902
4224
|
if (!await exists(dockerfilePath)) continue;
|
|
3903
4225
|
let content;
|
|
3904
4226
|
try {
|
|
3905
|
-
content = await
|
|
4227
|
+
content = await fs15.readFile(dockerfilePath, "utf8");
|
|
3906
4228
|
} catch (err) {
|
|
3907
4229
|
recordExtractionError(
|
|
3908
4230
|
"infra dockerfile",
|
|
3909
|
-
|
|
4231
|
+
path28.relative(scanPath, dockerfilePath),
|
|
3910
4232
|
err
|
|
3911
4233
|
);
|
|
3912
4234
|
continue;
|
|
@@ -3918,7 +4240,7 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
3918
4240
|
graph.addNode(node.id, node);
|
|
3919
4241
|
nodesAdded++;
|
|
3920
4242
|
}
|
|
3921
|
-
const relDockerfile = toPosix2(
|
|
4243
|
+
const relDockerfile = toPosix2(path28.relative(service.dir, dockerfilePath));
|
|
3922
4244
|
const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
|
|
3923
4245
|
graph,
|
|
3924
4246
|
service.pkg.name,
|
|
@@ -3927,17 +4249,17 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
3927
4249
|
);
|
|
3928
4250
|
nodesAdded += fn;
|
|
3929
4251
|
edgesAdded += fe;
|
|
3930
|
-
const edgeId = extractedEdgeId2(fileNodeId, node.id,
|
|
4252
|
+
const edgeId = extractedEdgeId2(fileNodeId, node.id, EdgeType11.RUNS_ON);
|
|
3931
4253
|
if (!graph.hasEdge(edgeId)) {
|
|
3932
4254
|
const edge = {
|
|
3933
4255
|
id: edgeId,
|
|
3934
4256
|
source: fileNodeId,
|
|
3935
4257
|
target: node.id,
|
|
3936
|
-
type:
|
|
3937
|
-
provenance:
|
|
3938
|
-
confidence:
|
|
4258
|
+
type: EdgeType11.RUNS_ON,
|
|
4259
|
+
provenance: Provenance9.EXTRACTED,
|
|
4260
|
+
confidence: confidenceForExtracted8("structural"),
|
|
3939
4261
|
evidence: {
|
|
3940
|
-
file: toPosix2(
|
|
4262
|
+
file: toPosix2(path28.relative(scanPath, dockerfilePath))
|
|
3941
4263
|
}
|
|
3942
4264
|
};
|
|
3943
4265
|
graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
|
|
@@ -3948,21 +4270,21 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
3948
4270
|
}
|
|
3949
4271
|
|
|
3950
4272
|
// src/extract/infra/terraform.ts
|
|
3951
|
-
import { promises as
|
|
3952
|
-
import
|
|
4273
|
+
import { promises as fs16 } from "fs";
|
|
4274
|
+
import path29 from "path";
|
|
3953
4275
|
var RESOURCE_RE = /resource\s+"(aws_[A-Za-z0-9_]+)"\s+"([A-Za-z0-9_-]+)"/g;
|
|
3954
4276
|
async function walkTfFiles(start, depth = 0, max = 5) {
|
|
3955
4277
|
if (depth > max) return [];
|
|
3956
4278
|
const out = [];
|
|
3957
|
-
const entries = await
|
|
4279
|
+
const entries = await fs16.readdir(start, { withFileTypes: true }).catch(() => []);
|
|
3958
4280
|
for (const entry of entries) {
|
|
3959
4281
|
if (entry.isDirectory()) {
|
|
3960
4282
|
if (IGNORED_DIRS.has(entry.name) || entry.name === ".terraform") continue;
|
|
3961
|
-
const child =
|
|
4283
|
+
const child = path29.join(start, entry.name);
|
|
3962
4284
|
if (await isPythonVenvDir(child)) continue;
|
|
3963
4285
|
out.push(...await walkTfFiles(child, depth + 1, max));
|
|
3964
4286
|
} else if (entry.isFile() && entry.name.endsWith(".tf")) {
|
|
3965
|
-
out.push(
|
|
4287
|
+
out.push(path29.join(start, entry.name));
|
|
3966
4288
|
}
|
|
3967
4289
|
}
|
|
3968
4290
|
return out;
|
|
@@ -3971,7 +4293,7 @@ async function addTerraformResources(graph, scanPath) {
|
|
|
3971
4293
|
let nodesAdded = 0;
|
|
3972
4294
|
const files = await walkTfFiles(scanPath);
|
|
3973
4295
|
for (const file of files) {
|
|
3974
|
-
const content = await
|
|
4296
|
+
const content = await fs16.readFile(file, "utf8");
|
|
3975
4297
|
RESOURCE_RE.lastIndex = 0;
|
|
3976
4298
|
let m;
|
|
3977
4299
|
while ((m = RESOURCE_RE.exec(content)) !== null) {
|
|
@@ -3988,8 +4310,8 @@ async function addTerraformResources(graph, scanPath) {
|
|
|
3988
4310
|
}
|
|
3989
4311
|
|
|
3990
4312
|
// src/extract/infra/k8s.ts
|
|
3991
|
-
import { promises as
|
|
3992
|
-
import
|
|
4313
|
+
import { promises as fs17 } from "fs";
|
|
4314
|
+
import path30 from "path";
|
|
3993
4315
|
import { parseAllDocuments as parseAllDocuments2 } from "yaml";
|
|
3994
4316
|
var K8S_KIND_TO_INFRA_KIND = {
|
|
3995
4317
|
Service: "k8s-service",
|
|
@@ -4003,15 +4325,15 @@ var K8S_KIND_TO_INFRA_KIND = {
|
|
|
4003
4325
|
async function walkYamlFiles2(start, depth = 0, max = 5) {
|
|
4004
4326
|
if (depth > max) return [];
|
|
4005
4327
|
const out = [];
|
|
4006
|
-
const entries = await
|
|
4328
|
+
const entries = await fs17.readdir(start, { withFileTypes: true }).catch(() => []);
|
|
4007
4329
|
for (const entry of entries) {
|
|
4008
4330
|
if (entry.isDirectory()) {
|
|
4009
4331
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
4010
|
-
const child =
|
|
4332
|
+
const child = path30.join(start, entry.name);
|
|
4011
4333
|
if (await isPythonVenvDir(child)) continue;
|
|
4012
4334
|
out.push(...await walkYamlFiles2(child, depth + 1, max));
|
|
4013
|
-
} else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(
|
|
4014
|
-
out.push(
|
|
4335
|
+
} else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(path30.extname(entry.name))) {
|
|
4336
|
+
out.push(path30.join(start, entry.name));
|
|
4015
4337
|
}
|
|
4016
4338
|
}
|
|
4017
4339
|
return out;
|
|
@@ -4020,7 +4342,7 @@ async function addK8sResources(graph, scanPath) {
|
|
|
4020
4342
|
let nodesAdded = 0;
|
|
4021
4343
|
const files = await walkYamlFiles2(scanPath);
|
|
4022
4344
|
for (const file of files) {
|
|
4023
|
-
const content = await
|
|
4345
|
+
const content = await fs17.readFile(file, "utf8");
|
|
4024
4346
|
let docs;
|
|
4025
4347
|
try {
|
|
4026
4348
|
docs = parseAllDocuments2(content).map((d) => d.toJSON());
|
|
@@ -4055,12 +4377,12 @@ async function addInfra(graph, scanPath, services) {
|
|
|
4055
4377
|
}
|
|
4056
4378
|
|
|
4057
4379
|
// src/extract/index.ts
|
|
4058
|
-
import
|
|
4380
|
+
import path32 from "path";
|
|
4059
4381
|
|
|
4060
4382
|
// src/extract/retire.ts
|
|
4061
4383
|
import { existsSync as existsSync2 } from "fs";
|
|
4062
|
-
import
|
|
4063
|
-
import { NodeType as NodeType11, Provenance as
|
|
4384
|
+
import path31 from "path";
|
|
4385
|
+
import { NodeType as NodeType11, Provenance as Provenance10 } from "@neat.is/types";
|
|
4064
4386
|
function dropOrphanedFileNodes(graph) {
|
|
4065
4387
|
const orphans = [];
|
|
4066
4388
|
graph.forEachNode((id, attrs) => {
|
|
@@ -4077,7 +4399,7 @@ function retireEdgesByFile(graph, file) {
|
|
|
4077
4399
|
const toDrop = [];
|
|
4078
4400
|
graph.forEachEdge((id, attrs) => {
|
|
4079
4401
|
const edge = attrs;
|
|
4080
|
-
if (edge.provenance !==
|
|
4402
|
+
if (edge.provenance !== Provenance10.EXTRACTED) return;
|
|
4081
4403
|
if (!edge.evidence?.file) return;
|
|
4082
4404
|
if (edge.evidence.file === normalized) toDrop.push(id);
|
|
4083
4405
|
});
|
|
@@ -4090,14 +4412,14 @@ function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = []) {
|
|
|
4090
4412
|
const bases = [scanPath, ...serviceDirs];
|
|
4091
4413
|
graph.forEachEdge((id, attrs) => {
|
|
4092
4414
|
const edge = attrs;
|
|
4093
|
-
if (edge.provenance !==
|
|
4415
|
+
if (edge.provenance !== Provenance10.EXTRACTED) return;
|
|
4094
4416
|
const evidenceFile = edge.evidence?.file;
|
|
4095
4417
|
if (!evidenceFile) return;
|
|
4096
|
-
if (
|
|
4418
|
+
if (path31.isAbsolute(evidenceFile)) {
|
|
4097
4419
|
if (!existsSync2(evidenceFile)) toDrop.push(id);
|
|
4098
4420
|
return;
|
|
4099
4421
|
}
|
|
4100
|
-
const found = bases.some((base) => existsSync2(
|
|
4422
|
+
const found = bases.some((base) => existsSync2(path31.join(base, evidenceFile)));
|
|
4101
4423
|
if (!found) toDrop.push(id);
|
|
4102
4424
|
});
|
|
4103
4425
|
for (const id of toDrop) graph.dropEdge(id);
|
|
@@ -4112,6 +4434,8 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
4112
4434
|
const services = await discoverServices(scanPath);
|
|
4113
4435
|
const phase1Nodes = addServiceNodes(graph, services);
|
|
4114
4436
|
await addServiceAliases(graph, scanPath, services);
|
|
4437
|
+
const fileEnum = await addFiles(graph, services);
|
|
4438
|
+
const importGraph = await addImports(graph, services);
|
|
4115
4439
|
const phase2 = await addDatabasesAndCompat(graph, services, scanPath);
|
|
4116
4440
|
const phase3 = await addConfigNodes(graph, services, scanPath);
|
|
4117
4441
|
const phase4 = await addCallEdges(graph, services);
|
|
@@ -4135,7 +4459,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
4135
4459
|
}
|
|
4136
4460
|
const droppedEntries = drainDroppedExtracted();
|
|
4137
4461
|
if (isRejectedLogEnabled() && opts.errorsPath && droppedEntries.length > 0) {
|
|
4138
|
-
const rejectedPath =
|
|
4462
|
+
const rejectedPath = path32.join(path32.dirname(opts.errorsPath), "rejected.ndjson");
|
|
4139
4463
|
try {
|
|
4140
4464
|
await writeRejectedExtracted(droppedEntries, rejectedPath);
|
|
4141
4465
|
} catch (err) {
|
|
@@ -4145,8 +4469,8 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
4145
4469
|
}
|
|
4146
4470
|
}
|
|
4147
4471
|
const result = {
|
|
4148
|
-
nodesAdded: phase1Nodes + phase2.nodesAdded + phase3.nodesAdded + phase4.nodesAdded + phase5.nodesAdded,
|
|
4149
|
-
edgesAdded: phase2.edgesAdded + phase3.edgesAdded + phase4.edgesAdded + phase5.edgesAdded,
|
|
4472
|
+
nodesAdded: phase1Nodes + fileEnum.nodesAdded + importGraph.nodesAdded + phase2.nodesAdded + phase3.nodesAdded + phase4.nodesAdded + phase5.nodesAdded,
|
|
4473
|
+
edgesAdded: fileEnum.edgesAdded + importGraph.edgesAdded + phase2.edgesAdded + phase3.edgesAdded + phase4.edgesAdded + phase5.edgesAdded,
|
|
4150
4474
|
frontiersPromoted,
|
|
4151
4475
|
extractionErrors: errorEntries.length,
|
|
4152
4476
|
errorEntries,
|
|
@@ -4170,10 +4494,10 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
4170
4494
|
// src/divergences.ts
|
|
4171
4495
|
import {
|
|
4172
4496
|
DivergenceResultSchema,
|
|
4173
|
-
EdgeType as
|
|
4497
|
+
EdgeType as EdgeType12,
|
|
4174
4498
|
NodeType as NodeType12,
|
|
4175
4499
|
parseEdgeId,
|
|
4176
|
-
Provenance as
|
|
4500
|
+
Provenance as Provenance11
|
|
4177
4501
|
} from "@neat.is/types";
|
|
4178
4502
|
function bucketKey(source, target, type) {
|
|
4179
4503
|
return `${type}|${source}|${target}`;
|
|
@@ -4187,17 +4511,17 @@ function bucketEdges(graph) {
|
|
|
4187
4511
|
const key = bucketKey(e.source, e.target, e.type);
|
|
4188
4512
|
const cur = buckets.get(key) ?? { source: e.source, target: e.target, type: e.type };
|
|
4189
4513
|
switch (provenance) {
|
|
4190
|
-
case
|
|
4514
|
+
case Provenance11.EXTRACTED:
|
|
4191
4515
|
cur.extracted = e;
|
|
4192
4516
|
break;
|
|
4193
|
-
case
|
|
4517
|
+
case Provenance11.OBSERVED:
|
|
4194
4518
|
cur.observed = e;
|
|
4195
4519
|
break;
|
|
4196
|
-
case
|
|
4520
|
+
case Provenance11.INFERRED:
|
|
4197
4521
|
cur.inferred = e;
|
|
4198
4522
|
break;
|
|
4199
4523
|
default:
|
|
4200
|
-
if (e.provenance ===
|
|
4524
|
+
if (e.provenance === Provenance11.STALE) cur.stale = e;
|
|
4201
4525
|
}
|
|
4202
4526
|
buckets.set(key, cur);
|
|
4203
4527
|
});
|
|
@@ -4227,7 +4551,7 @@ function gradedConfidence(edge) {
|
|
|
4227
4551
|
}
|
|
4228
4552
|
function detectMissingDivergences(graph, bucket) {
|
|
4229
4553
|
const out = [];
|
|
4230
|
-
if (bucket.type ===
|
|
4554
|
+
if (bucket.type === EdgeType12.CONTAINS) return out;
|
|
4231
4555
|
if (bucket.extracted && !bucket.observed) {
|
|
4232
4556
|
if (!nodeIsFrontier(graph, bucket.target)) {
|
|
4233
4557
|
out.push({
|
|
@@ -4268,7 +4592,7 @@ function declaredHostFor(svc) {
|
|
|
4268
4592
|
function hasExtractedConfiguredBy(graph, svcId) {
|
|
4269
4593
|
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
4270
4594
|
const e = graph.getEdgeAttributes(edgeId);
|
|
4271
|
-
if (e.type ===
|
|
4595
|
+
if (e.type === EdgeType12.CONFIGURED_BY && e.provenance === Provenance11.EXTRACTED) {
|
|
4272
4596
|
return true;
|
|
4273
4597
|
}
|
|
4274
4598
|
}
|
|
@@ -4281,8 +4605,8 @@ function detectHostMismatch(graph, svcId, svc) {
|
|
|
4281
4605
|
const out = [];
|
|
4282
4606
|
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
4283
4607
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
4284
|
-
if (edge.type !==
|
|
4285
|
-
if (edge.provenance !==
|
|
4608
|
+
if (edge.type !== EdgeType12.CONNECTS_TO) continue;
|
|
4609
|
+
if (edge.provenance !== Provenance11.OBSERVED) continue;
|
|
4286
4610
|
const target = graph.getNodeAttributes(edge.target);
|
|
4287
4611
|
if (target.type !== NodeType12.DatabaseNode) continue;
|
|
4288
4612
|
const observedHost = target.host?.trim();
|
|
@@ -4306,8 +4630,8 @@ function detectCompatDivergences(graph, svcId, svc) {
|
|
|
4306
4630
|
const deps = svc.dependencies ?? {};
|
|
4307
4631
|
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
4308
4632
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
4309
|
-
if (edge.type !==
|
|
4310
|
-
if (edge.provenance !==
|
|
4633
|
+
if (edge.type !== EdgeType12.CONNECTS_TO) continue;
|
|
4634
|
+
if (edge.provenance !== Provenance11.OBSERVED) continue;
|
|
4311
4635
|
const target = graph.getNodeAttributes(edge.target);
|
|
4312
4636
|
if (target.type !== NodeType12.DatabaseNode) continue;
|
|
4313
4637
|
for (const pair of compatPairs()) {
|
|
@@ -4411,9 +4735,9 @@ function computeDivergences(graph, opts = {}) {
|
|
|
4411
4735
|
}
|
|
4412
4736
|
|
|
4413
4737
|
// src/persist.ts
|
|
4414
|
-
import { promises as
|
|
4415
|
-
import
|
|
4416
|
-
import { Provenance as
|
|
4738
|
+
import { promises as fs18 } from "fs";
|
|
4739
|
+
import path33 from "path";
|
|
4740
|
+
import { Provenance as Provenance12, observedEdgeId as observedEdgeId2 } from "@neat.is/types";
|
|
4417
4741
|
var SCHEMA_VERSION = 4;
|
|
4418
4742
|
function migrateV1ToV2(payload) {
|
|
4419
4743
|
const nodes = payload.graph.nodes;
|
|
@@ -4435,7 +4759,7 @@ function migrateV2ToV3(payload) {
|
|
|
4435
4759
|
for (const edge of edges) {
|
|
4436
4760
|
const attrs = edge.attributes;
|
|
4437
4761
|
if (!attrs || attrs.provenance !== "FRONTIER") continue;
|
|
4438
|
-
attrs.provenance =
|
|
4762
|
+
attrs.provenance = Provenance12.OBSERVED;
|
|
4439
4763
|
const type = typeof attrs.type === "string" ? attrs.type : void 0;
|
|
4440
4764
|
const source = typeof attrs.source === "string" ? attrs.source : void 0;
|
|
4441
4765
|
const target = typeof attrs.target === "string" ? attrs.target : void 0;
|
|
@@ -4449,7 +4773,7 @@ function migrateV2ToV3(payload) {
|
|
|
4449
4773
|
return { ...payload, schemaVersion: 3 };
|
|
4450
4774
|
}
|
|
4451
4775
|
async function ensureDir(filePath) {
|
|
4452
|
-
await
|
|
4776
|
+
await fs18.mkdir(path33.dirname(filePath), { recursive: true });
|
|
4453
4777
|
}
|
|
4454
4778
|
async function saveGraphToDisk(graph, outPath) {
|
|
4455
4779
|
await ensureDir(outPath);
|
|
@@ -4459,13 +4783,13 @@ async function saveGraphToDisk(graph, outPath) {
|
|
|
4459
4783
|
graph: graph.export()
|
|
4460
4784
|
};
|
|
4461
4785
|
const tmp = `${outPath}.tmp`;
|
|
4462
|
-
await
|
|
4463
|
-
await
|
|
4786
|
+
await fs18.writeFile(tmp, JSON.stringify(payload), "utf8");
|
|
4787
|
+
await fs18.rename(tmp, outPath);
|
|
4464
4788
|
}
|
|
4465
4789
|
async function loadGraphFromDisk(graph, outPath) {
|
|
4466
4790
|
let raw;
|
|
4467
4791
|
try {
|
|
4468
|
-
raw = await
|
|
4792
|
+
raw = await fs18.readFile(outPath, "utf8");
|
|
4469
4793
|
} catch (err) {
|
|
4470
4794
|
if (err.code === "ENOENT") return;
|
|
4471
4795
|
throw err;
|
|
@@ -4523,7 +4847,7 @@ function startPersistLoop(graph, outPath, intervalMs = 6e4) {
|
|
|
4523
4847
|
}
|
|
4524
4848
|
|
|
4525
4849
|
// src/diff.ts
|
|
4526
|
-
import { promises as
|
|
4850
|
+
import { promises as fs19 } from "fs";
|
|
4527
4851
|
async function loadSnapshotForDiff(target) {
|
|
4528
4852
|
if (/^https?:\/\//i.test(target)) {
|
|
4529
4853
|
const res = await fetch(target);
|
|
@@ -4532,7 +4856,7 @@ async function loadSnapshotForDiff(target) {
|
|
|
4532
4856
|
}
|
|
4533
4857
|
return await res.json();
|
|
4534
4858
|
}
|
|
4535
|
-
const raw = await
|
|
4859
|
+
const raw = await fs19.readFile(target, "utf8");
|
|
4536
4860
|
return JSON.parse(raw);
|
|
4537
4861
|
}
|
|
4538
4862
|
function indexEntries(entries) {
|
|
@@ -4599,23 +4923,23 @@ function canonicalJson(value) {
|
|
|
4599
4923
|
}
|
|
4600
4924
|
|
|
4601
4925
|
// src/projects.ts
|
|
4602
|
-
import
|
|
4926
|
+
import path34 from "path";
|
|
4603
4927
|
function pathsForProject(project, baseDir) {
|
|
4604
4928
|
if (project === DEFAULT_PROJECT) {
|
|
4605
4929
|
return {
|
|
4606
|
-
snapshotPath:
|
|
4607
|
-
errorsPath:
|
|
4608
|
-
staleEventsPath:
|
|
4609
|
-
embeddingsCachePath:
|
|
4610
|
-
policyViolationsPath:
|
|
4930
|
+
snapshotPath: path34.join(baseDir, "graph.json"),
|
|
4931
|
+
errorsPath: path34.join(baseDir, "errors.ndjson"),
|
|
4932
|
+
staleEventsPath: path34.join(baseDir, "stale-events.ndjson"),
|
|
4933
|
+
embeddingsCachePath: path34.join(baseDir, "embeddings.json"),
|
|
4934
|
+
policyViolationsPath: path34.join(baseDir, "policy-violations.ndjson")
|
|
4611
4935
|
};
|
|
4612
4936
|
}
|
|
4613
4937
|
return {
|
|
4614
|
-
snapshotPath:
|
|
4615
|
-
errorsPath:
|
|
4616
|
-
staleEventsPath:
|
|
4617
|
-
embeddingsCachePath:
|
|
4618
|
-
policyViolationsPath:
|
|
4938
|
+
snapshotPath: path34.join(baseDir, `${project}.json`),
|
|
4939
|
+
errorsPath: path34.join(baseDir, `errors.${project}.ndjson`),
|
|
4940
|
+
staleEventsPath: path34.join(baseDir, `stale-events.${project}.ndjson`),
|
|
4941
|
+
embeddingsCachePath: path34.join(baseDir, `embeddings.${project}.json`),
|
|
4942
|
+
policyViolationsPath: path34.join(baseDir, `policy-violations.${project}.ndjson`)
|
|
4619
4943
|
};
|
|
4620
4944
|
}
|
|
4621
4945
|
var Projects = class {
|
|
@@ -4654,9 +4978,9 @@ function parseExtraProjects(raw) {
|
|
|
4654
4978
|
}
|
|
4655
4979
|
|
|
4656
4980
|
// src/registry.ts
|
|
4657
|
-
import { promises as
|
|
4981
|
+
import { promises as fs20 } from "fs";
|
|
4658
4982
|
import os2 from "os";
|
|
4659
|
-
import
|
|
4983
|
+
import path35 from "path";
|
|
4660
4984
|
import {
|
|
4661
4985
|
RegistryFileSchema
|
|
4662
4986
|
} from "@neat.is/types";
|
|
@@ -4664,17 +4988,17 @@ var LOCK_TIMEOUT_MS = 5e3;
|
|
|
4664
4988
|
var LOCK_RETRY_MS = 50;
|
|
4665
4989
|
function neatHome() {
|
|
4666
4990
|
const override = process.env.NEAT_HOME;
|
|
4667
|
-
if (override && override.length > 0) return
|
|
4668
|
-
return
|
|
4991
|
+
if (override && override.length > 0) return path35.resolve(override);
|
|
4992
|
+
return path35.join(os2.homedir(), ".neat");
|
|
4669
4993
|
}
|
|
4670
4994
|
function registryPath() {
|
|
4671
|
-
return
|
|
4995
|
+
return path35.join(neatHome(), "projects.json");
|
|
4672
4996
|
}
|
|
4673
4997
|
function registryLockPath() {
|
|
4674
|
-
return
|
|
4998
|
+
return path35.join(neatHome(), "projects.json.lock");
|
|
4675
4999
|
}
|
|
4676
5000
|
function daemonPidPath() {
|
|
4677
|
-
return
|
|
5001
|
+
return path35.join(neatHome(), "neatd.pid");
|
|
4678
5002
|
}
|
|
4679
5003
|
function isPidAliveDefault(pid) {
|
|
4680
5004
|
try {
|
|
@@ -4686,7 +5010,7 @@ function isPidAliveDefault(pid) {
|
|
|
4686
5010
|
}
|
|
4687
5011
|
async function readPidFile(file) {
|
|
4688
5012
|
try {
|
|
4689
|
-
const raw = await
|
|
5013
|
+
const raw = await fs20.readFile(file, "utf8");
|
|
4690
5014
|
const pid = Number.parseInt(raw.trim(), 10);
|
|
4691
5015
|
return Number.isInteger(pid) && pid > 0 ? pid : void 0;
|
|
4692
5016
|
} catch {
|
|
@@ -4734,32 +5058,32 @@ function lockHolderMessage(holder, lockPath, timeoutMs) {
|
|
|
4734
5058
|
}
|
|
4735
5059
|
}
|
|
4736
5060
|
async function normalizeProjectPath(input) {
|
|
4737
|
-
const resolved =
|
|
5061
|
+
const resolved = path35.resolve(input);
|
|
4738
5062
|
try {
|
|
4739
|
-
return await
|
|
5063
|
+
return await fs20.realpath(resolved);
|
|
4740
5064
|
} catch {
|
|
4741
5065
|
return resolved;
|
|
4742
5066
|
}
|
|
4743
5067
|
}
|
|
4744
5068
|
async function writeAtomically(target, contents) {
|
|
4745
|
-
await
|
|
5069
|
+
await fs20.mkdir(path35.dirname(target), { recursive: true });
|
|
4746
5070
|
const tmp = `${target}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
|
|
4747
|
-
const fd = await
|
|
5071
|
+
const fd = await fs20.open(tmp, "w");
|
|
4748
5072
|
try {
|
|
4749
5073
|
await fd.writeFile(contents, "utf8");
|
|
4750
5074
|
await fd.sync();
|
|
4751
5075
|
} finally {
|
|
4752
5076
|
await fd.close();
|
|
4753
5077
|
}
|
|
4754
|
-
await
|
|
5078
|
+
await fs20.rename(tmp, target);
|
|
4755
5079
|
}
|
|
4756
5080
|
async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaultLockHolderProbe) {
|
|
4757
5081
|
const deadline = Date.now() + timeoutMs;
|
|
4758
|
-
await
|
|
5082
|
+
await fs20.mkdir(path35.dirname(lockPath), { recursive: true });
|
|
4759
5083
|
let probedHolder = false;
|
|
4760
5084
|
while (true) {
|
|
4761
5085
|
try {
|
|
4762
|
-
const fd = await
|
|
5086
|
+
const fd = await fs20.open(lockPath, "wx");
|
|
4763
5087
|
try {
|
|
4764
5088
|
await fd.writeFile(`${process.pid}
|
|
4765
5089
|
`, "utf8");
|
|
@@ -4784,7 +5108,7 @@ async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaul
|
|
|
4784
5108
|
}
|
|
4785
5109
|
}
|
|
4786
5110
|
async function releaseLock(lockPath) {
|
|
4787
|
-
await
|
|
5111
|
+
await fs20.unlink(lockPath).catch(() => {
|
|
4788
5112
|
});
|
|
4789
5113
|
}
|
|
4790
5114
|
async function withLock(fn) {
|
|
@@ -4800,7 +5124,7 @@ async function readRegistry() {
|
|
|
4800
5124
|
const file = registryPath();
|
|
4801
5125
|
let raw;
|
|
4802
5126
|
try {
|
|
4803
|
-
raw = await
|
|
5127
|
+
raw = await fs20.readFile(file, "utf8");
|
|
4804
5128
|
} catch (err) {
|
|
4805
5129
|
if (err.code === "ENOENT") {
|
|
4806
5130
|
return { version: 1, projects: [] };
|
|
@@ -4898,14 +5222,14 @@ import cors from "@fastify/cors";
|
|
|
4898
5222
|
import { DivergenceTypeSchema, PoliciesCheckBodySchema, PolicySeveritySchema } from "@neat.is/types";
|
|
4899
5223
|
|
|
4900
5224
|
// src/extend/index.ts
|
|
4901
|
-
import { promises as
|
|
4902
|
-
import
|
|
5225
|
+
import { promises as fs22 } from "fs";
|
|
5226
|
+
import path37 from "path";
|
|
4903
5227
|
import os3 from "os";
|
|
4904
5228
|
import { resolve as registryResolve, list as registryList } from "@neat.is/instrumentation-registry";
|
|
4905
5229
|
|
|
4906
5230
|
// src/installers/package-manager.ts
|
|
4907
|
-
import { promises as
|
|
4908
|
-
import
|
|
5231
|
+
import { promises as fs21 } from "fs";
|
|
5232
|
+
import path36 from "path";
|
|
4909
5233
|
import { spawn } from "child_process";
|
|
4910
5234
|
var LOCKFILE_PRIORITY = [
|
|
4911
5235
|
{ lockfile: "bun.lockb", pm: "bun", args: ["install", "--no-summary"] },
|
|
@@ -4920,29 +5244,29 @@ var LOCKFILE_PRIORITY = [
|
|
|
4920
5244
|
var NPM_FALLBACK_ARGS = ["install", "--no-audit", "--no-fund", "--prefer-offline"];
|
|
4921
5245
|
async function exists2(p) {
|
|
4922
5246
|
try {
|
|
4923
|
-
await
|
|
5247
|
+
await fs21.access(p);
|
|
4924
5248
|
return true;
|
|
4925
5249
|
} catch {
|
|
4926
5250
|
return false;
|
|
4927
5251
|
}
|
|
4928
5252
|
}
|
|
4929
5253
|
async function detectPackageManager(serviceDir) {
|
|
4930
|
-
let dir =
|
|
5254
|
+
let dir = path36.resolve(serviceDir);
|
|
4931
5255
|
const stops = /* @__PURE__ */ new Set();
|
|
4932
5256
|
for (let i = 0; i < 64; i++) {
|
|
4933
5257
|
if (stops.has(dir)) break;
|
|
4934
5258
|
stops.add(dir);
|
|
4935
5259
|
for (const candidate of LOCKFILE_PRIORITY) {
|
|
4936
|
-
const lockPath =
|
|
5260
|
+
const lockPath = path36.join(dir, candidate.lockfile);
|
|
4937
5261
|
if (await exists2(lockPath)) {
|
|
4938
5262
|
return { pm: candidate.pm, cwd: dir, args: [...candidate.args] };
|
|
4939
5263
|
}
|
|
4940
5264
|
}
|
|
4941
|
-
const parent =
|
|
5265
|
+
const parent = path36.dirname(dir);
|
|
4942
5266
|
if (parent === dir) break;
|
|
4943
5267
|
dir = parent;
|
|
4944
5268
|
}
|
|
4945
|
-
return { pm: "npm", cwd:
|
|
5269
|
+
return { pm: "npm", cwd: path36.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
|
|
4946
5270
|
}
|
|
4947
5271
|
async function runPackageManagerInstall(cmd) {
|
|
4948
5272
|
return new Promise((resolve) => {
|
|
@@ -4982,32 +5306,32 @@ ${err.message}`
|
|
|
4982
5306
|
}
|
|
4983
5307
|
|
|
4984
5308
|
// src/extend/index.ts
|
|
4985
|
-
async function
|
|
5309
|
+
async function fileExists2(p) {
|
|
4986
5310
|
try {
|
|
4987
|
-
await
|
|
5311
|
+
await fs22.access(p);
|
|
4988
5312
|
return true;
|
|
4989
5313
|
} catch {
|
|
4990
5314
|
return false;
|
|
4991
5315
|
}
|
|
4992
5316
|
}
|
|
4993
5317
|
async function readPackageJson(scanPath) {
|
|
4994
|
-
const pkgPath =
|
|
4995
|
-
const raw = await
|
|
5318
|
+
const pkgPath = path37.join(scanPath, "package.json");
|
|
5319
|
+
const raw = await fs22.readFile(pkgPath, "utf8");
|
|
4996
5320
|
return JSON.parse(raw);
|
|
4997
5321
|
}
|
|
4998
5322
|
async function findHookFiles(scanPath) {
|
|
4999
|
-
const entries = await
|
|
5323
|
+
const entries = await fs22.readdir(scanPath);
|
|
5000
5324
|
return entries.filter(
|
|
5001
5325
|
(e) => (e.startsWith("instrumentation") || e.startsWith("otel-init")) && /\.(ts|js)$/.test(e)
|
|
5002
5326
|
).sort();
|
|
5003
5327
|
}
|
|
5004
5328
|
function extendLogPath() {
|
|
5005
|
-
return process.env.NEAT_EXTEND_LOG ??
|
|
5329
|
+
return process.env.NEAT_EXTEND_LOG ?? path37.join(os3.homedir(), ".neat", "extend-log.ndjson");
|
|
5006
5330
|
}
|
|
5007
5331
|
async function appendExtendLog(entry) {
|
|
5008
5332
|
const logPath = extendLogPath();
|
|
5009
|
-
await
|
|
5010
|
-
await
|
|
5333
|
+
await fs22.mkdir(path37.dirname(logPath), { recursive: true });
|
|
5334
|
+
await fs22.appendFile(logPath, JSON.stringify(entry) + "\n", "utf8");
|
|
5011
5335
|
}
|
|
5012
5336
|
function splicedContent(fileContent, snippet2) {
|
|
5013
5337
|
if (fileContent.includes("__INSTRUMENTATION_BLOCK__")) {
|
|
@@ -5065,7 +5389,7 @@ function lookupInstrumentation(library, installedVersion) {
|
|
|
5065
5389
|
}
|
|
5066
5390
|
async function describeProjectInstrumentation(ctx) {
|
|
5067
5391
|
const hookFiles = await findHookFiles(ctx.scanPath);
|
|
5068
|
-
const envNeat = await
|
|
5392
|
+
const envNeat = await fileExists2(path37.join(ctx.scanPath, ".env.neat"));
|
|
5069
5393
|
const registryInstrPackages = new Set(
|
|
5070
5394
|
registryList().map((e) => e.instrumentation_package).filter((p) => !!p)
|
|
5071
5395
|
);
|
|
@@ -5087,31 +5411,31 @@ async function applyExtension(ctx, args, options) {
|
|
|
5087
5411
|
);
|
|
5088
5412
|
}
|
|
5089
5413
|
for (const file of hookFiles) {
|
|
5090
|
-
const content = await
|
|
5414
|
+
const content = await fs22.readFile(path37.join(ctx.scanPath, file), "utf8");
|
|
5091
5415
|
if (content.includes(args.registration_snippet)) {
|
|
5092
5416
|
return { library: args.library, filesTouched: [], depsAdded: [], installOutput: "", alreadyApplied: true };
|
|
5093
5417
|
}
|
|
5094
5418
|
}
|
|
5095
5419
|
const primaryFile = hookFiles[0];
|
|
5096
|
-
const primaryPath =
|
|
5420
|
+
const primaryPath = path37.join(ctx.scanPath, primaryFile);
|
|
5097
5421
|
const filesTouched = [];
|
|
5098
5422
|
const depsAdded = [];
|
|
5099
|
-
const pkgPath =
|
|
5423
|
+
const pkgPath = path37.join(ctx.scanPath, "package.json");
|
|
5100
5424
|
const pkg = await readPackageJson(ctx.scanPath);
|
|
5101
5425
|
if (!(pkg.dependencies ?? {})[args.instrumentation_package]) {
|
|
5102
5426
|
pkg.dependencies = { ...pkg.dependencies ?? {}, [args.instrumentation_package]: args.version };
|
|
5103
|
-
await
|
|
5427
|
+
await fs22.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
|
|
5104
5428
|
filesTouched.push("package.json");
|
|
5105
5429
|
depsAdded.push(`${args.instrumentation_package}@${args.version}`);
|
|
5106
5430
|
}
|
|
5107
|
-
const hookContent = await
|
|
5431
|
+
const hookContent = await fs22.readFile(primaryPath, "utf8");
|
|
5108
5432
|
const patched = splicedContent(hookContent, args.registration_snippet);
|
|
5109
5433
|
if (!patched) {
|
|
5110
5434
|
throw new Error(
|
|
5111
5435
|
`Could not find instrumentation insertion point in ${primaryFile}. Expected __INSTRUMENTATION_BLOCK__, instrumentations.push(, or new NodeSDK(.`
|
|
5112
5436
|
);
|
|
5113
5437
|
}
|
|
5114
|
-
await
|
|
5438
|
+
await fs22.writeFile(primaryPath, patched, "utf8");
|
|
5115
5439
|
filesTouched.push(primaryFile);
|
|
5116
5440
|
const cmd = await detectPackageManager(ctx.scanPath);
|
|
5117
5441
|
const installer = options?.runInstall ?? runPackageManagerInstall;
|
|
@@ -5142,7 +5466,7 @@ async function dryRunExtension(ctx, args) {
|
|
|
5142
5466
|
};
|
|
5143
5467
|
}
|
|
5144
5468
|
for (const file of hookFiles) {
|
|
5145
|
-
const content = await
|
|
5469
|
+
const content = await fs22.readFile(path37.join(ctx.scanPath, file), "utf8");
|
|
5146
5470
|
if (content.includes(args.registration_snippet)) {
|
|
5147
5471
|
return {
|
|
5148
5472
|
library: args.library,
|
|
@@ -5164,7 +5488,7 @@ async function dryRunExtension(ctx, args) {
|
|
|
5164
5488
|
depsToAdd.push(`${args.instrumentation_package}@${args.version}`);
|
|
5165
5489
|
filesTouched.push("package.json");
|
|
5166
5490
|
}
|
|
5167
|
-
const hookContent = await
|
|
5491
|
+
const hookContent = await fs22.readFile(path37.join(ctx.scanPath, primaryFile), "utf8");
|
|
5168
5492
|
const patched = splicedContent(hookContent, args.registration_snippet);
|
|
5169
5493
|
if (patched) {
|
|
5170
5494
|
filesTouched.push(primaryFile);
|
|
@@ -5176,31 +5500,31 @@ async function dryRunExtension(ctx, args) {
|
|
|
5176
5500
|
}
|
|
5177
5501
|
async function rollbackExtension(ctx, args) {
|
|
5178
5502
|
const logPath = extendLogPath();
|
|
5179
|
-
if (!await
|
|
5503
|
+
if (!await fileExists2(logPath)) {
|
|
5180
5504
|
return { undone: false, message: "no apply found for library" };
|
|
5181
5505
|
}
|
|
5182
|
-
const raw = await
|
|
5506
|
+
const raw = await fs22.readFile(logPath, "utf8");
|
|
5183
5507
|
const entries = raw.trim().split("\n").filter(Boolean).map((line) => JSON.parse(line));
|
|
5184
5508
|
const match = [...entries].reverse().find((e) => e.project === ctx.project && e.library === args.library);
|
|
5185
5509
|
if (!match) {
|
|
5186
5510
|
return { undone: false, message: "no apply found for library" };
|
|
5187
5511
|
}
|
|
5188
|
-
const pkgPath =
|
|
5189
|
-
if (await
|
|
5512
|
+
const pkgPath = path37.join(ctx.scanPath, "package.json");
|
|
5513
|
+
if (await fileExists2(pkgPath)) {
|
|
5190
5514
|
const pkg = await readPackageJson(ctx.scanPath);
|
|
5191
5515
|
if (pkg.dependencies?.[match.instrumentation_package]) {
|
|
5192
5516
|
const { [match.instrumentation_package]: _removed, ...rest } = pkg.dependencies;
|
|
5193
5517
|
pkg.dependencies = rest;
|
|
5194
|
-
await
|
|
5518
|
+
await fs22.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
|
|
5195
5519
|
}
|
|
5196
5520
|
}
|
|
5197
5521
|
const hookFiles = await findHookFiles(ctx.scanPath);
|
|
5198
5522
|
for (const file of hookFiles) {
|
|
5199
|
-
const filePath =
|
|
5200
|
-
const content = await
|
|
5523
|
+
const filePath = path37.join(ctx.scanPath, file);
|
|
5524
|
+
const content = await fs22.readFile(filePath, "utf8");
|
|
5201
5525
|
if (content.includes(match.registration_snippet)) {
|
|
5202
5526
|
const filtered = content.split("\n").filter((line) => !line.includes(match.registration_snippet)).join("\n");
|
|
5203
|
-
await
|
|
5527
|
+
await fs22.writeFile(filePath, filtered, "utf8");
|
|
5204
5528
|
break;
|
|
5205
5529
|
}
|
|
5206
5530
|
}
|
|
@@ -5900,6 +6224,7 @@ export {
|
|
|
5900
6224
|
discoverServices,
|
|
5901
6225
|
addServiceNodes,
|
|
5902
6226
|
addServiceAliases,
|
|
6227
|
+
addImports,
|
|
5903
6228
|
addDatabasesAndCompat,
|
|
5904
6229
|
addConfigNodes,
|
|
5905
6230
|
addCallEdges,
|
|
@@ -5931,4 +6256,4 @@ export {
|
|
|
5931
6256
|
removeProject,
|
|
5932
6257
|
buildApi
|
|
5933
6258
|
};
|
|
5934
|
-
//# sourceMappingURL=chunk-
|
|
6259
|
+
//# sourceMappingURL=chunk-XS4CGNRO.js.map
|