@neat.is/core 0.4.26-dev.20260703 → 0.4.27-dev.20260705
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-2LNICOVU.js → chunk-A3322JYS.js} +29 -2
- package/dist/chunk-A3322JYS.js.map +1 -0
- package/dist/{chunk-4OR4RQEO.js → chunk-BIY46Q6U.js} +13 -1
- package/dist/chunk-BIY46Q6U.js.map +1 -0
- package/dist/{chunk-WZYH5DVG.js → chunk-QM6BMPVJ.js} +419 -218
- package/dist/chunk-QM6BMPVJ.js.map +1 -0
- package/dist/{chunk-C5NCCKPZ.js → chunk-UV5WSM7M.js} +2 -2
- package/dist/{chunk-S7TDPQDD.js → chunk-XV4D7A3Z.js} +105 -6
- package/dist/chunk-XV4D7A3Z.js.map +1 -0
- package/dist/cli.cjs +732 -456
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +49 -6
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +587 -273
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +97 -1
- package/dist/index.d.ts +97 -1
- package/dist/index.js +4 -4
- package/dist/neatd.cjs +600 -286
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +3 -3
- package/dist/{otel-grpc-LFYZDSK3.js → otel-grpc-ET5Z6KI6.js} +3 -3
- package/dist/server.cjs +410 -182
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +4 -4
- package/package.json +4 -2
- package/dist/chunk-2LNICOVU.js.map +0 -1
- package/dist/chunk-4OR4RQEO.js.map +0 -1
- package/dist/chunk-S7TDPQDD.js.map +0 -1
- package/dist/chunk-WZYH5DVG.js.map +0 -1
- /package/dist/{chunk-C5NCCKPZ.js.map → chunk-UV5WSM7M.js.map} +0 -0
- /package/dist/{otel-grpc-LFYZDSK3.js.map → otel-grpc-ET5Z6KI6.js.map} +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
mountBearerAuth,
|
|
3
3
|
readAuthEnv
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-A3322JYS.js";
|
|
5
5
|
|
|
6
6
|
// src/graph.ts
|
|
7
7
|
import GraphDefault from "graphology";
|
|
@@ -456,19 +456,19 @@ function confidenceFromMix(edges, now = Date.now()) {
|
|
|
456
456
|
function longestIncomingWalk(graph, start, maxDepth) {
|
|
457
457
|
let best = { path: [start], edges: [] };
|
|
458
458
|
const visited = /* @__PURE__ */ new Set([start]);
|
|
459
|
-
function step(node,
|
|
460
|
-
if (
|
|
461
|
-
best = { path: [...
|
|
459
|
+
function step(node, path42, edges) {
|
|
460
|
+
if (path42.length > best.path.length) {
|
|
461
|
+
best = { path: [...path42], edges: [...edges] };
|
|
462
462
|
}
|
|
463
|
-
if (
|
|
463
|
+
if (path42.length - 1 >= maxDepth) return;
|
|
464
464
|
const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
|
|
465
465
|
for (const [srcId, edge] of incoming) {
|
|
466
466
|
if (visited.has(srcId)) continue;
|
|
467
467
|
visited.add(srcId);
|
|
468
|
-
|
|
468
|
+
path42.push(srcId);
|
|
469
469
|
edges.push(edge);
|
|
470
|
-
step(srcId,
|
|
471
|
-
|
|
470
|
+
step(srcId, path42, edges);
|
|
471
|
+
path42.pop();
|
|
472
472
|
edges.pop();
|
|
473
473
|
visited.delete(srcId);
|
|
474
474
|
}
|
|
@@ -662,26 +662,26 @@ function dominantFailingCall(graph, serviceId4, visited) {
|
|
|
662
662
|
return best;
|
|
663
663
|
}
|
|
664
664
|
function followFailingCallChain(graph, originServiceId, maxDepth) {
|
|
665
|
-
const
|
|
665
|
+
const path42 = [originServiceId];
|
|
666
666
|
const edges = [];
|
|
667
667
|
const visited = /* @__PURE__ */ new Set([originServiceId]);
|
|
668
668
|
let current = originServiceId;
|
|
669
669
|
for (let depth = 0; depth < maxDepth; depth++) {
|
|
670
670
|
const hop = dominantFailingCall(graph, current, visited);
|
|
671
671
|
if (!hop) break;
|
|
672
|
-
|
|
672
|
+
path42.push(hop.nextService);
|
|
673
673
|
edges.push(hop.edge);
|
|
674
674
|
visited.add(hop.nextService);
|
|
675
675
|
current = hop.nextService;
|
|
676
676
|
}
|
|
677
677
|
if (edges.length === 0) return null;
|
|
678
|
-
return { path:
|
|
678
|
+
return { path: path42, edges, culprit: current };
|
|
679
679
|
}
|
|
680
680
|
function crossServiceRootCause(graph, originId, incidents, errorEvent) {
|
|
681
681
|
const chain = followFailingCallChain(graph, originId, ROOT_CAUSE_MAX_DEPTH);
|
|
682
682
|
if (!chain) return null;
|
|
683
683
|
const culprit = chain.culprit;
|
|
684
|
-
const
|
|
684
|
+
const path42 = [...chain.path];
|
|
685
685
|
const edgeProvenances = chain.edges.map((e) => e.provenance);
|
|
686
686
|
const baseConfidence = confidenceFromMix(chain.edges);
|
|
687
687
|
const confidence = Math.max(0, Math.min(1, baseConfidence * INCIDENT_ROOT_CAUSE_CONFIDENCE));
|
|
@@ -689,14 +689,14 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
|
|
|
689
689
|
if (loc) {
|
|
690
690
|
let rootCauseNode = culprit;
|
|
691
691
|
if (loc.fileNode) {
|
|
692
|
-
|
|
692
|
+
path42.push(loc.fileNode);
|
|
693
693
|
edgeProvenances.push(Provenance.OBSERVED);
|
|
694
694
|
rootCauseNode = loc.fileNode;
|
|
695
695
|
}
|
|
696
696
|
return RootCauseResultSchema.parse({
|
|
697
697
|
rootCauseNode,
|
|
698
698
|
rootCauseReason: loc.rootCauseReason,
|
|
699
|
-
traversalPath:
|
|
699
|
+
traversalPath: path42,
|
|
700
700
|
edgeProvenances,
|
|
701
701
|
confidence,
|
|
702
702
|
...loc.fixRecommendation ? { fixRecommendation: loc.fixRecommendation } : {}
|
|
@@ -708,7 +708,7 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
|
|
|
708
708
|
return RootCauseResultSchema.parse({
|
|
709
709
|
rootCauseNode: culprit,
|
|
710
710
|
rootCauseReason: `${culpritName} is failing downstream calls (${errs} observed error${errs === 1 ? "" : "s"})`,
|
|
711
|
-
traversalPath:
|
|
711
|
+
traversalPath: path42,
|
|
712
712
|
edgeProvenances,
|
|
713
713
|
confidence,
|
|
714
714
|
fixRecommendation: `Inspect ${culpritName}'s failing handler`
|
|
@@ -1382,10 +1382,12 @@ import {
|
|
|
1382
1382
|
fileId,
|
|
1383
1383
|
frontierId,
|
|
1384
1384
|
graphqlOperationId,
|
|
1385
|
+
grpcMethodId,
|
|
1385
1386
|
inferredEdgeId,
|
|
1386
1387
|
infraId,
|
|
1387
1388
|
observedEdgeId,
|
|
1388
|
-
serviceId
|
|
1389
|
+
serviceId,
|
|
1390
|
+
websocketChannelId
|
|
1389
1391
|
} from "@neat.is/types";
|
|
1390
1392
|
var HOUR_MS = 60 * 60 * 1e3;
|
|
1391
1393
|
var DAY_MS = 24 * HOUR_MS;
|
|
@@ -1748,6 +1750,40 @@ function ensureGraphqlOperationNode(graph, serviceName, operationType, operation
|
|
|
1748
1750
|
graph.addNode(id, node);
|
|
1749
1751
|
return id;
|
|
1750
1752
|
}
|
|
1753
|
+
function spanServesGrpcMethod(kind) {
|
|
1754
|
+
return kind !== WIRE_SPAN_KIND_CLIENT && kind !== WIRE_SPAN_KIND_PRODUCER && kind !== WIRE_SPAN_KIND_CONSUMER;
|
|
1755
|
+
}
|
|
1756
|
+
function ensureGrpcMethodNode(graph, rpcService, rpcMethod) {
|
|
1757
|
+
const id = grpcMethodId(rpcService, rpcMethod);
|
|
1758
|
+
if (graph.hasNode(id)) return id;
|
|
1759
|
+
const node = {
|
|
1760
|
+
id,
|
|
1761
|
+
type: NodeType3.GrpcMethodNode,
|
|
1762
|
+
name: `${rpcService}/${rpcMethod}`,
|
|
1763
|
+
rpcService,
|
|
1764
|
+
rpcMethod,
|
|
1765
|
+
discoveredVia: "otel"
|
|
1766
|
+
};
|
|
1767
|
+
graph.addNode(id, node);
|
|
1768
|
+
return id;
|
|
1769
|
+
}
|
|
1770
|
+
function spanServesWebsocketChannel(kind) {
|
|
1771
|
+
return kind !== WIRE_SPAN_KIND_CLIENT && kind !== WIRE_SPAN_KIND_PRODUCER && kind !== WIRE_SPAN_KIND_CONSUMER;
|
|
1772
|
+
}
|
|
1773
|
+
function ensureWebsocketChannelNode(graph, serviceName, channel) {
|
|
1774
|
+
const id = websocketChannelId(serviceName, channel);
|
|
1775
|
+
if (graph.hasNode(id)) return id;
|
|
1776
|
+
const node = {
|
|
1777
|
+
id,
|
|
1778
|
+
type: NodeType3.WebSocketChannelNode,
|
|
1779
|
+
name: channel,
|
|
1780
|
+
service: serviceName,
|
|
1781
|
+
channel,
|
|
1782
|
+
discoveredVia: "otel"
|
|
1783
|
+
};
|
|
1784
|
+
graph.addNode(id, node);
|
|
1785
|
+
return id;
|
|
1786
|
+
}
|
|
1751
1787
|
function messagingDestinationKind(system) {
|
|
1752
1788
|
return `${system}-topic`;
|
|
1753
1789
|
}
|
|
@@ -2183,6 +2219,34 @@ async function handleSpan(ctx, span) {
|
|
|
2183
2219
|
callSiteEvidence
|
|
2184
2220
|
);
|
|
2185
2221
|
if (result) affectedNode = targetId;
|
|
2222
|
+
} else if (span.rpcSystem === "grpc" && span.rpcService && span.rpcMethod && spanServesGrpcMethod(span.kind)) {
|
|
2223
|
+
const targetId = ensureGrpcMethodNode(ctx.graph, span.rpcService, span.rpcMethod);
|
|
2224
|
+
const result = upsertObservedEdge(
|
|
2225
|
+
ctx.graph,
|
|
2226
|
+
EdgeType3.CONTAINS,
|
|
2227
|
+
observedSource(),
|
|
2228
|
+
targetId,
|
|
2229
|
+
ts,
|
|
2230
|
+
isError,
|
|
2231
|
+
callSiteEvidence
|
|
2232
|
+
);
|
|
2233
|
+
if (result) affectedNode = targetId;
|
|
2234
|
+
} else if (span.websocketChannel && spanServesWebsocketChannel(span.kind)) {
|
|
2235
|
+
const targetId = ensureWebsocketChannelNode(
|
|
2236
|
+
ctx.graph,
|
|
2237
|
+
span.service,
|
|
2238
|
+
span.websocketChannel
|
|
2239
|
+
);
|
|
2240
|
+
const result = upsertObservedEdge(
|
|
2241
|
+
ctx.graph,
|
|
2242
|
+
EdgeType3.CONNECTS_TO,
|
|
2243
|
+
observedSource(),
|
|
2244
|
+
targetId,
|
|
2245
|
+
ts,
|
|
2246
|
+
isError,
|
|
2247
|
+
callSiteEvidence
|
|
2248
|
+
);
|
|
2249
|
+
if (result) affectedNode = targetId;
|
|
2186
2250
|
} else {
|
|
2187
2251
|
const host = pickAddress(span);
|
|
2188
2252
|
let resolvedViaAddress = false;
|
|
@@ -4774,24 +4838,151 @@ async function addRoutes(graph, services) {
|
|
|
4774
4838
|
return { nodesAdded, edgesAdded };
|
|
4775
4839
|
}
|
|
4776
4840
|
|
|
4841
|
+
// src/extract/proto.ts
|
|
4842
|
+
import { promises as fs15 } from "fs";
|
|
4843
|
+
import path23 from "path";
|
|
4844
|
+
import {
|
|
4845
|
+
EdgeType as EdgeType9,
|
|
4846
|
+
NodeType as NodeType10,
|
|
4847
|
+
Provenance as Provenance8,
|
|
4848
|
+
confidenceForExtracted as confidenceForExtracted6,
|
|
4849
|
+
extractedEdgeId as extractedEdgeId6,
|
|
4850
|
+
grpcMethodId as grpcMethodId2
|
|
4851
|
+
} from "@neat.is/types";
|
|
4852
|
+
var PROTO_EXTENSION = ".proto";
|
|
4853
|
+
function packageOf(content) {
|
|
4854
|
+
const m = content.match(/^\s*package\s+([A-Za-z_][A-Za-z0-9_.]*)\s*;/m);
|
|
4855
|
+
return m ? m[1] : null;
|
|
4856
|
+
}
|
|
4857
|
+
function lineAt(content, offset) {
|
|
4858
|
+
return content.slice(0, offset).split("\n").length;
|
|
4859
|
+
}
|
|
4860
|
+
function grpcMethodsFromProto(content, fqPackage) {
|
|
4861
|
+
const out = [];
|
|
4862
|
+
const serviceRe = /\bservice\s+([A-Za-z_][A-Za-z0-9_]*)\s*\{/g;
|
|
4863
|
+
let sm;
|
|
4864
|
+
while ((sm = serviceRe.exec(content)) !== null) {
|
|
4865
|
+
const serviceName = sm[1];
|
|
4866
|
+
const rpcService = fqPackage ? `${fqPackage}.${serviceName}` : serviceName;
|
|
4867
|
+
const bodyStart = serviceRe.lastIndex;
|
|
4868
|
+
let depth = 1;
|
|
4869
|
+
let i = bodyStart;
|
|
4870
|
+
for (; i < content.length && depth > 0; i++) {
|
|
4871
|
+
const ch = content[i];
|
|
4872
|
+
if (ch === "{") depth++;
|
|
4873
|
+
else if (ch === "}") depth--;
|
|
4874
|
+
}
|
|
4875
|
+
const body = content.slice(bodyStart, i - 1);
|
|
4876
|
+
const rpcRe = /\brpc\s+([A-Za-z_][A-Za-z0-9_]*)\s*\(/g;
|
|
4877
|
+
let rm;
|
|
4878
|
+
while ((rm = rpcRe.exec(body)) !== null) {
|
|
4879
|
+
const rpcMethod = rm[1];
|
|
4880
|
+
const line = lineAt(content, bodyStart + rm.index);
|
|
4881
|
+
out.push({ rpcService, rpcMethod, line });
|
|
4882
|
+
}
|
|
4883
|
+
serviceRe.lastIndex = i;
|
|
4884
|
+
}
|
|
4885
|
+
return out;
|
|
4886
|
+
}
|
|
4887
|
+
async function walkProtoFiles(dir) {
|
|
4888
|
+
const out = [];
|
|
4889
|
+
async function walk3(current) {
|
|
4890
|
+
const entries = await fs15.readdir(current, { withFileTypes: true }).catch(() => []);
|
|
4891
|
+
for (const entry of entries) {
|
|
4892
|
+
const full = path23.join(current, entry.name);
|
|
4893
|
+
if (entry.isDirectory()) {
|
|
4894
|
+
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
4895
|
+
if (await isPythonVenvDir(full)) continue;
|
|
4896
|
+
await walk3(full);
|
|
4897
|
+
} else if (entry.isFile() && path23.extname(entry.name) === PROTO_EXTENSION) {
|
|
4898
|
+
out.push(full);
|
|
4899
|
+
}
|
|
4900
|
+
}
|
|
4901
|
+
}
|
|
4902
|
+
await walk3(dir);
|
|
4903
|
+
return out;
|
|
4904
|
+
}
|
|
4905
|
+
async function addGrpcMethods(graph, services) {
|
|
4906
|
+
let nodesAdded = 0;
|
|
4907
|
+
let edgesAdded = 0;
|
|
4908
|
+
for (const service of services) {
|
|
4909
|
+
const protoPaths = await walkProtoFiles(service.dir);
|
|
4910
|
+
for (const protoPath of protoPaths) {
|
|
4911
|
+
if (isTestPath(protoPath)) continue;
|
|
4912
|
+
const relFile = toPosix2(path23.relative(service.dir, protoPath));
|
|
4913
|
+
let content;
|
|
4914
|
+
try {
|
|
4915
|
+
content = await fs15.readFile(protoPath, "utf8");
|
|
4916
|
+
} catch (err) {
|
|
4917
|
+
recordExtractionError("proto extraction", protoPath, err);
|
|
4918
|
+
continue;
|
|
4919
|
+
}
|
|
4920
|
+
let methods;
|
|
4921
|
+
try {
|
|
4922
|
+
methods = grpcMethodsFromProto(content, packageOf(content));
|
|
4923
|
+
} catch (err) {
|
|
4924
|
+
recordExtractionError("proto extraction", protoPath, err);
|
|
4925
|
+
continue;
|
|
4926
|
+
}
|
|
4927
|
+
if (methods.length === 0) continue;
|
|
4928
|
+
for (const method of methods) {
|
|
4929
|
+
const mid = grpcMethodId2(method.rpcService, method.rpcMethod);
|
|
4930
|
+
if (!graph.hasNode(mid)) {
|
|
4931
|
+
const node = {
|
|
4932
|
+
id: mid,
|
|
4933
|
+
type: NodeType10.GrpcMethodNode,
|
|
4934
|
+
name: `${method.rpcService}/${method.rpcMethod}`,
|
|
4935
|
+
rpcService: method.rpcService,
|
|
4936
|
+
rpcMethod: method.rpcMethod,
|
|
4937
|
+
path: relFile,
|
|
4938
|
+
line: method.line,
|
|
4939
|
+
discoveredVia: "static"
|
|
4940
|
+
};
|
|
4941
|
+
graph.addNode(mid, node);
|
|
4942
|
+
nodesAdded++;
|
|
4943
|
+
}
|
|
4944
|
+
const containsId = extractedEdgeId6(service.node.id, mid, EdgeType9.CONTAINS);
|
|
4945
|
+
if (!graph.hasEdge(containsId)) {
|
|
4946
|
+
const edge = {
|
|
4947
|
+
id: containsId,
|
|
4948
|
+
source: service.node.id,
|
|
4949
|
+
target: mid,
|
|
4950
|
+
type: EdgeType9.CONTAINS,
|
|
4951
|
+
provenance: Provenance8.EXTRACTED,
|
|
4952
|
+
confidence: confidenceForExtracted6("structural"),
|
|
4953
|
+
evidence: {
|
|
4954
|
+
file: relFile,
|
|
4955
|
+
line: method.line,
|
|
4956
|
+
snippet: snippet(content, method.line)
|
|
4957
|
+
}
|
|
4958
|
+
};
|
|
4959
|
+
graph.addEdgeWithKey(containsId, service.node.id, mid, edge);
|
|
4960
|
+
edgesAdded++;
|
|
4961
|
+
}
|
|
4962
|
+
}
|
|
4963
|
+
}
|
|
4964
|
+
}
|
|
4965
|
+
return { nodesAdded, edgesAdded };
|
|
4966
|
+
}
|
|
4967
|
+
|
|
4777
4968
|
// src/extract/calls/index.ts
|
|
4778
4969
|
import {
|
|
4779
|
-
EdgeType as
|
|
4780
|
-
NodeType as
|
|
4781
|
-
Provenance as
|
|
4782
|
-
confidenceForExtracted as
|
|
4970
|
+
EdgeType as EdgeType12,
|
|
4971
|
+
NodeType as NodeType12,
|
|
4972
|
+
Provenance as Provenance11,
|
|
4973
|
+
confidenceForExtracted as confidenceForExtracted9,
|
|
4783
4974
|
passesExtractedFloor as passesExtractedFloor3
|
|
4784
4975
|
} from "@neat.is/types";
|
|
4785
4976
|
|
|
4786
4977
|
// src/extract/calls/http.ts
|
|
4787
|
-
import
|
|
4978
|
+
import path24 from "path";
|
|
4788
4979
|
import Parser3 from "tree-sitter";
|
|
4789
4980
|
import JavaScript3 from "tree-sitter-javascript";
|
|
4790
4981
|
import Python2 from "tree-sitter-python";
|
|
4791
4982
|
import {
|
|
4792
|
-
EdgeType as
|
|
4793
|
-
Provenance as
|
|
4794
|
-
confidenceForExtracted as
|
|
4983
|
+
EdgeType as EdgeType10,
|
|
4984
|
+
Provenance as Provenance9,
|
|
4985
|
+
confidenceForExtracted as confidenceForExtracted7,
|
|
4795
4986
|
passesExtractedFloor
|
|
4796
4987
|
} from "@neat.is/types";
|
|
4797
4988
|
var STRING_LITERAL_NODE_TYPES = /* @__PURE__ */ new Set(["string_fragment", "string_content"]);
|
|
@@ -4863,7 +5054,7 @@ async function addHttpCallEdges(graph, services) {
|
|
|
4863
5054
|
const seen = /* @__PURE__ */ new Set();
|
|
4864
5055
|
for (const file of files) {
|
|
4865
5056
|
if (isTestPath(file.path)) continue;
|
|
4866
|
-
const parser =
|
|
5057
|
+
const parser = path24.extname(file.path) === ".py" ? pyParser : jsParser;
|
|
4867
5058
|
let sites;
|
|
4868
5059
|
try {
|
|
4869
5060
|
sites = callsFromSource(file.content, parser, knownHosts);
|
|
@@ -4872,14 +5063,14 @@ async function addHttpCallEdges(graph, services) {
|
|
|
4872
5063
|
continue;
|
|
4873
5064
|
}
|
|
4874
5065
|
if (sites.length === 0) continue;
|
|
4875
|
-
const relFile = toPosix2(
|
|
5066
|
+
const relFile = toPosix2(path24.relative(service.dir, file.path));
|
|
4876
5067
|
for (const site of sites) {
|
|
4877
5068
|
const targetId = hostToNodeId.get(site.host);
|
|
4878
5069
|
if (!targetId || targetId === service.node.id) continue;
|
|
4879
5070
|
const dedupKey = `${relFile}|${targetId}`;
|
|
4880
5071
|
if (seen.has(dedupKey)) continue;
|
|
4881
5072
|
seen.add(dedupKey);
|
|
4882
|
-
const confidence =
|
|
5073
|
+
const confidence = confidenceForExtracted7("url-literal-service-target");
|
|
4883
5074
|
const ev = {
|
|
4884
5075
|
file: relFile,
|
|
4885
5076
|
line: site.line,
|
|
@@ -4897,21 +5088,21 @@ async function addHttpCallEdges(graph, services) {
|
|
|
4897
5088
|
noteExtractedDropped({
|
|
4898
5089
|
source: fileNodeId,
|
|
4899
5090
|
target: targetId,
|
|
4900
|
-
type:
|
|
5091
|
+
type: EdgeType10.CALLS,
|
|
4901
5092
|
confidence,
|
|
4902
5093
|
confidenceKind: "url-literal-service-target",
|
|
4903
5094
|
evidence: ev
|
|
4904
5095
|
});
|
|
4905
5096
|
continue;
|
|
4906
5097
|
}
|
|
4907
|
-
const edgeId = extractedEdgeId2(fileNodeId, targetId,
|
|
5098
|
+
const edgeId = extractedEdgeId2(fileNodeId, targetId, EdgeType10.CALLS);
|
|
4908
5099
|
if (!graph.hasEdge(edgeId)) {
|
|
4909
5100
|
const edge = {
|
|
4910
5101
|
id: edgeId,
|
|
4911
5102
|
source: fileNodeId,
|
|
4912
5103
|
target: targetId,
|
|
4913
|
-
type:
|
|
4914
|
-
provenance:
|
|
5104
|
+
type: EdgeType10.CALLS,
|
|
5105
|
+
provenance: Provenance9.EXTRACTED,
|
|
4915
5106
|
confidence,
|
|
4916
5107
|
evidence: ev
|
|
4917
5108
|
};
|
|
@@ -4925,14 +5116,14 @@ async function addHttpCallEdges(graph, services) {
|
|
|
4925
5116
|
}
|
|
4926
5117
|
|
|
4927
5118
|
// src/extract/calls/route-match.ts
|
|
4928
|
-
import
|
|
5119
|
+
import path25 from "path";
|
|
4929
5120
|
import Parser4 from "tree-sitter";
|
|
4930
5121
|
import JavaScript4 from "tree-sitter-javascript";
|
|
4931
5122
|
import {
|
|
4932
|
-
EdgeType as
|
|
4933
|
-
NodeType as
|
|
4934
|
-
Provenance as
|
|
4935
|
-
confidenceForExtracted as
|
|
5123
|
+
EdgeType as EdgeType11,
|
|
5124
|
+
NodeType as NodeType11,
|
|
5125
|
+
Provenance as Provenance10,
|
|
5126
|
+
confidenceForExtracted as confidenceForExtracted8,
|
|
4936
5127
|
passesExtractedFloor as passesExtractedFloor2,
|
|
4937
5128
|
serviceId as serviceId3
|
|
4938
5129
|
} from "@neat.is/types";
|
|
@@ -5100,7 +5291,7 @@ function buildRouteIndex(graph) {
|
|
|
5100
5291
|
const index = /* @__PURE__ */ new Map();
|
|
5101
5292
|
graph.forEachNode((_id, attrs) => {
|
|
5102
5293
|
const node = attrs;
|
|
5103
|
-
if (node.type !==
|
|
5294
|
+
if (node.type !== NodeType11.RouteNode) return;
|
|
5104
5295
|
const route = attrs;
|
|
5105
5296
|
const owner = serviceId3(route.service);
|
|
5106
5297
|
const entry = {
|
|
@@ -5131,7 +5322,7 @@ async function addRouteCallEdges(graph, services) {
|
|
|
5131
5322
|
const seen = /* @__PURE__ */ new Set();
|
|
5132
5323
|
for (const file of files) {
|
|
5133
5324
|
if (isTestPath(file.path)) continue;
|
|
5134
|
-
if (!JS_CLIENT_EXTENSIONS.has(
|
|
5325
|
+
if (!JS_CLIENT_EXTENSIONS.has(path25.extname(file.path))) continue;
|
|
5135
5326
|
let sites;
|
|
5136
5327
|
try {
|
|
5137
5328
|
sites = clientCallSitesFromSource(file.content, jsParser, knownHosts);
|
|
@@ -5140,7 +5331,7 @@ async function addRouteCallEdges(graph, services) {
|
|
|
5140
5331
|
continue;
|
|
5141
5332
|
}
|
|
5142
5333
|
if (sites.length === 0) continue;
|
|
5143
|
-
const relFile = toPosix2(
|
|
5334
|
+
const relFile = toPosix2(path25.relative(service.dir, file.path));
|
|
5144
5335
|
for (const site of sites) {
|
|
5145
5336
|
const serverServiceId = hostToNodeId.get(site.host);
|
|
5146
5337
|
if (!serverServiceId || serverServiceId === service.node.id) continue;
|
|
@@ -5160,7 +5351,7 @@ async function addRouteCallEdges(graph, services) {
|
|
|
5160
5351
|
);
|
|
5161
5352
|
nodesAdded += n;
|
|
5162
5353
|
edgesAdded += e;
|
|
5163
|
-
const confidence =
|
|
5354
|
+
const confidence = confidenceForExtracted8("verified-call-site");
|
|
5164
5355
|
const ev = {
|
|
5165
5356
|
file: relFile,
|
|
5166
5357
|
line: site.line,
|
|
@@ -5172,21 +5363,21 @@ async function addRouteCallEdges(graph, services) {
|
|
|
5172
5363
|
noteExtractedDropped({
|
|
5173
5364
|
source: fileNodeId,
|
|
5174
5365
|
target: match.routeNodeId,
|
|
5175
|
-
type:
|
|
5366
|
+
type: EdgeType11.CALLS,
|
|
5176
5367
|
confidence,
|
|
5177
5368
|
confidenceKind: "verified-call-site",
|
|
5178
5369
|
evidence: ev
|
|
5179
5370
|
});
|
|
5180
5371
|
continue;
|
|
5181
5372
|
}
|
|
5182
|
-
const edgeId = extractedEdgeId2(fileNodeId, match.routeNodeId,
|
|
5373
|
+
const edgeId = extractedEdgeId2(fileNodeId, match.routeNodeId, EdgeType11.CALLS);
|
|
5183
5374
|
if (!graph.hasEdge(edgeId)) {
|
|
5184
5375
|
const edge = {
|
|
5185
5376
|
id: edgeId,
|
|
5186
5377
|
source: fileNodeId,
|
|
5187
5378
|
target: match.routeNodeId,
|
|
5188
|
-
type:
|
|
5189
|
-
provenance:
|
|
5379
|
+
type: EdgeType11.CALLS,
|
|
5380
|
+
provenance: Provenance10.EXTRACTED,
|
|
5190
5381
|
confidence,
|
|
5191
5382
|
evidence: ev
|
|
5192
5383
|
};
|
|
@@ -5200,7 +5391,7 @@ async function addRouteCallEdges(graph, services) {
|
|
|
5200
5391
|
}
|
|
5201
5392
|
|
|
5202
5393
|
// src/extract/calls/kafka.ts
|
|
5203
|
-
import
|
|
5394
|
+
import path26 from "path";
|
|
5204
5395
|
import { infraId as infraId2 } from "@neat.is/types";
|
|
5205
5396
|
var PRODUCER_TOPIC_RE = /(?:producer|kafkaProducer)[\s\S]{0,40}?\.send\s*\(\s*\{[\s\S]{0,200}?topic\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
5206
5397
|
var CONSUMER_TOPIC_RE = /(?:consumer|kafkaConsumer)[\s\S]{0,40}?\.(?:subscribe|run)\s*\(\s*\{[\s\S]{0,200}?topic[s]?\s*:\s*(?:\[\s*)?['"`]([^'"`]+)['"`]/g;
|
|
@@ -5231,7 +5422,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
|
|
|
5231
5422
|
// tier (ADR-066).
|
|
5232
5423
|
confidenceKind: "verified-call-site",
|
|
5233
5424
|
evidence: {
|
|
5234
|
-
file:
|
|
5425
|
+
file: path26.relative(serviceDir, file.path),
|
|
5235
5426
|
line,
|
|
5236
5427
|
snippet: snippet(file.content, line)
|
|
5237
5428
|
}
|
|
@@ -5243,7 +5434,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
|
|
|
5243
5434
|
}
|
|
5244
5435
|
|
|
5245
5436
|
// src/extract/calls/redis.ts
|
|
5246
|
-
import
|
|
5437
|
+
import path27 from "path";
|
|
5247
5438
|
import { infraId as infraId3 } from "@neat.is/types";
|
|
5248
5439
|
var REDIS_URL_RE = /redis(?:s)?:\/\/(?:[^@'"`\s]+@)?([^:/'"`\s]+)(?::(\d+))?/g;
|
|
5249
5440
|
function redisEndpointsFromFile(file, serviceDir) {
|
|
@@ -5266,7 +5457,7 @@ function redisEndpointsFromFile(file, serviceDir) {
|
|
|
5266
5457
|
// support tier (ADR-066).
|
|
5267
5458
|
confidenceKind: "url-with-structural-support",
|
|
5268
5459
|
evidence: {
|
|
5269
|
-
file:
|
|
5460
|
+
file: path27.relative(serviceDir, file.path),
|
|
5270
5461
|
line,
|
|
5271
5462
|
snippet: snippet(file.content, line)
|
|
5272
5463
|
}
|
|
@@ -5276,7 +5467,7 @@ function redisEndpointsFromFile(file, serviceDir) {
|
|
|
5276
5467
|
}
|
|
5277
5468
|
|
|
5278
5469
|
// src/extract/calls/aws.ts
|
|
5279
|
-
import
|
|
5470
|
+
import path28 from "path";
|
|
5280
5471
|
import { infraId as infraId4 } from "@neat.is/types";
|
|
5281
5472
|
var S3_BUCKET_RE = /Bucket\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
5282
5473
|
var DYNAMO_TABLE_RE = /TableName\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
@@ -5310,7 +5501,7 @@ function awsEndpointsFromFile(file, serviceDir) {
|
|
|
5310
5501
|
// (ADR-066).
|
|
5311
5502
|
confidenceKind: "verified-call-site",
|
|
5312
5503
|
evidence: {
|
|
5313
|
-
file:
|
|
5504
|
+
file: path28.relative(serviceDir, file.path),
|
|
5314
5505
|
line,
|
|
5315
5506
|
snippet: snippet(file.content, line)
|
|
5316
5507
|
}
|
|
@@ -5334,7 +5525,7 @@ function awsEndpointsFromFile(file, serviceDir) {
|
|
|
5334
5525
|
}
|
|
5335
5526
|
|
|
5336
5527
|
// src/extract/calls/grpc.ts
|
|
5337
|
-
import
|
|
5528
|
+
import path29 from "path";
|
|
5338
5529
|
import { infraId as infraId5 } from "@neat.is/types";
|
|
5339
5530
|
var GRPC_CLIENT_RE = /new\s+([A-Z][A-Za-z0-9_]*)Client\s*\(\s*['"`]?([^,'"`)]+)?/g;
|
|
5340
5531
|
var AWS_SDK_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@aws-sdk\/client-([a-z0-9-]+)['"`]/g;
|
|
@@ -5393,7 +5584,7 @@ function grpcEndpointsFromFile(file, serviceDir) {
|
|
|
5393
5584
|
// tier (ADR-066).
|
|
5394
5585
|
confidenceKind: "verified-call-site",
|
|
5395
5586
|
evidence: {
|
|
5396
|
-
file:
|
|
5587
|
+
file: path29.relative(serviceDir, file.path),
|
|
5397
5588
|
line,
|
|
5398
5589
|
snippet: snippet(file.content, line)
|
|
5399
5590
|
}
|
|
@@ -5403,7 +5594,7 @@ function grpcEndpointsFromFile(file, serviceDir) {
|
|
|
5403
5594
|
}
|
|
5404
5595
|
|
|
5405
5596
|
// src/extract/calls/supabase.ts
|
|
5406
|
-
import
|
|
5597
|
+
import path30 from "path";
|
|
5407
5598
|
import { infraId as infraId6 } from "@neat.is/types";
|
|
5408
5599
|
var SUPABASE_JS_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@supabase\/supabase-js['"`]/;
|
|
5409
5600
|
var SUPABASE_SSR_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@supabase\/ssr['"`]/;
|
|
@@ -5452,7 +5643,7 @@ function supabaseEndpointsFromFile(file, serviceDir) {
|
|
|
5452
5643
|
// tier (ADR-066), the same grade aws.ts / grpc.ts emit at.
|
|
5453
5644
|
confidenceKind: "verified-call-site",
|
|
5454
5645
|
evidence: {
|
|
5455
|
-
file:
|
|
5646
|
+
file: path30.relative(serviceDir, file.path),
|
|
5456
5647
|
line,
|
|
5457
5648
|
snippet: snippet(file.content, line)
|
|
5458
5649
|
}
|
|
@@ -5465,11 +5656,11 @@ function supabaseEndpointsFromFile(file, serviceDir) {
|
|
|
5465
5656
|
function edgeTypeFromEndpoint(ep) {
|
|
5466
5657
|
switch (ep.edgeType) {
|
|
5467
5658
|
case "PUBLISHES_TO":
|
|
5468
|
-
return
|
|
5659
|
+
return EdgeType12.PUBLISHES_TO;
|
|
5469
5660
|
case "CONSUMES_FROM":
|
|
5470
|
-
return
|
|
5661
|
+
return EdgeType12.CONSUMES_FROM;
|
|
5471
5662
|
default:
|
|
5472
|
-
return
|
|
5663
|
+
return EdgeType12.CALLS;
|
|
5473
5664
|
}
|
|
5474
5665
|
}
|
|
5475
5666
|
function isAwsKind(kind) {
|
|
@@ -5497,7 +5688,7 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
5497
5688
|
if (!graph.hasNode(ep.infraId)) {
|
|
5498
5689
|
const node = {
|
|
5499
5690
|
id: ep.infraId,
|
|
5500
|
-
type:
|
|
5691
|
+
type: NodeType12.InfraNode,
|
|
5501
5692
|
name: ep.name,
|
|
5502
5693
|
// #238 — `aws-*` covers AWS-SDK client kinds (aws-s3, aws-dynamodb,
|
|
5503
5694
|
// aws-cognito-identity-provider, …); `s3-` / `dynamodb-` cover the
|
|
@@ -5509,7 +5700,7 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
5509
5700
|
nodesAdded++;
|
|
5510
5701
|
}
|
|
5511
5702
|
const edgeType = edgeTypeFromEndpoint(ep);
|
|
5512
|
-
const confidence =
|
|
5703
|
+
const confidence = confidenceForExtracted9(ep.confidenceKind);
|
|
5513
5704
|
const relFile = toPosix2(ep.evidence.file);
|
|
5514
5705
|
const { fileNodeId, nodesAdded: n, edgesAdded: e } = ensureFileNode(
|
|
5515
5706
|
graph,
|
|
@@ -5539,7 +5730,7 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
5539
5730
|
source: fileNodeId,
|
|
5540
5731
|
target: ep.infraId,
|
|
5541
5732
|
type: edgeType,
|
|
5542
|
-
provenance:
|
|
5733
|
+
provenance: Provenance11.EXTRACTED,
|
|
5543
5734
|
confidence,
|
|
5544
5735
|
evidence: ep.evidence
|
|
5545
5736
|
};
|
|
@@ -5561,15 +5752,15 @@ async function addCallEdges(graph, services) {
|
|
|
5561
5752
|
}
|
|
5562
5753
|
|
|
5563
5754
|
// src/extract/infra/docker-compose.ts
|
|
5564
|
-
import
|
|
5565
|
-
import { EdgeType as
|
|
5755
|
+
import path31 from "path";
|
|
5756
|
+
import { EdgeType as EdgeType13, Provenance as Provenance12, confidenceForExtracted as confidenceForExtracted10 } from "@neat.is/types";
|
|
5566
5757
|
|
|
5567
5758
|
// src/extract/infra/shared.ts
|
|
5568
|
-
import { NodeType as
|
|
5759
|
+
import { NodeType as NodeType13, infraId as infraId7 } from "@neat.is/types";
|
|
5569
5760
|
function makeInfraNode(kind, name, provider = "self", extras) {
|
|
5570
5761
|
return {
|
|
5571
5762
|
id: infraId7(kind, name),
|
|
5572
|
-
type:
|
|
5763
|
+
type: NodeType13.InfraNode,
|
|
5573
5764
|
name,
|
|
5574
5765
|
provider,
|
|
5575
5766
|
kind,
|
|
@@ -5598,7 +5789,7 @@ function dependsOnList(value) {
|
|
|
5598
5789
|
}
|
|
5599
5790
|
function serviceNameToServiceNode(name, services) {
|
|
5600
5791
|
for (const s of services) {
|
|
5601
|
-
if (s.node.name === name ||
|
|
5792
|
+
if (s.node.name === name || path31.basename(s.dir) === name) return s.node.id;
|
|
5602
5793
|
}
|
|
5603
5794
|
return null;
|
|
5604
5795
|
}
|
|
@@ -5607,7 +5798,7 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
5607
5798
|
let edgesAdded = 0;
|
|
5608
5799
|
let composePath = null;
|
|
5609
5800
|
for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
|
|
5610
|
-
const abs =
|
|
5801
|
+
const abs = path31.join(scanPath, name);
|
|
5611
5802
|
if (await exists(abs)) {
|
|
5612
5803
|
composePath = abs;
|
|
5613
5804
|
break;
|
|
@@ -5620,13 +5811,13 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
5620
5811
|
} catch (err) {
|
|
5621
5812
|
recordExtractionError(
|
|
5622
5813
|
"infra docker-compose",
|
|
5623
|
-
|
|
5814
|
+
path31.relative(scanPath, composePath),
|
|
5624
5815
|
err
|
|
5625
5816
|
);
|
|
5626
5817
|
return { nodesAdded, edgesAdded };
|
|
5627
5818
|
}
|
|
5628
5819
|
if (!compose?.services) return { nodesAdded, edgesAdded };
|
|
5629
|
-
const evidenceFile =
|
|
5820
|
+
const evidenceFile = path31.relative(scanPath, composePath).split(path31.sep).join("/");
|
|
5630
5821
|
const composeNameToNodeId = /* @__PURE__ */ new Map();
|
|
5631
5822
|
for (const [composeName, svc] of Object.entries(compose.services)) {
|
|
5632
5823
|
const matchedServiceId = serviceNameToServiceNode(composeName, services);
|
|
@@ -5648,15 +5839,15 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
5648
5839
|
for (const dep of dependsOnList(svc.depends_on)) {
|
|
5649
5840
|
const targetId = composeNameToNodeId.get(dep);
|
|
5650
5841
|
if (!targetId) continue;
|
|
5651
|
-
const edgeId = extractedEdgeId2(sourceId, targetId,
|
|
5842
|
+
const edgeId = extractedEdgeId2(sourceId, targetId, EdgeType13.DEPENDS_ON);
|
|
5652
5843
|
if (graph.hasEdge(edgeId)) continue;
|
|
5653
5844
|
const edge = {
|
|
5654
5845
|
id: edgeId,
|
|
5655
5846
|
source: sourceId,
|
|
5656
5847
|
target: targetId,
|
|
5657
|
-
type:
|
|
5658
|
-
provenance:
|
|
5659
|
-
confidence:
|
|
5848
|
+
type: EdgeType13.DEPENDS_ON,
|
|
5849
|
+
provenance: Provenance12.EXTRACTED,
|
|
5850
|
+
confidence: confidenceForExtracted10("structural"),
|
|
5660
5851
|
evidence: { file: evidenceFile }
|
|
5661
5852
|
};
|
|
5662
5853
|
graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
|
|
@@ -5667,9 +5858,9 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
5667
5858
|
}
|
|
5668
5859
|
|
|
5669
5860
|
// src/extract/infra/dockerfile.ts
|
|
5670
|
-
import
|
|
5671
|
-
import { promises as
|
|
5672
|
-
import { EdgeType as
|
|
5861
|
+
import path32 from "path";
|
|
5862
|
+
import { promises as fs16 } from "fs";
|
|
5863
|
+
import { EdgeType as EdgeType14, Provenance as Provenance13, confidenceForExtracted as confidenceForExtracted11 } from "@neat.is/types";
|
|
5673
5864
|
function readDockerfile(content) {
|
|
5674
5865
|
let image = null;
|
|
5675
5866
|
const ports = [];
|
|
@@ -5698,15 +5889,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
5698
5889
|
let nodesAdded = 0;
|
|
5699
5890
|
let edgesAdded = 0;
|
|
5700
5891
|
for (const service of services) {
|
|
5701
|
-
const dockerfilePath =
|
|
5892
|
+
const dockerfilePath = path32.join(service.dir, "Dockerfile");
|
|
5702
5893
|
if (!await exists(dockerfilePath)) continue;
|
|
5703
5894
|
let content;
|
|
5704
5895
|
try {
|
|
5705
|
-
content = await
|
|
5896
|
+
content = await fs16.readFile(dockerfilePath, "utf8");
|
|
5706
5897
|
} catch (err) {
|
|
5707
5898
|
recordExtractionError(
|
|
5708
5899
|
"infra dockerfile",
|
|
5709
|
-
|
|
5900
|
+
path32.relative(scanPath, dockerfilePath),
|
|
5710
5901
|
err
|
|
5711
5902
|
);
|
|
5712
5903
|
continue;
|
|
@@ -5718,8 +5909,8 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
5718
5909
|
graph.addNode(node.id, node);
|
|
5719
5910
|
nodesAdded++;
|
|
5720
5911
|
}
|
|
5721
|
-
const relDockerfile = toPosix2(
|
|
5722
|
-
const evidenceFile = toPosix2(
|
|
5912
|
+
const relDockerfile = toPosix2(path32.relative(service.dir, dockerfilePath));
|
|
5913
|
+
const evidenceFile = toPosix2(path32.relative(scanPath, dockerfilePath));
|
|
5723
5914
|
const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
|
|
5724
5915
|
graph,
|
|
5725
5916
|
service.pkg.name,
|
|
@@ -5728,15 +5919,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
5728
5919
|
);
|
|
5729
5920
|
nodesAdded += fn;
|
|
5730
5921
|
edgesAdded += fe;
|
|
5731
|
-
const edgeId = extractedEdgeId2(fileNodeId, node.id,
|
|
5922
|
+
const edgeId = extractedEdgeId2(fileNodeId, node.id, EdgeType14.RUNS_ON);
|
|
5732
5923
|
if (!graph.hasEdge(edgeId)) {
|
|
5733
5924
|
const edge = {
|
|
5734
5925
|
id: edgeId,
|
|
5735
5926
|
source: fileNodeId,
|
|
5736
5927
|
target: node.id,
|
|
5737
|
-
type:
|
|
5738
|
-
provenance:
|
|
5739
|
-
confidence:
|
|
5928
|
+
type: EdgeType14.RUNS_ON,
|
|
5929
|
+
provenance: Provenance13.EXTRACTED,
|
|
5930
|
+
confidence: confidenceForExtracted11("structural"),
|
|
5740
5931
|
evidence: {
|
|
5741
5932
|
file: evidenceFile,
|
|
5742
5933
|
...facts.entrypoint ? { snippet: facts.entrypoint.slice(0, 120) } : {}
|
|
@@ -5751,15 +5942,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
5751
5942
|
graph.addNode(portNode.id, portNode);
|
|
5752
5943
|
nodesAdded++;
|
|
5753
5944
|
}
|
|
5754
|
-
const portEdgeId = extractedEdgeId2(fileNodeId, portNode.id,
|
|
5945
|
+
const portEdgeId = extractedEdgeId2(fileNodeId, portNode.id, EdgeType14.CONNECTS_TO);
|
|
5755
5946
|
if (graph.hasEdge(portEdgeId)) continue;
|
|
5756
5947
|
const portEdge = {
|
|
5757
5948
|
id: portEdgeId,
|
|
5758
5949
|
source: fileNodeId,
|
|
5759
5950
|
target: portNode.id,
|
|
5760
|
-
type:
|
|
5761
|
-
provenance:
|
|
5762
|
-
confidence:
|
|
5951
|
+
type: EdgeType14.CONNECTS_TO,
|
|
5952
|
+
provenance: Provenance13.EXTRACTED,
|
|
5953
|
+
confidence: confidenceForExtracted11("structural"),
|
|
5763
5954
|
evidence: { file: evidenceFile, snippet: `EXPOSE ${port}` }
|
|
5764
5955
|
};
|
|
5765
5956
|
graph.addEdgeWithKey(portEdgeId, portEdge.source, portEdge.target, portEdge);
|
|
@@ -5770,23 +5961,23 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
5770
5961
|
}
|
|
5771
5962
|
|
|
5772
5963
|
// src/extract/infra/terraform.ts
|
|
5773
|
-
import { promises as
|
|
5774
|
-
import
|
|
5775
|
-
import { EdgeType as
|
|
5964
|
+
import { promises as fs17 } from "fs";
|
|
5965
|
+
import path33 from "path";
|
|
5966
|
+
import { EdgeType as EdgeType15, Provenance as Provenance14, confidenceForExtracted as confidenceForExtracted12 } from "@neat.is/types";
|
|
5776
5967
|
var RESOURCE_RE = /resource\s+"(aws_[A-Za-z0-9_]+)"\s+"([A-Za-z0-9_-]+)"/g;
|
|
5777
5968
|
var REFERENCE_RE = /(?<![\w.])(aws_[A-Za-z0-9_]+)\.([A-Za-z0-9_-]+)/g;
|
|
5778
5969
|
async function walkTfFiles(start, depth = 0, max = 5) {
|
|
5779
5970
|
if (depth > max) return [];
|
|
5780
5971
|
const out = [];
|
|
5781
|
-
const entries = await
|
|
5972
|
+
const entries = await fs17.readdir(start, { withFileTypes: true }).catch(() => []);
|
|
5782
5973
|
for (const entry of entries) {
|
|
5783
5974
|
if (entry.isDirectory()) {
|
|
5784
5975
|
if (IGNORED_DIRS.has(entry.name) || entry.name === ".terraform") continue;
|
|
5785
|
-
const child =
|
|
5976
|
+
const child = path33.join(start, entry.name);
|
|
5786
5977
|
if (await isPythonVenvDir(child)) continue;
|
|
5787
5978
|
out.push(...await walkTfFiles(child, depth + 1, max));
|
|
5788
5979
|
} else if (entry.isFile() && entry.name.endsWith(".tf")) {
|
|
5789
|
-
out.push(
|
|
5980
|
+
out.push(path33.join(start, entry.name));
|
|
5790
5981
|
}
|
|
5791
5982
|
}
|
|
5792
5983
|
return out;
|
|
@@ -5805,7 +5996,7 @@ function blockBody(content, from) {
|
|
|
5805
5996
|
}
|
|
5806
5997
|
return null;
|
|
5807
5998
|
}
|
|
5808
|
-
function
|
|
5999
|
+
function lineAt2(content, index) {
|
|
5809
6000
|
let line = 1;
|
|
5810
6001
|
for (let i = 0; i < index && i < content.length; i++) {
|
|
5811
6002
|
if (content[i] === "\n") line++;
|
|
@@ -5817,8 +6008,8 @@ async function addTerraformResources(graph, scanPath) {
|
|
|
5817
6008
|
let edgesAdded = 0;
|
|
5818
6009
|
const files = await walkTfFiles(scanPath);
|
|
5819
6010
|
for (const file of files) {
|
|
5820
|
-
const content = await
|
|
5821
|
-
const evidenceFile = toPosix2(
|
|
6011
|
+
const content = await fs17.readFile(file, "utf8");
|
|
6012
|
+
const evidenceFile = toPosix2(path33.relative(scanPath, file));
|
|
5822
6013
|
const resources = [];
|
|
5823
6014
|
const byKey = /* @__PURE__ */ new Map();
|
|
5824
6015
|
RESOURCE_RE.lastIndex = 0;
|
|
@@ -5853,16 +6044,16 @@ async function addTerraformResources(graph, scanPath) {
|
|
|
5853
6044
|
if (!target) continue;
|
|
5854
6045
|
if (seen.has(target.nodeId)) continue;
|
|
5855
6046
|
seen.add(target.nodeId);
|
|
5856
|
-
const edgeId = extractedEdgeId2(resource.nodeId, target.nodeId,
|
|
6047
|
+
const edgeId = extractedEdgeId2(resource.nodeId, target.nodeId, EdgeType15.DEPENDS_ON);
|
|
5857
6048
|
if (graph.hasEdge(edgeId)) continue;
|
|
5858
|
-
const line =
|
|
6049
|
+
const line = lineAt2(content, resource.bodyOffset + ref.index);
|
|
5859
6050
|
const edge = {
|
|
5860
6051
|
id: edgeId,
|
|
5861
6052
|
source: resource.nodeId,
|
|
5862
6053
|
target: target.nodeId,
|
|
5863
|
-
type:
|
|
5864
|
-
provenance:
|
|
5865
|
-
confidence:
|
|
6054
|
+
type: EdgeType15.DEPENDS_ON,
|
|
6055
|
+
provenance: Provenance14.EXTRACTED,
|
|
6056
|
+
confidence: confidenceForExtracted12("structural"),
|
|
5866
6057
|
evidence: { file: evidenceFile, line, snippet: key }
|
|
5867
6058
|
};
|
|
5868
6059
|
graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
|
|
@@ -5874,8 +6065,8 @@ async function addTerraformResources(graph, scanPath) {
|
|
|
5874
6065
|
}
|
|
5875
6066
|
|
|
5876
6067
|
// src/extract/infra/k8s.ts
|
|
5877
|
-
import { promises as
|
|
5878
|
-
import
|
|
6068
|
+
import { promises as fs18 } from "fs";
|
|
6069
|
+
import path34 from "path";
|
|
5879
6070
|
import { parseAllDocuments as parseAllDocuments2 } from "yaml";
|
|
5880
6071
|
var K8S_KIND_TO_INFRA_KIND = {
|
|
5881
6072
|
Service: "k8s-service",
|
|
@@ -5889,15 +6080,15 @@ var K8S_KIND_TO_INFRA_KIND = {
|
|
|
5889
6080
|
async function walkYamlFiles2(start, depth = 0, max = 5) {
|
|
5890
6081
|
if (depth > max) return [];
|
|
5891
6082
|
const out = [];
|
|
5892
|
-
const entries = await
|
|
6083
|
+
const entries = await fs18.readdir(start, { withFileTypes: true }).catch(() => []);
|
|
5893
6084
|
for (const entry of entries) {
|
|
5894
6085
|
if (entry.isDirectory()) {
|
|
5895
6086
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
5896
|
-
const child =
|
|
6087
|
+
const child = path34.join(start, entry.name);
|
|
5897
6088
|
if (await isPythonVenvDir(child)) continue;
|
|
5898
6089
|
out.push(...await walkYamlFiles2(child, depth + 1, max));
|
|
5899
|
-
} else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(
|
|
5900
|
-
out.push(
|
|
6090
|
+
} else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(path34.extname(entry.name))) {
|
|
6091
|
+
out.push(path34.join(start, entry.name));
|
|
5901
6092
|
}
|
|
5902
6093
|
}
|
|
5903
6094
|
return out;
|
|
@@ -5906,7 +6097,7 @@ async function addK8sResources(graph, scanPath) {
|
|
|
5906
6097
|
let nodesAdded = 0;
|
|
5907
6098
|
const files = await walkYamlFiles2(scanPath);
|
|
5908
6099
|
for (const file of files) {
|
|
5909
|
-
const content = await
|
|
6100
|
+
const content = await fs18.readFile(file, "utf8");
|
|
5910
6101
|
let docs;
|
|
5911
6102
|
try {
|
|
5912
6103
|
docs = parseAllDocuments2(content).map((d) => d.toJSON());
|
|
@@ -5941,16 +6132,16 @@ async function addInfra(graph, scanPath, services) {
|
|
|
5941
6132
|
}
|
|
5942
6133
|
|
|
5943
6134
|
// src/extract/index.ts
|
|
5944
|
-
import
|
|
6135
|
+
import path36 from "path";
|
|
5945
6136
|
|
|
5946
6137
|
// src/extract/retire.ts
|
|
5947
6138
|
import { existsSync as existsSync2 } from "fs";
|
|
5948
|
-
import
|
|
5949
|
-
import { NodeType as
|
|
6139
|
+
import path35 from "path";
|
|
6140
|
+
import { NodeType as NodeType14, Provenance as Provenance15 } from "@neat.is/types";
|
|
5950
6141
|
function dropOrphanedFileNodes(graph) {
|
|
5951
6142
|
const orphans = [];
|
|
5952
6143
|
graph.forEachNode((id, attrs) => {
|
|
5953
|
-
if (attrs.type !==
|
|
6144
|
+
if (attrs.type !== NodeType14.FileNode) return;
|
|
5954
6145
|
if (graph.inboundEdges(id).length === 0 && graph.outboundEdges(id).length === 0) {
|
|
5955
6146
|
orphans.push(id);
|
|
5956
6147
|
}
|
|
@@ -5963,7 +6154,7 @@ function retireEdgesByFile(graph, file) {
|
|
|
5963
6154
|
const toDrop = [];
|
|
5964
6155
|
graph.forEachEdge((id, attrs) => {
|
|
5965
6156
|
const edge = attrs;
|
|
5966
|
-
if (edge.provenance !==
|
|
6157
|
+
if (edge.provenance !== Provenance15.EXTRACTED) return;
|
|
5967
6158
|
if (!edge.evidence?.file) return;
|
|
5968
6159
|
if (edge.evidence.file === normalized) toDrop.push(id);
|
|
5969
6160
|
});
|
|
@@ -5976,14 +6167,14 @@ function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = []) {
|
|
|
5976
6167
|
const bases = [scanPath, ...serviceDirs];
|
|
5977
6168
|
graph.forEachEdge((id, attrs) => {
|
|
5978
6169
|
const edge = attrs;
|
|
5979
|
-
if (edge.provenance !==
|
|
6170
|
+
if (edge.provenance !== Provenance15.EXTRACTED) return;
|
|
5980
6171
|
const evidenceFile = edge.evidence?.file;
|
|
5981
6172
|
if (!evidenceFile) return;
|
|
5982
|
-
if (
|
|
6173
|
+
if (path35.isAbsolute(evidenceFile)) {
|
|
5983
6174
|
if (!existsSync2(evidenceFile)) toDrop.push(id);
|
|
5984
6175
|
return;
|
|
5985
6176
|
}
|
|
5986
|
-
const found = bases.some((base) => existsSync2(
|
|
6177
|
+
const found = bases.some((base) => existsSync2(path35.join(base, evidenceFile)));
|
|
5987
6178
|
if (!found) toDrop.push(id);
|
|
5988
6179
|
});
|
|
5989
6180
|
for (const id of toDrop) graph.dropEdge(id);
|
|
@@ -6003,6 +6194,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
6003
6194
|
const phase2 = await addDatabasesAndCompat(graph, services, scanPath);
|
|
6004
6195
|
const phase3 = await addConfigNodes(graph, services, scanPath);
|
|
6005
6196
|
const routePhase = await addRoutes(graph, services);
|
|
6197
|
+
const grpcPhase = await addGrpcMethods(graph, services);
|
|
6006
6198
|
const phase4 = await addCallEdges(graph, services);
|
|
6007
6199
|
const phase5 = await addInfra(graph, scanPath, services);
|
|
6008
6200
|
const ghostsRetired = retireExtractedEdgesByMissingFile(
|
|
@@ -6024,7 +6216,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
6024
6216
|
}
|
|
6025
6217
|
const droppedEntries = drainDroppedExtracted();
|
|
6026
6218
|
if (isRejectedLogEnabled() && opts.errorsPath && droppedEntries.length > 0) {
|
|
6027
|
-
const rejectedPath =
|
|
6219
|
+
const rejectedPath = path36.join(path36.dirname(opts.errorsPath), "rejected.ndjson");
|
|
6028
6220
|
try {
|
|
6029
6221
|
await writeRejectedExtracted(droppedEntries, rejectedPath);
|
|
6030
6222
|
} catch (err) {
|
|
@@ -6034,8 +6226,8 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
6034
6226
|
}
|
|
6035
6227
|
}
|
|
6036
6228
|
const result = {
|
|
6037
|
-
nodesAdded: phase1Nodes + fileEnum.nodesAdded + importGraph.nodesAdded + phase2.nodesAdded + phase3.nodesAdded + routePhase.nodesAdded + phase4.nodesAdded + phase5.nodesAdded,
|
|
6038
|
-
edgesAdded: fileEnum.edgesAdded + importGraph.edgesAdded + phase2.edgesAdded + phase3.edgesAdded + routePhase.edgesAdded + phase4.edgesAdded + phase5.edgesAdded,
|
|
6229
|
+
nodesAdded: phase1Nodes + fileEnum.nodesAdded + importGraph.nodesAdded + phase2.nodesAdded + phase3.nodesAdded + routePhase.nodesAdded + grpcPhase.nodesAdded + phase4.nodesAdded + phase5.nodesAdded,
|
|
6230
|
+
edgesAdded: fileEnum.edgesAdded + importGraph.edgesAdded + phase2.edgesAdded + phase3.edgesAdded + routePhase.edgesAdded + grpcPhase.edgesAdded + phase4.edgesAdded + phase5.edgesAdded,
|
|
6039
6231
|
frontiersPromoted,
|
|
6040
6232
|
extractionErrors: errorEntries.length,
|
|
6041
6233
|
errorEntries,
|
|
@@ -6060,10 +6252,10 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
6060
6252
|
import {
|
|
6061
6253
|
databaseId as databaseId3,
|
|
6062
6254
|
DivergenceResultSchema,
|
|
6063
|
-
EdgeType as
|
|
6064
|
-
NodeType as
|
|
6255
|
+
EdgeType as EdgeType16,
|
|
6256
|
+
NodeType as NodeType15,
|
|
6065
6257
|
parseEdgeId,
|
|
6066
|
-
Provenance as
|
|
6258
|
+
Provenance as Provenance16
|
|
6067
6259
|
} from "@neat.is/types";
|
|
6068
6260
|
function bucketKey(source, target, type) {
|
|
6069
6261
|
return `${type}|${source}|${target}`;
|
|
@@ -6077,17 +6269,17 @@ function bucketEdges(graph) {
|
|
|
6077
6269
|
const key = bucketKey(e.source, e.target, e.type);
|
|
6078
6270
|
const cur = buckets.get(key) ?? { source: e.source, target: e.target, type: e.type };
|
|
6079
6271
|
switch (provenance) {
|
|
6080
|
-
case
|
|
6272
|
+
case Provenance16.EXTRACTED:
|
|
6081
6273
|
cur.extracted = e;
|
|
6082
6274
|
break;
|
|
6083
|
-
case
|
|
6275
|
+
case Provenance16.OBSERVED:
|
|
6084
6276
|
cur.observed = e;
|
|
6085
6277
|
break;
|
|
6086
|
-
case
|
|
6278
|
+
case Provenance16.INFERRED:
|
|
6087
6279
|
cur.inferred = e;
|
|
6088
6280
|
break;
|
|
6089
6281
|
default:
|
|
6090
|
-
if (e.provenance ===
|
|
6282
|
+
if (e.provenance === Provenance16.STALE) cur.stale = e;
|
|
6091
6283
|
}
|
|
6092
6284
|
buckets.set(key, cur);
|
|
6093
6285
|
});
|
|
@@ -6096,7 +6288,12 @@ function bucketEdges(graph) {
|
|
|
6096
6288
|
function nodeIsFrontier(graph, nodeId) {
|
|
6097
6289
|
if (!graph.hasNode(nodeId)) return false;
|
|
6098
6290
|
const attrs = graph.getNodeAttributes(nodeId);
|
|
6099
|
-
return attrs.type ===
|
|
6291
|
+
return attrs.type === NodeType15.FrontierNode;
|
|
6292
|
+
}
|
|
6293
|
+
function nodeIsWebsocketChannel(graph, nodeId) {
|
|
6294
|
+
if (!graph.hasNode(nodeId)) return false;
|
|
6295
|
+
const attrs = graph.getNodeAttributes(nodeId);
|
|
6296
|
+
return attrs.type === NodeType15.WebSocketChannelNode;
|
|
6100
6297
|
}
|
|
6101
6298
|
function clampConfidence(n) {
|
|
6102
6299
|
if (!Number.isFinite(n)) return 0;
|
|
@@ -6116,14 +6313,14 @@ function gradedConfidence(edge) {
|
|
|
6116
6313
|
return clampConfidence(confidenceForEdge(edge));
|
|
6117
6314
|
}
|
|
6118
6315
|
var OBSERVABLE_EDGE_TYPES = /* @__PURE__ */ new Set([
|
|
6119
|
-
|
|
6120
|
-
|
|
6121
|
-
|
|
6122
|
-
|
|
6316
|
+
EdgeType16.CALLS,
|
|
6317
|
+
EdgeType16.CONNECTS_TO,
|
|
6318
|
+
EdgeType16.PUBLISHES_TO,
|
|
6319
|
+
EdgeType16.CONSUMES_FROM
|
|
6123
6320
|
]);
|
|
6124
6321
|
function detectMissingDivergences(graph, bucket) {
|
|
6125
6322
|
const out = [];
|
|
6126
|
-
if (bucket.type ===
|
|
6323
|
+
if (bucket.type === EdgeType16.CONTAINS) return out;
|
|
6127
6324
|
if (bucket.extracted && !bucket.observed && OBSERVABLE_EDGE_TYPES.has(bucket.type)) {
|
|
6128
6325
|
if (!nodeIsFrontier(graph, bucket.target)) {
|
|
6129
6326
|
out.push({
|
|
@@ -6138,7 +6335,7 @@ function detectMissingDivergences(graph, bucket) {
|
|
|
6138
6335
|
});
|
|
6139
6336
|
}
|
|
6140
6337
|
}
|
|
6141
|
-
if (bucket.observed && !bucket.extracted) {
|
|
6338
|
+
if (bucket.observed && !bucket.extracted && !nodeIsWebsocketChannel(graph, bucket.target)) {
|
|
6142
6339
|
out.push({
|
|
6143
6340
|
type: "missing-extracted",
|
|
6144
6341
|
source: bucket.source,
|
|
@@ -6164,7 +6361,7 @@ function declaredHostFor(svc) {
|
|
|
6164
6361
|
function hasExtractedConfiguredBy(graph, svcId) {
|
|
6165
6362
|
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
6166
6363
|
const e = graph.getEdgeAttributes(edgeId);
|
|
6167
|
-
if (e.type ===
|
|
6364
|
+
if (e.type === EdgeType16.CONFIGURED_BY && e.provenance === Provenance16.EXTRACTED) {
|
|
6168
6365
|
return true;
|
|
6169
6366
|
}
|
|
6170
6367
|
}
|
|
@@ -6177,10 +6374,10 @@ function detectHostMismatch(graph, svcId, svc) {
|
|
|
6177
6374
|
const out = [];
|
|
6178
6375
|
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
6179
6376
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
6180
|
-
if (edge.type !==
|
|
6181
|
-
if (edge.provenance !==
|
|
6377
|
+
if (edge.type !== EdgeType16.CONNECTS_TO) continue;
|
|
6378
|
+
if (edge.provenance !== Provenance16.OBSERVED) continue;
|
|
6182
6379
|
const target = graph.getNodeAttributes(edge.target);
|
|
6183
|
-
if (target.type !==
|
|
6380
|
+
if (target.type !== NodeType15.DatabaseNode) continue;
|
|
6184
6381
|
const observedHost = target.host?.trim();
|
|
6185
6382
|
if (!observedHost) continue;
|
|
6186
6383
|
if (observedHost === declaredHost) continue;
|
|
@@ -6202,10 +6399,10 @@ function detectCompatDivergences(graph, svcId, svc) {
|
|
|
6202
6399
|
const deps = svc.dependencies ?? {};
|
|
6203
6400
|
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
6204
6401
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
6205
|
-
if (edge.type !==
|
|
6206
|
-
if (edge.provenance !==
|
|
6402
|
+
if (edge.type !== EdgeType16.CONNECTS_TO) continue;
|
|
6403
|
+
if (edge.provenance !== Provenance16.OBSERVED) continue;
|
|
6207
6404
|
const target = graph.getNodeAttributes(edge.target);
|
|
6208
|
-
if (target.type !==
|
|
6405
|
+
if (target.type !== NodeType15.DatabaseNode) continue;
|
|
6209
6406
|
for (const pair of compatPairs()) {
|
|
6210
6407
|
if (pair.engine !== target.engine) continue;
|
|
6211
6408
|
const declared = deps[pair.driver];
|
|
@@ -6283,7 +6480,7 @@ function computeDivergences(graph, opts = {}) {
|
|
|
6283
6480
|
}
|
|
6284
6481
|
graph.forEachNode((nodeId, attrs) => {
|
|
6285
6482
|
const n = attrs;
|
|
6286
|
-
if (n.type !==
|
|
6483
|
+
if (n.type !== NodeType15.ServiceNode) return;
|
|
6287
6484
|
const svc = n;
|
|
6288
6485
|
for (const d of detectHostMismatch(graph, nodeId, svc)) all.push(d);
|
|
6289
6486
|
for (const d of detectCompatDivergences(graph, nodeId, svc)) all.push(d);
|
|
@@ -6325,9 +6522,9 @@ function computeDivergences(graph, opts = {}) {
|
|
|
6325
6522
|
}
|
|
6326
6523
|
|
|
6327
6524
|
// src/persist.ts
|
|
6328
|
-
import { promises as
|
|
6329
|
-
import
|
|
6330
|
-
import { Provenance as
|
|
6525
|
+
import { promises as fs19 } from "fs";
|
|
6526
|
+
import path37 from "path";
|
|
6527
|
+
import { Provenance as Provenance17, observedEdgeId as observedEdgeId2 } from "@neat.is/types";
|
|
6331
6528
|
var SCHEMA_VERSION = 4;
|
|
6332
6529
|
function migrateV1ToV2(payload) {
|
|
6333
6530
|
const nodes = payload.graph.nodes;
|
|
@@ -6349,7 +6546,7 @@ function migrateV2ToV3(payload) {
|
|
|
6349
6546
|
for (const edge of edges) {
|
|
6350
6547
|
const attrs = edge.attributes;
|
|
6351
6548
|
if (!attrs || attrs.provenance !== "FRONTIER") continue;
|
|
6352
|
-
attrs.provenance =
|
|
6549
|
+
attrs.provenance = Provenance17.OBSERVED;
|
|
6353
6550
|
const type = typeof attrs.type === "string" ? attrs.type : void 0;
|
|
6354
6551
|
const source = typeof attrs.source === "string" ? attrs.source : void 0;
|
|
6355
6552
|
const target = typeof attrs.target === "string" ? attrs.target : void 0;
|
|
@@ -6363,7 +6560,7 @@ function migrateV2ToV3(payload) {
|
|
|
6363
6560
|
return { ...payload, schemaVersion: 3 };
|
|
6364
6561
|
}
|
|
6365
6562
|
async function ensureDir(filePath) {
|
|
6366
|
-
await
|
|
6563
|
+
await fs19.mkdir(path37.dirname(filePath), { recursive: true });
|
|
6367
6564
|
}
|
|
6368
6565
|
async function saveGraphToDisk(graph, outPath) {
|
|
6369
6566
|
await ensureDir(outPath);
|
|
@@ -6373,13 +6570,13 @@ async function saveGraphToDisk(graph, outPath) {
|
|
|
6373
6570
|
graph: graph.export()
|
|
6374
6571
|
};
|
|
6375
6572
|
const tmp = `${outPath}.tmp`;
|
|
6376
|
-
await
|
|
6377
|
-
await
|
|
6573
|
+
await fs19.writeFile(tmp, JSON.stringify(payload), "utf8");
|
|
6574
|
+
await fs19.rename(tmp, outPath);
|
|
6378
6575
|
}
|
|
6379
6576
|
async function loadGraphFromDisk(graph, outPath) {
|
|
6380
6577
|
let raw;
|
|
6381
6578
|
try {
|
|
6382
|
-
raw = await
|
|
6579
|
+
raw = await fs19.readFile(outPath, "utf8");
|
|
6383
6580
|
} catch (err) {
|
|
6384
6581
|
if (err.code === "ENOENT") return;
|
|
6385
6582
|
throw err;
|
|
@@ -6443,7 +6640,7 @@ function startPersistLoop(graph, outPath, opts = {}) {
|
|
|
6443
6640
|
}
|
|
6444
6641
|
|
|
6445
6642
|
// src/diff.ts
|
|
6446
|
-
import { promises as
|
|
6643
|
+
import { promises as fs20 } from "fs";
|
|
6447
6644
|
async function loadSnapshotForDiff(target) {
|
|
6448
6645
|
if (/^https?:\/\//i.test(target)) {
|
|
6449
6646
|
const res = await fetch(target);
|
|
@@ -6452,7 +6649,7 @@ async function loadSnapshotForDiff(target) {
|
|
|
6452
6649
|
}
|
|
6453
6650
|
return await res.json();
|
|
6454
6651
|
}
|
|
6455
|
-
const raw = await
|
|
6652
|
+
const raw = await fs20.readFile(target, "utf8");
|
|
6456
6653
|
return JSON.parse(raw);
|
|
6457
6654
|
}
|
|
6458
6655
|
function indexEntries(entries) {
|
|
@@ -6519,23 +6716,23 @@ function canonicalJson(value) {
|
|
|
6519
6716
|
}
|
|
6520
6717
|
|
|
6521
6718
|
// src/projects.ts
|
|
6522
|
-
import
|
|
6719
|
+
import path38 from "path";
|
|
6523
6720
|
function pathsForProject(project, baseDir) {
|
|
6524
6721
|
if (project === DEFAULT_PROJECT) {
|
|
6525
6722
|
return {
|
|
6526
|
-
snapshotPath:
|
|
6527
|
-
errorsPath:
|
|
6528
|
-
staleEventsPath:
|
|
6529
|
-
embeddingsCachePath:
|
|
6530
|
-
policyViolationsPath:
|
|
6723
|
+
snapshotPath: path38.join(baseDir, "graph.json"),
|
|
6724
|
+
errorsPath: path38.join(baseDir, "errors.ndjson"),
|
|
6725
|
+
staleEventsPath: path38.join(baseDir, "stale-events.ndjson"),
|
|
6726
|
+
embeddingsCachePath: path38.join(baseDir, "embeddings.json"),
|
|
6727
|
+
policyViolationsPath: path38.join(baseDir, "policy-violations.ndjson")
|
|
6531
6728
|
};
|
|
6532
6729
|
}
|
|
6533
6730
|
return {
|
|
6534
|
-
snapshotPath:
|
|
6535
|
-
errorsPath:
|
|
6536
|
-
staleEventsPath:
|
|
6537
|
-
embeddingsCachePath:
|
|
6538
|
-
policyViolationsPath:
|
|
6731
|
+
snapshotPath: path38.join(baseDir, `${project}.json`),
|
|
6732
|
+
errorsPath: path38.join(baseDir, `errors.${project}.ndjson`),
|
|
6733
|
+
staleEventsPath: path38.join(baseDir, `stale-events.${project}.ndjson`),
|
|
6734
|
+
embeddingsCachePath: path38.join(baseDir, `embeddings.${project}.json`),
|
|
6735
|
+
policyViolationsPath: path38.join(baseDir, `policy-violations.${project}.ndjson`)
|
|
6539
6736
|
};
|
|
6540
6737
|
}
|
|
6541
6738
|
var Projects = class {
|
|
@@ -6574,9 +6771,9 @@ function parseExtraProjects(raw) {
|
|
|
6574
6771
|
}
|
|
6575
6772
|
|
|
6576
6773
|
// src/registry.ts
|
|
6577
|
-
import { promises as
|
|
6774
|
+
import { promises as fs21 } from "fs";
|
|
6578
6775
|
import os2 from "os";
|
|
6579
|
-
import
|
|
6776
|
+
import path39 from "path";
|
|
6580
6777
|
import {
|
|
6581
6778
|
RegistryFileSchema
|
|
6582
6779
|
} from "@neat.is/types";
|
|
@@ -6584,20 +6781,20 @@ var LOCK_TIMEOUT_MS = 5e3;
|
|
|
6584
6781
|
var LOCK_RETRY_MS = 50;
|
|
6585
6782
|
function neatHome() {
|
|
6586
6783
|
const override = process.env.NEAT_HOME;
|
|
6587
|
-
if (override && override.length > 0) return
|
|
6588
|
-
return
|
|
6784
|
+
if (override && override.length > 0) return path39.resolve(override);
|
|
6785
|
+
return path39.join(os2.homedir(), ".neat");
|
|
6589
6786
|
}
|
|
6590
6787
|
function registryPath() {
|
|
6591
|
-
return
|
|
6788
|
+
return path39.join(neatHome(), "projects.json");
|
|
6592
6789
|
}
|
|
6593
6790
|
function registryLockPath() {
|
|
6594
|
-
return
|
|
6791
|
+
return path39.join(neatHome(), "projects.json.lock");
|
|
6595
6792
|
}
|
|
6596
6793
|
function daemonPidPath() {
|
|
6597
|
-
return
|
|
6794
|
+
return path39.join(neatHome(), "neatd.pid");
|
|
6598
6795
|
}
|
|
6599
6796
|
function daemonsDir() {
|
|
6600
|
-
return
|
|
6797
|
+
return path39.join(neatHome(), "daemons");
|
|
6601
6798
|
}
|
|
6602
6799
|
function isFiniteInt(v) {
|
|
6603
6800
|
return typeof v === "number" && Number.isFinite(v);
|
|
@@ -6630,7 +6827,7 @@ async function discoverDaemons(probe = defaultDiscoveryProbe) {
|
|
|
6630
6827
|
const dir = daemonsDir();
|
|
6631
6828
|
let names;
|
|
6632
6829
|
try {
|
|
6633
|
-
names = await
|
|
6830
|
+
names = await fs21.readdir(dir);
|
|
6634
6831
|
} catch (err) {
|
|
6635
6832
|
if (err.code === "ENOENT") return [];
|
|
6636
6833
|
throw err;
|
|
@@ -6638,10 +6835,10 @@ async function discoverDaemons(probe = defaultDiscoveryProbe) {
|
|
|
6638
6835
|
const out = [];
|
|
6639
6836
|
for (const name of names) {
|
|
6640
6837
|
if (!name.endsWith(".json")) continue;
|
|
6641
|
-
const file =
|
|
6838
|
+
const file = path39.join(dir, name);
|
|
6642
6839
|
let raw;
|
|
6643
6840
|
try {
|
|
6644
|
-
raw = await
|
|
6841
|
+
raw = await fs21.readFile(file, "utf8");
|
|
6645
6842
|
} catch {
|
|
6646
6843
|
continue;
|
|
6647
6844
|
}
|
|
@@ -6654,7 +6851,7 @@ async function discoverDaemons(probe = defaultDiscoveryProbe) {
|
|
|
6654
6851
|
return out;
|
|
6655
6852
|
}
|
|
6656
6853
|
async function removeDaemonRecord(source) {
|
|
6657
|
-
await
|
|
6854
|
+
await fs21.unlink(source).catch(() => {
|
|
6658
6855
|
});
|
|
6659
6856
|
}
|
|
6660
6857
|
async function listMachineProjects(probe = defaultDiscoveryProbe) {
|
|
@@ -6711,7 +6908,7 @@ function isPidAliveDefault(pid) {
|
|
|
6711
6908
|
}
|
|
6712
6909
|
async function readPidFile(file) {
|
|
6713
6910
|
try {
|
|
6714
|
-
const raw = await
|
|
6911
|
+
const raw = await fs21.readFile(file, "utf8");
|
|
6715
6912
|
const pid = Number.parseInt(raw.trim(), 10);
|
|
6716
6913
|
return Number.isInteger(pid) && pid > 0 ? pid : void 0;
|
|
6717
6914
|
} catch {
|
|
@@ -6759,32 +6956,32 @@ function lockHolderMessage(holder, lockPath, timeoutMs) {
|
|
|
6759
6956
|
}
|
|
6760
6957
|
}
|
|
6761
6958
|
async function normalizeProjectPath(input) {
|
|
6762
|
-
const resolved =
|
|
6959
|
+
const resolved = path39.resolve(input);
|
|
6763
6960
|
try {
|
|
6764
|
-
return await
|
|
6961
|
+
return await fs21.realpath(resolved);
|
|
6765
6962
|
} catch {
|
|
6766
6963
|
return resolved;
|
|
6767
6964
|
}
|
|
6768
6965
|
}
|
|
6769
6966
|
async function writeAtomically(target, contents) {
|
|
6770
|
-
await
|
|
6967
|
+
await fs21.mkdir(path39.dirname(target), { recursive: true });
|
|
6771
6968
|
const tmp = `${target}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
|
|
6772
|
-
const fd = await
|
|
6969
|
+
const fd = await fs21.open(tmp, "w");
|
|
6773
6970
|
try {
|
|
6774
6971
|
await fd.writeFile(contents, "utf8");
|
|
6775
6972
|
await fd.sync();
|
|
6776
6973
|
} finally {
|
|
6777
6974
|
await fd.close();
|
|
6778
6975
|
}
|
|
6779
|
-
await
|
|
6976
|
+
await fs21.rename(tmp, target);
|
|
6780
6977
|
}
|
|
6781
6978
|
async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaultLockHolderProbe) {
|
|
6782
6979
|
const deadline = Date.now() + timeoutMs;
|
|
6783
|
-
await
|
|
6980
|
+
await fs21.mkdir(path39.dirname(lockPath), { recursive: true });
|
|
6784
6981
|
let probedHolder = false;
|
|
6785
6982
|
while (true) {
|
|
6786
6983
|
try {
|
|
6787
|
-
const fd = await
|
|
6984
|
+
const fd = await fs21.open(lockPath, "wx");
|
|
6788
6985
|
try {
|
|
6789
6986
|
await fd.writeFile(`${process.pid}
|
|
6790
6987
|
`, "utf8");
|
|
@@ -6809,7 +7006,7 @@ async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaul
|
|
|
6809
7006
|
}
|
|
6810
7007
|
}
|
|
6811
7008
|
async function releaseLock(lockPath) {
|
|
6812
|
-
await
|
|
7009
|
+
await fs21.unlink(lockPath).catch(() => {
|
|
6813
7010
|
});
|
|
6814
7011
|
}
|
|
6815
7012
|
async function withLock(fn) {
|
|
@@ -6825,7 +7022,7 @@ async function readRegistry() {
|
|
|
6825
7022
|
const file = registryPath();
|
|
6826
7023
|
let raw;
|
|
6827
7024
|
try {
|
|
6828
|
-
raw = await
|
|
7025
|
+
raw = await fs21.readFile(file, "utf8");
|
|
6829
7026
|
} catch (err) {
|
|
6830
7027
|
if (err.code === "ENOENT") {
|
|
6831
7028
|
return { version: 1, projects: [] };
|
|
@@ -6930,7 +7127,7 @@ function pruneTtlMs() {
|
|
|
6930
7127
|
}
|
|
6931
7128
|
async function statPathStatus(p) {
|
|
6932
7129
|
try {
|
|
6933
|
-
const stat = await
|
|
7130
|
+
const stat = await fs21.stat(p);
|
|
6934
7131
|
return stat.isDirectory() ? "present" : "unknown";
|
|
6935
7132
|
} catch (err) {
|
|
6936
7133
|
return err.code === "ENOENT" ? "gone" : "unknown";
|
|
@@ -6975,14 +7172,14 @@ import cors from "@fastify/cors";
|
|
|
6975
7172
|
import { DivergenceTypeSchema, PoliciesCheckBodySchema, PolicySeveritySchema } from "@neat.is/types";
|
|
6976
7173
|
|
|
6977
7174
|
// src/extend/index.ts
|
|
6978
|
-
import { promises as
|
|
6979
|
-
import
|
|
7175
|
+
import { promises as fs23 } from "fs";
|
|
7176
|
+
import path41 from "path";
|
|
6980
7177
|
import os3 from "os";
|
|
6981
7178
|
import { resolve as registryResolve, list as registryList } from "@neat.is/instrumentation-registry";
|
|
6982
7179
|
|
|
6983
7180
|
// src/installers/package-manager.ts
|
|
6984
|
-
import { promises as
|
|
6985
|
-
import
|
|
7181
|
+
import { promises as fs22 } from "fs";
|
|
7182
|
+
import path40 from "path";
|
|
6986
7183
|
import { spawn } from "child_process";
|
|
6987
7184
|
var LOCKFILE_PRIORITY = [
|
|
6988
7185
|
{ lockfile: "bun.lockb", pm: "bun", args: ["install", "--no-summary"] },
|
|
@@ -6997,29 +7194,29 @@ var LOCKFILE_PRIORITY = [
|
|
|
6997
7194
|
var NPM_FALLBACK_ARGS = ["install", "--no-audit", "--no-fund", "--prefer-offline"];
|
|
6998
7195
|
async function exists2(p) {
|
|
6999
7196
|
try {
|
|
7000
|
-
await
|
|
7197
|
+
await fs22.access(p);
|
|
7001
7198
|
return true;
|
|
7002
7199
|
} catch {
|
|
7003
7200
|
return false;
|
|
7004
7201
|
}
|
|
7005
7202
|
}
|
|
7006
7203
|
async function detectPackageManager(serviceDir) {
|
|
7007
|
-
let dir =
|
|
7204
|
+
let dir = path40.resolve(serviceDir);
|
|
7008
7205
|
const stops = /* @__PURE__ */ new Set();
|
|
7009
7206
|
for (let i = 0; i < 64; i++) {
|
|
7010
7207
|
if (stops.has(dir)) break;
|
|
7011
7208
|
stops.add(dir);
|
|
7012
7209
|
for (const candidate of LOCKFILE_PRIORITY) {
|
|
7013
|
-
const lockPath =
|
|
7210
|
+
const lockPath = path40.join(dir, candidate.lockfile);
|
|
7014
7211
|
if (await exists2(lockPath)) {
|
|
7015
7212
|
return { pm: candidate.pm, cwd: dir, args: [...candidate.args] };
|
|
7016
7213
|
}
|
|
7017
7214
|
}
|
|
7018
|
-
const parent =
|
|
7215
|
+
const parent = path40.dirname(dir);
|
|
7019
7216
|
if (parent === dir) break;
|
|
7020
7217
|
dir = parent;
|
|
7021
7218
|
}
|
|
7022
|
-
return { pm: "npm", cwd:
|
|
7219
|
+
return { pm: "npm", cwd: path40.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
|
|
7023
7220
|
}
|
|
7024
7221
|
async function runPackageManagerInstall(cmd) {
|
|
7025
7222
|
return new Promise((resolve) => {
|
|
@@ -7061,30 +7258,30 @@ ${err.message}`
|
|
|
7061
7258
|
// src/extend/index.ts
|
|
7062
7259
|
async function fileExists2(p) {
|
|
7063
7260
|
try {
|
|
7064
|
-
await
|
|
7261
|
+
await fs23.access(p);
|
|
7065
7262
|
return true;
|
|
7066
7263
|
} catch {
|
|
7067
7264
|
return false;
|
|
7068
7265
|
}
|
|
7069
7266
|
}
|
|
7070
7267
|
async function readPackageJson(scanPath) {
|
|
7071
|
-
const pkgPath =
|
|
7072
|
-
const raw = await
|
|
7268
|
+
const pkgPath = path41.join(scanPath, "package.json");
|
|
7269
|
+
const raw = await fs23.readFile(pkgPath, "utf8");
|
|
7073
7270
|
return JSON.parse(raw);
|
|
7074
7271
|
}
|
|
7075
7272
|
async function findHookFiles(scanPath) {
|
|
7076
|
-
const entries = await
|
|
7273
|
+
const entries = await fs23.readdir(scanPath);
|
|
7077
7274
|
return entries.filter(
|
|
7078
7275
|
(e) => (e.startsWith("instrumentation") || e.startsWith("otel-init")) && /\.(ts|js)$/.test(e)
|
|
7079
7276
|
).sort();
|
|
7080
7277
|
}
|
|
7081
7278
|
function extendLogPath() {
|
|
7082
|
-
return process.env.NEAT_EXTEND_LOG ??
|
|
7279
|
+
return process.env.NEAT_EXTEND_LOG ?? path41.join(os3.homedir(), ".neat", "extend-log.ndjson");
|
|
7083
7280
|
}
|
|
7084
7281
|
async function appendExtendLog(entry) {
|
|
7085
7282
|
const logPath = extendLogPath();
|
|
7086
|
-
await
|
|
7087
|
-
await
|
|
7283
|
+
await fs23.mkdir(path41.dirname(logPath), { recursive: true });
|
|
7284
|
+
await fs23.appendFile(logPath, JSON.stringify(entry) + "\n", "utf8");
|
|
7088
7285
|
}
|
|
7089
7286
|
function splicedContent(fileContent, snippet2) {
|
|
7090
7287
|
if (fileContent.includes("__INSTRUMENTATION_BLOCK__")) {
|
|
@@ -7142,7 +7339,7 @@ function lookupInstrumentation(library, installedVersion) {
|
|
|
7142
7339
|
}
|
|
7143
7340
|
async function describeProjectInstrumentation(ctx) {
|
|
7144
7341
|
const hookFiles = await findHookFiles(ctx.scanPath);
|
|
7145
|
-
const envNeat = await fileExists2(
|
|
7342
|
+
const envNeat = await fileExists2(path41.join(ctx.scanPath, ".env.neat"));
|
|
7146
7343
|
const registryInstrPackages = new Set(
|
|
7147
7344
|
registryList().map((e) => e.instrumentation_package).filter((p) => !!p)
|
|
7148
7345
|
);
|
|
@@ -7164,31 +7361,31 @@ async function applyExtension(ctx, args, options) {
|
|
|
7164
7361
|
);
|
|
7165
7362
|
}
|
|
7166
7363
|
for (const file of hookFiles) {
|
|
7167
|
-
const content = await
|
|
7364
|
+
const content = await fs23.readFile(path41.join(ctx.scanPath, file), "utf8");
|
|
7168
7365
|
if (content.includes(args.registration_snippet)) {
|
|
7169
7366
|
return { library: args.library, filesTouched: [], depsAdded: [], installOutput: "", alreadyApplied: true };
|
|
7170
7367
|
}
|
|
7171
7368
|
}
|
|
7172
7369
|
const primaryFile = hookFiles[0];
|
|
7173
|
-
const primaryPath =
|
|
7370
|
+
const primaryPath = path41.join(ctx.scanPath, primaryFile);
|
|
7174
7371
|
const filesTouched = [];
|
|
7175
7372
|
const depsAdded = [];
|
|
7176
|
-
const pkgPath =
|
|
7373
|
+
const pkgPath = path41.join(ctx.scanPath, "package.json");
|
|
7177
7374
|
const pkg = await readPackageJson(ctx.scanPath);
|
|
7178
7375
|
if (!(pkg.dependencies ?? {})[args.instrumentation_package]) {
|
|
7179
7376
|
pkg.dependencies = { ...pkg.dependencies ?? {}, [args.instrumentation_package]: args.version };
|
|
7180
|
-
await
|
|
7377
|
+
await fs23.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
|
|
7181
7378
|
filesTouched.push("package.json");
|
|
7182
7379
|
depsAdded.push(`${args.instrumentation_package}@${args.version}`);
|
|
7183
7380
|
}
|
|
7184
|
-
const hookContent = await
|
|
7381
|
+
const hookContent = await fs23.readFile(primaryPath, "utf8");
|
|
7185
7382
|
const patched = splicedContent(hookContent, args.registration_snippet);
|
|
7186
7383
|
if (!patched) {
|
|
7187
7384
|
throw new Error(
|
|
7188
7385
|
`Could not find instrumentation insertion point in ${primaryFile}. Expected __INSTRUMENTATION_BLOCK__, instrumentations.push(, or new NodeSDK(.`
|
|
7189
7386
|
);
|
|
7190
7387
|
}
|
|
7191
|
-
await
|
|
7388
|
+
await fs23.writeFile(primaryPath, patched, "utf8");
|
|
7192
7389
|
filesTouched.push(primaryFile);
|
|
7193
7390
|
const cmd = await detectPackageManager(ctx.scanPath);
|
|
7194
7391
|
const installer = options?.runInstall ?? runPackageManagerInstall;
|
|
@@ -7219,7 +7416,7 @@ async function dryRunExtension(ctx, args) {
|
|
|
7219
7416
|
};
|
|
7220
7417
|
}
|
|
7221
7418
|
for (const file of hookFiles) {
|
|
7222
|
-
const content = await
|
|
7419
|
+
const content = await fs23.readFile(path41.join(ctx.scanPath, file), "utf8");
|
|
7223
7420
|
if (content.includes(args.registration_snippet)) {
|
|
7224
7421
|
return {
|
|
7225
7422
|
library: args.library,
|
|
@@ -7241,7 +7438,7 @@ async function dryRunExtension(ctx, args) {
|
|
|
7241
7438
|
depsToAdd.push(`${args.instrumentation_package}@${args.version}`);
|
|
7242
7439
|
filesTouched.push("package.json");
|
|
7243
7440
|
}
|
|
7244
|
-
const hookContent = await
|
|
7441
|
+
const hookContent = await fs23.readFile(path41.join(ctx.scanPath, primaryFile), "utf8");
|
|
7245
7442
|
const patched = splicedContent(hookContent, args.registration_snippet);
|
|
7246
7443
|
if (patched) {
|
|
7247
7444
|
filesTouched.push(primaryFile);
|
|
@@ -7256,28 +7453,28 @@ async function rollbackExtension(ctx, args) {
|
|
|
7256
7453
|
if (!await fileExists2(logPath)) {
|
|
7257
7454
|
return { undone: false, message: "no apply found for library" };
|
|
7258
7455
|
}
|
|
7259
|
-
const raw = await
|
|
7456
|
+
const raw = await fs23.readFile(logPath, "utf8");
|
|
7260
7457
|
const entries = raw.trim().split("\n").filter(Boolean).map((line) => JSON.parse(line));
|
|
7261
7458
|
const match = [...entries].reverse().find((e) => e.project === ctx.project && e.library === args.library);
|
|
7262
7459
|
if (!match) {
|
|
7263
7460
|
return { undone: false, message: "no apply found for library" };
|
|
7264
7461
|
}
|
|
7265
|
-
const pkgPath =
|
|
7462
|
+
const pkgPath = path41.join(ctx.scanPath, "package.json");
|
|
7266
7463
|
if (await fileExists2(pkgPath)) {
|
|
7267
7464
|
const pkg = await readPackageJson(ctx.scanPath);
|
|
7268
7465
|
if (pkg.dependencies?.[match.instrumentation_package]) {
|
|
7269
7466
|
const { [match.instrumentation_package]: _removed, ...rest } = pkg.dependencies;
|
|
7270
7467
|
pkg.dependencies = rest;
|
|
7271
|
-
await
|
|
7468
|
+
await fs23.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
|
|
7272
7469
|
}
|
|
7273
7470
|
}
|
|
7274
7471
|
const hookFiles = await findHookFiles(ctx.scanPath);
|
|
7275
7472
|
for (const file of hookFiles) {
|
|
7276
|
-
const filePath =
|
|
7277
|
-
const content = await
|
|
7473
|
+
const filePath = path41.join(ctx.scanPath, file);
|
|
7474
|
+
const content = await fs23.readFile(filePath, "utf8");
|
|
7278
7475
|
if (content.includes(match.registration_snippet)) {
|
|
7279
7476
|
const filtered = content.split("\n").filter((line) => !line.includes(match.registration_snippet)).join("\n");
|
|
7280
|
-
await
|
|
7477
|
+
await fs23.writeFile(filePath, filtered, "utf8");
|
|
7281
7478
|
break;
|
|
7282
7479
|
}
|
|
7283
7480
|
}
|
|
@@ -8027,6 +8224,10 @@ export {
|
|
|
8027
8224
|
loadPolicyFile,
|
|
8028
8225
|
PolicyViolationsLog,
|
|
8029
8226
|
thresholdForEdgeType,
|
|
8227
|
+
reconcileObservedRelPath,
|
|
8228
|
+
ensureObservedFileNode,
|
|
8229
|
+
ensureServiceNode,
|
|
8230
|
+
upsertObservedEdge,
|
|
8030
8231
|
stitchTrace,
|
|
8031
8232
|
makeErrorSpanWriter,
|
|
8032
8233
|
handleSpan,
|
|
@@ -8081,4 +8282,4 @@ export {
|
|
|
8081
8282
|
pruneRegistry,
|
|
8082
8283
|
buildApi
|
|
8083
8284
|
};
|
|
8084
|
-
//# sourceMappingURL=chunk-
|
|
8285
|
+
//# sourceMappingURL=chunk-QM6BMPVJ.js.map
|