@neat.is/core 0.4.6 → 0.4.8
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-RBWL4HRB.js → chunk-J5CEKCTR.js} +337 -133
- package/dist/chunk-J5CEKCTR.js.map +1 -0
- package/dist/{chunk-NON5AXOR.js → chunk-RC3CIDZO.js} +2 -2
- package/dist/cli.cjs +501 -164
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.d.cts +2 -1
- package/dist/cli.d.ts +2 -1
- package/dist/cli.js +125 -19
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +324 -131
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +2 -2
- package/dist/neatd.cjs +324 -131
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +2 -2
- package/dist/server.cjs +307 -114
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +1 -1
- package/package.json +2 -2
- package/dist/chunk-RBWL4HRB.js.map +0 -1
- /package/dist/{chunk-NON5AXOR.js.map → chunk-RC3CIDZO.js.map} +0 -0
package/dist/server.cjs
CHANGED
|
@@ -610,7 +610,7 @@ function getGraph(project = DEFAULT_PROJECT) {
|
|
|
610
610
|
init_cjs_shims();
|
|
611
611
|
var import_fastify = __toESM(require("fastify"), 1);
|
|
612
612
|
var import_cors = __toESM(require("@fastify/cors"), 1);
|
|
613
|
-
var
|
|
613
|
+
var import_types23 = require("@neat.is/types");
|
|
614
614
|
|
|
615
615
|
// src/divergences.ts
|
|
616
616
|
init_cjs_shims();
|
|
@@ -939,6 +939,24 @@ function isFrontierNode(graph, nodeId) {
|
|
|
939
939
|
const attrs = graph.getNodeAttributes(nodeId);
|
|
940
940
|
return attrs.type === import_types.NodeType.FrontierNode;
|
|
941
941
|
}
|
|
942
|
+
function resolveOwningService(graph, nodeId) {
|
|
943
|
+
if (!graph.hasNode(nodeId)) return null;
|
|
944
|
+
const attrs = graph.getNodeAttributes(nodeId);
|
|
945
|
+
if (attrs.type === import_types.NodeType.ServiceNode) {
|
|
946
|
+
return { id: nodeId, svc: attrs };
|
|
947
|
+
}
|
|
948
|
+
if (attrs.type === import_types.NodeType.FileNode) {
|
|
949
|
+
for (const edgeId of graph.inboundEdges(nodeId)) {
|
|
950
|
+
const e = graph.getEdgeAttributes(edgeId);
|
|
951
|
+
if (e.type !== import_types.EdgeType.CONTAINS) continue;
|
|
952
|
+
const owner = graph.getNodeAttributes(e.source);
|
|
953
|
+
if (owner.type === import_types.NodeType.ServiceNode) {
|
|
954
|
+
return { id: e.source, svc: owner };
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
return null;
|
|
959
|
+
}
|
|
942
960
|
function bestEdgeBySource(graph, edgeIds) {
|
|
943
961
|
const best = /* @__PURE__ */ new Map();
|
|
944
962
|
for (const id of edgeIds) {
|
|
@@ -1045,9 +1063,9 @@ function databaseRootCauseShape(graph, origin, walk) {
|
|
|
1045
1063
|
const candidatePairs = compatPairs().filter((p) => p.engine === targetDb.engine);
|
|
1046
1064
|
if (candidatePairs.length === 0) return null;
|
|
1047
1065
|
for (const id of walk.path) {
|
|
1048
|
-
const
|
|
1049
|
-
if (
|
|
1050
|
-
const svc =
|
|
1066
|
+
const owner = resolveOwningService(graph, id);
|
|
1067
|
+
if (!owner) continue;
|
|
1068
|
+
const { id: serviceId3, svc } = owner;
|
|
1051
1069
|
const deps = svc.dependencies ?? {};
|
|
1052
1070
|
for (const pair of candidatePairs) {
|
|
1053
1071
|
const declared = deps[pair.driver];
|
|
@@ -1060,7 +1078,7 @@ function databaseRootCauseShape(graph, origin, walk) {
|
|
|
1060
1078
|
);
|
|
1061
1079
|
if (!result.compatible) {
|
|
1062
1080
|
return {
|
|
1063
|
-
rootCauseNode:
|
|
1081
|
+
rootCauseNode: serviceId3,
|
|
1064
1082
|
rootCauseReason: result.reason ?? "incompatible driver",
|
|
1065
1083
|
...result.minDriverVersion ? {
|
|
1066
1084
|
fixRecommendation: `Upgrade ${svc.name} ${pair.driver} driver to >= ${result.minDriverVersion}`
|
|
@@ -1073,9 +1091,9 @@ function databaseRootCauseShape(graph, origin, walk) {
|
|
|
1073
1091
|
}
|
|
1074
1092
|
function serviceRootCauseShape(graph, _origin, walk) {
|
|
1075
1093
|
for (const id of walk.path) {
|
|
1076
|
-
const
|
|
1077
|
-
if (
|
|
1078
|
-
const svc =
|
|
1094
|
+
const owner = resolveOwningService(graph, id);
|
|
1095
|
+
if (!owner) continue;
|
|
1096
|
+
const { id: serviceId3, svc } = owner;
|
|
1079
1097
|
const deps = svc.dependencies ?? {};
|
|
1080
1098
|
const serviceNodeEngine = svc.nodeEngine;
|
|
1081
1099
|
for (const constraint of nodeEngineConstraints()) {
|
|
@@ -1084,7 +1102,7 @@ function serviceRootCauseShape(graph, _origin, walk) {
|
|
|
1084
1102
|
const result = checkNodeEngineConstraint(constraint, declared, serviceNodeEngine);
|
|
1085
1103
|
if (!result.compatible && result.reason) {
|
|
1086
1104
|
return {
|
|
1087
|
-
rootCauseNode:
|
|
1105
|
+
rootCauseNode: serviceId3,
|
|
1088
1106
|
rootCauseReason: result.reason,
|
|
1089
1107
|
...result.requiredNodeVersion ? {
|
|
1090
1108
|
fixRecommendation: `Bump ${svc.name}'s engines.node to >= ${result.requiredNodeVersion}`
|
|
@@ -1099,7 +1117,7 @@ function serviceRootCauseShape(graph, _origin, walk) {
|
|
|
1099
1117
|
const result = checkPackageConflict(conflict, declared, requiredDeclared);
|
|
1100
1118
|
if (!result.compatible && result.reason) {
|
|
1101
1119
|
return {
|
|
1102
|
-
rootCauseNode:
|
|
1120
|
+
rootCauseNode: serviceId3,
|
|
1103
1121
|
rootCauseReason: result.reason,
|
|
1104
1122
|
fixRecommendation: `Upgrade ${svc.name}'s ${conflict.requires.name} to >= ${conflict.requires.minVersion}`
|
|
1105
1123
|
};
|
|
@@ -1108,9 +1126,15 @@ function serviceRootCauseShape(graph, _origin, walk) {
|
|
|
1108
1126
|
}
|
|
1109
1127
|
return null;
|
|
1110
1128
|
}
|
|
1129
|
+
function fileRootCauseShape(graph, origin, walk) {
|
|
1130
|
+
const owner = resolveOwningService(graph, origin.id);
|
|
1131
|
+
if (!owner) return null;
|
|
1132
|
+
return serviceRootCauseShape(graph, owner.svc, walk);
|
|
1133
|
+
}
|
|
1111
1134
|
var rootCauseShapes = {
|
|
1112
1135
|
[import_types.NodeType.DatabaseNode]: databaseRootCauseShape,
|
|
1113
|
-
[import_types.NodeType.ServiceNode]: serviceRootCauseShape
|
|
1136
|
+
[import_types.NodeType.ServiceNode]: serviceRootCauseShape,
|
|
1137
|
+
[import_types.NodeType.FileNode]: fileRootCauseShape
|
|
1114
1138
|
};
|
|
1115
1139
|
function getRootCause(graph, errorNodeId, errorEvent) {
|
|
1116
1140
|
if (!graph.hasNode(errorNodeId)) return null;
|
|
@@ -1267,6 +1291,7 @@ function gradedConfidence(edge) {
|
|
|
1267
1291
|
}
|
|
1268
1292
|
function detectMissingDivergences(graph, bucket) {
|
|
1269
1293
|
const out = [];
|
|
1294
|
+
if (bucket.type === import_types2.EdgeType.CONTAINS) return out;
|
|
1270
1295
|
if (bucket.extracted && !bucket.observed) {
|
|
1271
1296
|
if (!nodeIsFrontier(graph, bucket.target)) {
|
|
1272
1297
|
out.push({
|
|
@@ -1857,6 +1882,86 @@ function hostFromUrl(u) {
|
|
|
1857
1882
|
function pickAddress(span) {
|
|
1858
1883
|
return pickAttr(span, "server.address", "net.peer.name", "net.host.name") ?? hostFromUrl(pickAttr(span, "url.full", "http.url"));
|
|
1859
1884
|
}
|
|
1885
|
+
var CODE_FILEPATH_ATTR = "code.filepath";
|
|
1886
|
+
var CODE_LINENO_ATTR = "code.lineno";
|
|
1887
|
+
var CODE_FUNCTION_ATTR = "code.function";
|
|
1888
|
+
function toPosix(p) {
|
|
1889
|
+
return p.split("\\").join("/");
|
|
1890
|
+
}
|
|
1891
|
+
function languageForExt(relPath) {
|
|
1892
|
+
const dot = relPath.lastIndexOf(".");
|
|
1893
|
+
if (dot === -1) return void 0;
|
|
1894
|
+
switch (relPath.slice(dot).toLowerCase()) {
|
|
1895
|
+
case ".py":
|
|
1896
|
+
return "python";
|
|
1897
|
+
case ".ts":
|
|
1898
|
+
case ".tsx":
|
|
1899
|
+
return "typescript";
|
|
1900
|
+
case ".js":
|
|
1901
|
+
case ".jsx":
|
|
1902
|
+
case ".mjs":
|
|
1903
|
+
case ".cjs":
|
|
1904
|
+
return "javascript";
|
|
1905
|
+
default:
|
|
1906
|
+
return void 0;
|
|
1907
|
+
}
|
|
1908
|
+
}
|
|
1909
|
+
function relPathForRuntimeFile(filepath, serviceNode) {
|
|
1910
|
+
let p = toPosix(filepath).replace(/^file:\/\//, "");
|
|
1911
|
+
const root = serviceNode?.repoPath;
|
|
1912
|
+
if (root && root !== "." && root.length > 0) {
|
|
1913
|
+
const rootPosix = toPosix(root);
|
|
1914
|
+
const anchor = `/${rootPosix}/`;
|
|
1915
|
+
const idx = p.lastIndexOf(anchor);
|
|
1916
|
+
if (idx !== -1) return p.slice(idx + anchor.length);
|
|
1917
|
+
const base = rootPosix.split("/").filter(Boolean).pop();
|
|
1918
|
+
if (base) {
|
|
1919
|
+
const baseAnchor = `/${base}/`;
|
|
1920
|
+
const bidx = p.lastIndexOf(baseAnchor);
|
|
1921
|
+
if (bidx !== -1) return p.slice(bidx + baseAnchor.length);
|
|
1922
|
+
}
|
|
1923
|
+
}
|
|
1924
|
+
p = p.replace(/^[A-Za-z]:/, "").replace(/^\/+/, "");
|
|
1925
|
+
return p.length > 0 ? p : null;
|
|
1926
|
+
}
|
|
1927
|
+
function callSiteFromSpan(span, serviceNode) {
|
|
1928
|
+
const filepath = span.attributes[CODE_FILEPATH_ATTR];
|
|
1929
|
+
if (typeof filepath !== "string" || filepath.length === 0) return null;
|
|
1930
|
+
const relPath = relPathForRuntimeFile(filepath, serviceNode);
|
|
1931
|
+
if (!relPath) return null;
|
|
1932
|
+
const linenoRaw = span.attributes[CODE_LINENO_ATTR];
|
|
1933
|
+
const line = typeof linenoRaw === "number" && Number.isFinite(linenoRaw) ? linenoRaw : void 0;
|
|
1934
|
+
const fnRaw = span.attributes[CODE_FUNCTION_ATTR];
|
|
1935
|
+
const fn = typeof fnRaw === "string" && fnRaw.length > 0 ? fnRaw : void 0;
|
|
1936
|
+
return { relPath, ...line !== void 0 ? { line } : {}, ...fn ? { fn } : {} };
|
|
1937
|
+
}
|
|
1938
|
+
function ensureObservedFileNode(graph, serviceName, serviceNodeId, callSite) {
|
|
1939
|
+
const fileNodeId = (0, import_types4.fileId)(serviceName, callSite.relPath);
|
|
1940
|
+
if (!graph.hasNode(fileNodeId)) {
|
|
1941
|
+
const language = languageForExt(callSite.relPath);
|
|
1942
|
+
const node = {
|
|
1943
|
+
id: fileNodeId,
|
|
1944
|
+
type: import_types4.NodeType.FileNode,
|
|
1945
|
+
service: serviceName,
|
|
1946
|
+
path: callSite.relPath,
|
|
1947
|
+
...language ? { language } : {},
|
|
1948
|
+
discoveredVia: "otel"
|
|
1949
|
+
};
|
|
1950
|
+
graph.addNode(fileNodeId, node);
|
|
1951
|
+
}
|
|
1952
|
+
const containsId = makeObservedEdgeId(import_types4.EdgeType.CONTAINS, serviceNodeId, fileNodeId);
|
|
1953
|
+
if (!graph.hasEdge(containsId)) {
|
|
1954
|
+
const edge = {
|
|
1955
|
+
id: containsId,
|
|
1956
|
+
source: serviceNodeId,
|
|
1957
|
+
target: fileNodeId,
|
|
1958
|
+
type: import_types4.EdgeType.CONTAINS,
|
|
1959
|
+
provenance: import_types4.Provenance.OBSERVED
|
|
1960
|
+
};
|
|
1961
|
+
graph.addEdgeWithKey(containsId, serviceNodeId, fileNodeId, edge);
|
|
1962
|
+
}
|
|
1963
|
+
return fileNodeId;
|
|
1964
|
+
}
|
|
1860
1965
|
function makeObservedEdgeId(type, source, target) {
|
|
1861
1966
|
return (0, import_types4.observedEdgeId)(source, target, type);
|
|
1862
1967
|
}
|
|
@@ -2074,6 +2179,9 @@ async function handleSpan(ctx, span) {
|
|
|
2074
2179
|
const sourceId = ensureServiceNode(ctx.graph, span.service, env);
|
|
2075
2180
|
const isError = span.statusCode === 2;
|
|
2076
2181
|
cacheSpanService(span, nowMs);
|
|
2182
|
+
const sourceServiceNode = ctx.graph.getNodeAttributes(sourceId);
|
|
2183
|
+
const callSite = callSiteFromSpan(span, sourceServiceNode);
|
|
2184
|
+
const observedSource = () => callSite ? ensureObservedFileNode(ctx.graph, span.service, sourceId, callSite) : sourceId;
|
|
2077
2185
|
let affectedNode = sourceId;
|
|
2078
2186
|
if (span.dbSystem) {
|
|
2079
2187
|
const host = pickAddress(span);
|
|
@@ -2083,7 +2191,7 @@ async function handleSpan(ctx, span) {
|
|
|
2083
2191
|
const result = upsertObservedEdge(
|
|
2084
2192
|
ctx.graph,
|
|
2085
2193
|
import_types4.EdgeType.CONNECTS_TO,
|
|
2086
|
-
|
|
2194
|
+
observedSource(),
|
|
2087
2195
|
targetId,
|
|
2088
2196
|
ts,
|
|
2089
2197
|
isError
|
|
@@ -2099,7 +2207,7 @@ async function handleSpan(ctx, span) {
|
|
|
2099
2207
|
upsertObservedEdge(
|
|
2100
2208
|
ctx.graph,
|
|
2101
2209
|
import_types4.EdgeType.CALLS,
|
|
2102
|
-
|
|
2210
|
+
observedSource(),
|
|
2103
2211
|
targetId,
|
|
2104
2212
|
ts,
|
|
2105
2213
|
isError
|
|
@@ -2111,7 +2219,7 @@ async function handleSpan(ctx, span) {
|
|
|
2111
2219
|
upsertObservedEdge(
|
|
2112
2220
|
ctx.graph,
|
|
2113
2221
|
import_types4.EdgeType.CALLS,
|
|
2114
|
-
|
|
2222
|
+
observedSource(),
|
|
2115
2223
|
frontierNodeId,
|
|
2116
2224
|
ts,
|
|
2117
2225
|
isError
|
|
@@ -3742,7 +3850,7 @@ async function addConfigNodes(graph, services, scanPath) {
|
|
|
3742
3850
|
|
|
3743
3851
|
// src/extract/calls/index.ts
|
|
3744
3852
|
init_cjs_shims();
|
|
3745
|
-
var
|
|
3853
|
+
var import_types16 = require("@neat.is/types");
|
|
3746
3854
|
|
|
3747
3855
|
// src/extract/calls/http.ts
|
|
3748
3856
|
init_cjs_shims();
|
|
@@ -3750,12 +3858,13 @@ var import_node_path20 = __toESM(require("path"), 1);
|
|
|
3750
3858
|
var import_tree_sitter = __toESM(require("tree-sitter"), 1);
|
|
3751
3859
|
var import_tree_sitter_javascript = __toESM(require("tree-sitter-javascript"), 1);
|
|
3752
3860
|
var import_tree_sitter_python = __toESM(require("tree-sitter-python"), 1);
|
|
3753
|
-
var
|
|
3861
|
+
var import_types11 = require("@neat.is/types");
|
|
3754
3862
|
|
|
3755
3863
|
// src/extract/calls/shared.ts
|
|
3756
3864
|
init_cjs_shims();
|
|
3757
3865
|
var import_node_fs13 = require("fs");
|
|
3758
3866
|
var import_node_path19 = __toESM(require("path"), 1);
|
|
3867
|
+
var import_types10 = require("@neat.is/types");
|
|
3759
3868
|
async function walkSourceFiles(dir) {
|
|
3760
3869
|
const out = [];
|
|
3761
3870
|
async function walk(current) {
|
|
@@ -3795,6 +3904,58 @@ function snippet(text, line) {
|
|
|
3795
3904
|
const lines = text.split("\n");
|
|
3796
3905
|
return (lines[line - 1] ?? "").trim();
|
|
3797
3906
|
}
|
|
3907
|
+
function toPosix2(p) {
|
|
3908
|
+
return p.split("\\").join("/");
|
|
3909
|
+
}
|
|
3910
|
+
function languageForPath(relPath) {
|
|
3911
|
+
switch (import_node_path19.default.extname(relPath).toLowerCase()) {
|
|
3912
|
+
case ".py":
|
|
3913
|
+
return "python";
|
|
3914
|
+
case ".ts":
|
|
3915
|
+
case ".tsx":
|
|
3916
|
+
return "typescript";
|
|
3917
|
+
case ".js":
|
|
3918
|
+
case ".jsx":
|
|
3919
|
+
case ".mjs":
|
|
3920
|
+
case ".cjs":
|
|
3921
|
+
return "javascript";
|
|
3922
|
+
default:
|
|
3923
|
+
return void 0;
|
|
3924
|
+
}
|
|
3925
|
+
}
|
|
3926
|
+
function ensureFileNode(graph, serviceName, serviceNodeId, relPath) {
|
|
3927
|
+
let nodesAdded = 0;
|
|
3928
|
+
let edgesAdded = 0;
|
|
3929
|
+
const fileNodeId = (0, import_types10.fileId)(serviceName, relPath);
|
|
3930
|
+
if (!graph.hasNode(fileNodeId)) {
|
|
3931
|
+
const language = languageForPath(relPath);
|
|
3932
|
+
const node = {
|
|
3933
|
+
id: fileNodeId,
|
|
3934
|
+
type: import_types10.NodeType.FileNode,
|
|
3935
|
+
service: serviceName,
|
|
3936
|
+
path: relPath,
|
|
3937
|
+
...language ? { language } : {},
|
|
3938
|
+
discoveredVia: "static"
|
|
3939
|
+
};
|
|
3940
|
+
graph.addNode(fileNodeId, node);
|
|
3941
|
+
nodesAdded++;
|
|
3942
|
+
}
|
|
3943
|
+
const containsId = (0, import_types10.extractedEdgeId)(serviceNodeId, fileNodeId, import_types10.EdgeType.CONTAINS);
|
|
3944
|
+
if (!graph.hasEdge(containsId)) {
|
|
3945
|
+
const edge = {
|
|
3946
|
+
id: containsId,
|
|
3947
|
+
source: serviceNodeId,
|
|
3948
|
+
target: fileNodeId,
|
|
3949
|
+
type: import_types10.EdgeType.CONTAINS,
|
|
3950
|
+
provenance: import_types10.Provenance.EXTRACTED,
|
|
3951
|
+
confidence: (0, import_types10.confidenceForExtracted)("structural"),
|
|
3952
|
+
evidence: { file: relPath }
|
|
3953
|
+
};
|
|
3954
|
+
graph.addEdgeWithKey(containsId, serviceNodeId, fileNodeId, edge);
|
|
3955
|
+
edgesAdded++;
|
|
3956
|
+
}
|
|
3957
|
+
return { fileNodeId, nodesAdded, edgesAdded };
|
|
3958
|
+
}
|
|
3798
3959
|
|
|
3799
3960
|
// src/extract/calls/http.ts
|
|
3800
3961
|
var STRING_LITERAL_NODE_TYPES = /* @__PURE__ */ new Set(["string_fragment", "string_content"]);
|
|
@@ -3828,16 +3989,16 @@ function callsFromSource(source, parser, knownHosts) {
|
|
|
3828
3989
|
const tree = parser.parse(source);
|
|
3829
3990
|
const literals = [];
|
|
3830
3991
|
collectStringLiterals(tree.rootNode, literals);
|
|
3831
|
-
const
|
|
3992
|
+
const out = [];
|
|
3832
3993
|
for (const lit of literals) {
|
|
3833
3994
|
if (isInsideJsxExternalLink(lit.node)) continue;
|
|
3834
3995
|
for (const host of knownHosts) {
|
|
3835
3996
|
if (urlMatchesHost(lit.text, host)) {
|
|
3836
|
-
|
|
3997
|
+
out.push({ host, line: lit.node.startPosition.row + 1 });
|
|
3837
3998
|
}
|
|
3838
3999
|
}
|
|
3839
4000
|
}
|
|
3840
|
-
return
|
|
4001
|
+
return out;
|
|
3841
4002
|
}
|
|
3842
4003
|
function makeJsParser() {
|
|
3843
4004
|
const p = new import_tree_sitter.default();
|
|
@@ -3860,71 +4021,78 @@ async function addHttpCallEdges(graph, services) {
|
|
|
3860
4021
|
hostToNodeId.set(import_node_path20.default.basename(service.dir), service.node.id);
|
|
3861
4022
|
hostToNodeId.set(service.pkg.name, service.node.id);
|
|
3862
4023
|
}
|
|
4024
|
+
let nodesAdded = 0;
|
|
3863
4025
|
let edgesAdded = 0;
|
|
3864
4026
|
for (const service of services) {
|
|
3865
4027
|
const files = await loadSourceFiles(service.dir);
|
|
3866
|
-
const
|
|
4028
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3867
4029
|
for (const file of files) {
|
|
3868
4030
|
if (isTestPath(file.path)) continue;
|
|
3869
4031
|
const parser = import_node_path20.default.extname(file.path) === ".py" ? pyParser : jsParser;
|
|
3870
|
-
let
|
|
4032
|
+
let sites;
|
|
3871
4033
|
try {
|
|
3872
|
-
|
|
4034
|
+
sites = callsFromSource(file.content, parser, knownHosts);
|
|
3873
4035
|
} catch (err) {
|
|
3874
4036
|
recordExtractionError("http call extraction", file.path, err);
|
|
3875
4037
|
continue;
|
|
3876
4038
|
}
|
|
3877
|
-
|
|
3878
|
-
|
|
4039
|
+
if (sites.length === 0) continue;
|
|
4040
|
+
const relFile = toPosix2(import_node_path20.default.relative(service.dir, file.path));
|
|
4041
|
+
for (const site of sites) {
|
|
4042
|
+
const targetId = hostToNodeId.get(site.host);
|
|
3879
4043
|
if (!targetId || targetId === service.node.id) continue;
|
|
3880
|
-
|
|
3881
|
-
|
|
4044
|
+
const dedupKey = `${relFile}|${targetId}`;
|
|
4045
|
+
if (seen.has(dedupKey)) continue;
|
|
4046
|
+
seen.add(dedupKey);
|
|
4047
|
+
const confidence = (0, import_types11.confidenceForExtracted)("hostname-shape-match");
|
|
4048
|
+
const ev = {
|
|
4049
|
+
file: relFile,
|
|
4050
|
+
line: site.line,
|
|
4051
|
+
snippet: snippet(file.content, site.line)
|
|
4052
|
+
};
|
|
4053
|
+
const { fileNodeId, nodesAdded: n, edgesAdded: e } = ensureFileNode(
|
|
4054
|
+
graph,
|
|
4055
|
+
service.pkg.name,
|
|
4056
|
+
service.node.id,
|
|
4057
|
+
relFile
|
|
4058
|
+
);
|
|
4059
|
+
nodesAdded += n;
|
|
4060
|
+
edgesAdded += e;
|
|
4061
|
+
if (!(0, import_types11.passesExtractedFloor)(confidence)) {
|
|
4062
|
+
noteExtractedDropped({
|
|
4063
|
+
source: fileNodeId,
|
|
4064
|
+
target: targetId,
|
|
4065
|
+
type: import_types11.EdgeType.CALLS,
|
|
4066
|
+
confidence,
|
|
4067
|
+
confidenceKind: "hostname-shape-match",
|
|
4068
|
+
evidence: ev
|
|
4069
|
+
});
|
|
4070
|
+
continue;
|
|
4071
|
+
}
|
|
4072
|
+
const edgeId = (0, import_types5.extractedEdgeId)(fileNodeId, targetId, import_types11.EdgeType.CALLS);
|
|
4073
|
+
if (!graph.hasEdge(edgeId)) {
|
|
4074
|
+
const edge = {
|
|
4075
|
+
id: edgeId,
|
|
4076
|
+
source: fileNodeId,
|
|
4077
|
+
target: targetId,
|
|
4078
|
+
type: import_types11.EdgeType.CALLS,
|
|
4079
|
+
provenance: import_types11.Provenance.EXTRACTED,
|
|
4080
|
+
confidence,
|
|
4081
|
+
evidence: ev
|
|
4082
|
+
};
|
|
4083
|
+
graph.addEdgeWithKey(edgeId, fileNodeId, targetId, edge);
|
|
4084
|
+
edgesAdded++;
|
|
3882
4085
|
}
|
|
3883
|
-
}
|
|
3884
|
-
}
|
|
3885
|
-
for (const [targetId, evidenceFile] of seenTargets) {
|
|
3886
|
-
const fileContent = files.find((f) => f.path === evidenceFile.file)?.content ?? "";
|
|
3887
|
-
const line = lineOf(fileContent, `//${evidenceFile.host}`);
|
|
3888
|
-
const confidence = (0, import_types10.confidenceForExtracted)("hostname-shape-match");
|
|
3889
|
-
const ev = {
|
|
3890
|
-
file: import_node_path20.default.relative(service.dir, evidenceFile.file),
|
|
3891
|
-
line,
|
|
3892
|
-
snippet: snippet(fileContent, line)
|
|
3893
|
-
};
|
|
3894
|
-
const edgeId = (0, import_types5.extractedEdgeId)(service.node.id, targetId, import_types10.EdgeType.CALLS);
|
|
3895
|
-
if (!(0, import_types10.passesExtractedFloor)(confidence)) {
|
|
3896
|
-
noteExtractedDropped({
|
|
3897
|
-
source: service.node.id,
|
|
3898
|
-
target: targetId,
|
|
3899
|
-
type: import_types10.EdgeType.CALLS,
|
|
3900
|
-
confidence,
|
|
3901
|
-
confidenceKind: "hostname-shape-match",
|
|
3902
|
-
evidence: ev
|
|
3903
|
-
});
|
|
3904
|
-
continue;
|
|
3905
|
-
}
|
|
3906
|
-
const edge = {
|
|
3907
|
-
id: edgeId,
|
|
3908
|
-
source: service.node.id,
|
|
3909
|
-
target: targetId,
|
|
3910
|
-
type: import_types10.EdgeType.CALLS,
|
|
3911
|
-
provenance: import_types10.Provenance.EXTRACTED,
|
|
3912
|
-
confidence,
|
|
3913
|
-
evidence: ev
|
|
3914
|
-
};
|
|
3915
|
-
if (!graph.hasEdge(edge.id)) {
|
|
3916
|
-
graph.addEdgeWithKey(edge.id, edge.source, edge.target, edge);
|
|
3917
|
-
edgesAdded++;
|
|
3918
4086
|
}
|
|
3919
4087
|
}
|
|
3920
4088
|
}
|
|
3921
|
-
return edgesAdded;
|
|
4089
|
+
return { nodesAdded, edgesAdded };
|
|
3922
4090
|
}
|
|
3923
4091
|
|
|
3924
4092
|
// src/extract/calls/kafka.ts
|
|
3925
4093
|
init_cjs_shims();
|
|
3926
4094
|
var import_node_path21 = __toESM(require("path"), 1);
|
|
3927
|
-
var
|
|
4095
|
+
var import_types12 = require("@neat.is/types");
|
|
3928
4096
|
var PRODUCER_TOPIC_RE = /(?:producer|kafkaProducer)[\s\S]{0,40}?\.send\s*\(\s*\{[\s\S]{0,200}?topic\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
3929
4097
|
var CONSUMER_TOPIC_RE = /(?:consumer|kafkaConsumer)[\s\S]{0,40}?\.(?:subscribe|run)\s*\(\s*\{[\s\S]{0,200}?topic[s]?\s*:\s*(?:\[\s*)?['"`]([^'"`]+)['"`]/g;
|
|
3930
4098
|
function findAll(re, text) {
|
|
@@ -3945,7 +4113,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
|
|
|
3945
4113
|
seen.add(key);
|
|
3946
4114
|
const line = lineOf(file.content, topic);
|
|
3947
4115
|
out.push({
|
|
3948
|
-
infraId: (0,
|
|
4116
|
+
infraId: (0, import_types12.infraId)("kafka-topic", topic),
|
|
3949
4117
|
name: topic,
|
|
3950
4118
|
kind: "kafka-topic",
|
|
3951
4119
|
edgeType,
|
|
@@ -3968,7 +4136,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
|
|
|
3968
4136
|
// src/extract/calls/redis.ts
|
|
3969
4137
|
init_cjs_shims();
|
|
3970
4138
|
var import_node_path22 = __toESM(require("path"), 1);
|
|
3971
|
-
var
|
|
4139
|
+
var import_types13 = require("@neat.is/types");
|
|
3972
4140
|
var REDIS_URL_RE = /redis(?:s)?:\/\/(?:[^@'"`\s]+@)?([^:/'"`\s]+)(?::(\d+))?/g;
|
|
3973
4141
|
function redisEndpointsFromFile(file, serviceDir) {
|
|
3974
4142
|
const out = [];
|
|
@@ -3981,7 +4149,7 @@ function redisEndpointsFromFile(file, serviceDir) {
|
|
|
3981
4149
|
seen.add(host);
|
|
3982
4150
|
const line = lineOf(file.content, host);
|
|
3983
4151
|
out.push({
|
|
3984
|
-
infraId: (0,
|
|
4152
|
+
infraId: (0, import_types13.infraId)("redis", host),
|
|
3985
4153
|
name: host,
|
|
3986
4154
|
kind: "redis",
|
|
3987
4155
|
edgeType: "CALLS",
|
|
@@ -4002,7 +4170,7 @@ function redisEndpointsFromFile(file, serviceDir) {
|
|
|
4002
4170
|
// src/extract/calls/aws.ts
|
|
4003
4171
|
init_cjs_shims();
|
|
4004
4172
|
var import_node_path23 = __toESM(require("path"), 1);
|
|
4005
|
-
var
|
|
4173
|
+
var import_types14 = require("@neat.is/types");
|
|
4006
4174
|
var S3_BUCKET_RE = /Bucket\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
4007
4175
|
var DYNAMO_TABLE_RE = /TableName\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
4008
4176
|
function hasMarker(text, markers) {
|
|
@@ -4026,7 +4194,7 @@ function awsEndpointsFromFile(file, serviceDir) {
|
|
|
4026
4194
|
seen.add(key);
|
|
4027
4195
|
const line = lineOf(file.content, name);
|
|
4028
4196
|
out.push({
|
|
4029
|
-
infraId: (0,
|
|
4197
|
+
infraId: (0, import_types14.infraId)(kind, name),
|
|
4030
4198
|
name,
|
|
4031
4199
|
kind,
|
|
4032
4200
|
edgeType: "CALLS",
|
|
@@ -4061,7 +4229,7 @@ function awsEndpointsFromFile(file, serviceDir) {
|
|
|
4061
4229
|
// src/extract/calls/grpc.ts
|
|
4062
4230
|
init_cjs_shims();
|
|
4063
4231
|
var import_node_path24 = __toESM(require("path"), 1);
|
|
4064
|
-
var
|
|
4232
|
+
var import_types15 = require("@neat.is/types");
|
|
4065
4233
|
var GRPC_CLIENT_RE = /new\s+([A-Z][A-Za-z0-9_]*)Client\s*\(\s*['"`]?([^,'"`)]+)?/g;
|
|
4066
4234
|
var AWS_SDK_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@aws-sdk\/client-([a-z0-9-]+)['"`]/g;
|
|
4067
4235
|
var GRPC_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@grpc\/grpc-js['"`]|_grpc_pb['"`]/;
|
|
@@ -4109,7 +4277,7 @@ function grpcEndpointsFromFile(file, serviceDir) {
|
|
|
4109
4277
|
const { kind } = classified;
|
|
4110
4278
|
const line = lineOf(file.content, m[0]);
|
|
4111
4279
|
out.push({
|
|
4112
|
-
infraId: (0,
|
|
4280
|
+
infraId: (0, import_types15.infraId)(kind, name),
|
|
4113
4281
|
name,
|
|
4114
4282
|
kind,
|
|
4115
4283
|
edgeType: "CALLS",
|
|
@@ -4131,11 +4299,11 @@ function grpcEndpointsFromFile(file, serviceDir) {
|
|
|
4131
4299
|
function edgeTypeFromEndpoint(ep) {
|
|
4132
4300
|
switch (ep.edgeType) {
|
|
4133
4301
|
case "PUBLISHES_TO":
|
|
4134
|
-
return
|
|
4302
|
+
return import_types16.EdgeType.PUBLISHES_TO;
|
|
4135
4303
|
case "CONSUMES_FROM":
|
|
4136
|
-
return
|
|
4304
|
+
return import_types16.EdgeType.CONSUMES_FROM;
|
|
4137
4305
|
default:
|
|
4138
|
-
return
|
|
4306
|
+
return import_types16.EdgeType.CALLS;
|
|
4139
4307
|
}
|
|
4140
4308
|
}
|
|
4141
4309
|
function isAwsKind(kind) {
|
|
@@ -4162,7 +4330,7 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
4162
4330
|
if (!graph.hasNode(ep.infraId)) {
|
|
4163
4331
|
const node = {
|
|
4164
4332
|
id: ep.infraId,
|
|
4165
|
-
type:
|
|
4333
|
+
type: import_types16.NodeType.InfraNode,
|
|
4166
4334
|
name: ep.name,
|
|
4167
4335
|
// #238 — `aws-*` covers AWS-SDK client kinds (aws-s3, aws-dynamodb,
|
|
4168
4336
|
// aws-cognito-identity-provider, …); `s3-` / `dynamodb-` cover the
|
|
@@ -4174,13 +4342,19 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
4174
4342
|
nodesAdded++;
|
|
4175
4343
|
}
|
|
4176
4344
|
const edgeType = edgeTypeFromEndpoint(ep);
|
|
4177
|
-
const
|
|
4178
|
-
|
|
4179
|
-
|
|
4180
|
-
|
|
4181
|
-
|
|
4345
|
+
const confidence = (0, import_types16.confidenceForExtracted)(ep.confidenceKind);
|
|
4346
|
+
const relFile = toPosix2(ep.evidence.file);
|
|
4347
|
+
const { fileNodeId, nodesAdded: n, edgesAdded: e } = ensureFileNode(
|
|
4348
|
+
graph,
|
|
4349
|
+
service.pkg.name,
|
|
4350
|
+
service.node.id,
|
|
4351
|
+
relFile
|
|
4352
|
+
);
|
|
4353
|
+
nodesAdded += n;
|
|
4354
|
+
edgesAdded += e;
|
|
4355
|
+
if (!(0, import_types16.passesExtractedFloor)(confidence)) {
|
|
4182
4356
|
noteExtractedDropped({
|
|
4183
|
-
source:
|
|
4357
|
+
source: fileNodeId,
|
|
4184
4358
|
target: ep.infraId,
|
|
4185
4359
|
type: edgeType,
|
|
4186
4360
|
confidence,
|
|
@@ -4189,13 +4363,16 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
4189
4363
|
});
|
|
4190
4364
|
continue;
|
|
4191
4365
|
}
|
|
4366
|
+
const edgeId = (0, import_types5.extractedEdgeId)(fileNodeId, ep.infraId, edgeType);
|
|
4367
|
+
if (seenEdges.has(edgeId)) continue;
|
|
4368
|
+
seenEdges.add(edgeId);
|
|
4192
4369
|
if (!graph.hasEdge(edgeId)) {
|
|
4193
4370
|
const edge = {
|
|
4194
4371
|
id: edgeId,
|
|
4195
|
-
source:
|
|
4372
|
+
source: fileNodeId,
|
|
4196
4373
|
target: ep.infraId,
|
|
4197
4374
|
type: edgeType,
|
|
4198
|
-
provenance:
|
|
4375
|
+
provenance: import_types16.Provenance.EXTRACTED,
|
|
4199
4376
|
confidence,
|
|
4200
4377
|
evidence: ep.evidence
|
|
4201
4378
|
};
|
|
@@ -4207,11 +4384,11 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
4207
4384
|
return { nodesAdded, edgesAdded };
|
|
4208
4385
|
}
|
|
4209
4386
|
async function addCallEdges(graph, services) {
|
|
4210
|
-
const
|
|
4387
|
+
const http = await addHttpCallEdges(graph, services);
|
|
4211
4388
|
const ext = await addExternalEndpointEdges(graph, services);
|
|
4212
4389
|
return {
|
|
4213
|
-
nodesAdded: ext.nodesAdded,
|
|
4214
|
-
edgesAdded:
|
|
4390
|
+
nodesAdded: http.nodesAdded + ext.nodesAdded,
|
|
4391
|
+
edgesAdded: http.edgesAdded + ext.edgesAdded
|
|
4215
4392
|
};
|
|
4216
4393
|
}
|
|
4217
4394
|
|
|
@@ -4221,15 +4398,15 @@ init_cjs_shims();
|
|
|
4221
4398
|
// src/extract/infra/docker-compose.ts
|
|
4222
4399
|
init_cjs_shims();
|
|
4223
4400
|
var import_node_path25 = __toESM(require("path"), 1);
|
|
4224
|
-
var
|
|
4401
|
+
var import_types18 = require("@neat.is/types");
|
|
4225
4402
|
|
|
4226
4403
|
// src/extract/infra/shared.ts
|
|
4227
4404
|
init_cjs_shims();
|
|
4228
|
-
var
|
|
4405
|
+
var import_types17 = require("@neat.is/types");
|
|
4229
4406
|
function makeInfraNode(kind, name, provider = "self", extras) {
|
|
4230
4407
|
return {
|
|
4231
|
-
id: (0,
|
|
4232
|
-
type:
|
|
4408
|
+
id: (0, import_types17.infraId)(kind, name),
|
|
4409
|
+
type: import_types17.NodeType.InfraNode,
|
|
4233
4410
|
name,
|
|
4234
4411
|
provider,
|
|
4235
4412
|
kind,
|
|
@@ -4308,15 +4485,15 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
4308
4485
|
for (const dep of dependsOnList(svc.depends_on)) {
|
|
4309
4486
|
const targetId = composeNameToNodeId.get(dep);
|
|
4310
4487
|
if (!targetId) continue;
|
|
4311
|
-
const edgeId = (0, import_types5.extractedEdgeId)(sourceId, targetId,
|
|
4488
|
+
const edgeId = (0, import_types5.extractedEdgeId)(sourceId, targetId, import_types18.EdgeType.DEPENDS_ON);
|
|
4312
4489
|
if (graph.hasEdge(edgeId)) continue;
|
|
4313
4490
|
const edge = {
|
|
4314
4491
|
id: edgeId,
|
|
4315
4492
|
source: sourceId,
|
|
4316
4493
|
target: targetId,
|
|
4317
|
-
type:
|
|
4318
|
-
provenance:
|
|
4319
|
-
confidence: (0,
|
|
4494
|
+
type: import_types18.EdgeType.DEPENDS_ON,
|
|
4495
|
+
provenance: import_types18.Provenance.EXTRACTED,
|
|
4496
|
+
confidence: (0, import_types18.confidenceForExtracted)("structural"),
|
|
4320
4497
|
evidence: { file: evidenceFile }
|
|
4321
4498
|
};
|
|
4322
4499
|
graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
|
|
@@ -4330,7 +4507,7 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
4330
4507
|
init_cjs_shims();
|
|
4331
4508
|
var import_node_path26 = __toESM(require("path"), 1);
|
|
4332
4509
|
var import_node_fs14 = require("fs");
|
|
4333
|
-
var
|
|
4510
|
+
var import_types19 = require("@neat.is/types");
|
|
4334
4511
|
function runtimeImage(content) {
|
|
4335
4512
|
const lines = content.split("\n");
|
|
4336
4513
|
let last = null;
|
|
@@ -4369,15 +4546,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
4369
4546
|
graph.addNode(node.id, node);
|
|
4370
4547
|
nodesAdded++;
|
|
4371
4548
|
}
|
|
4372
|
-
const edgeId = (0, import_types5.extractedEdgeId)(service.node.id, node.id,
|
|
4549
|
+
const edgeId = (0, import_types5.extractedEdgeId)(service.node.id, node.id, import_types19.EdgeType.RUNS_ON);
|
|
4373
4550
|
if (!graph.hasEdge(edgeId)) {
|
|
4374
4551
|
const edge = {
|
|
4375
4552
|
id: edgeId,
|
|
4376
4553
|
source: service.node.id,
|
|
4377
4554
|
target: node.id,
|
|
4378
|
-
type:
|
|
4379
|
-
provenance:
|
|
4380
|
-
confidence: (0,
|
|
4555
|
+
type: import_types19.EdgeType.RUNS_ON,
|
|
4556
|
+
provenance: import_types19.Provenance.EXTRACTED,
|
|
4557
|
+
confidence: (0, import_types19.confidenceForExtracted)("structural"),
|
|
4381
4558
|
evidence: {
|
|
4382
4559
|
file: import_node_path26.default.relative(scanPath, dockerfilePath).split(import_node_path26.default.sep).join("/")
|
|
4383
4560
|
}
|
|
@@ -4505,13 +4682,24 @@ var import_node_path30 = __toESM(require("path"), 1);
|
|
|
4505
4682
|
init_cjs_shims();
|
|
4506
4683
|
var import_node_fs17 = require("fs");
|
|
4507
4684
|
var import_node_path29 = __toESM(require("path"), 1);
|
|
4508
|
-
var
|
|
4685
|
+
var import_types20 = require("@neat.is/types");
|
|
4686
|
+
function dropOrphanedFileNodes(graph) {
|
|
4687
|
+
const orphans = [];
|
|
4688
|
+
graph.forEachNode((id, attrs) => {
|
|
4689
|
+
if (attrs.type !== import_types20.NodeType.FileNode) return;
|
|
4690
|
+
if (graph.inboundEdges(id).length === 0 && graph.outboundEdges(id).length === 0) {
|
|
4691
|
+
orphans.push(id);
|
|
4692
|
+
}
|
|
4693
|
+
});
|
|
4694
|
+
for (const id of orphans) graph.dropNode(id);
|
|
4695
|
+
return orphans.length;
|
|
4696
|
+
}
|
|
4509
4697
|
function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = []) {
|
|
4510
4698
|
const toDrop = [];
|
|
4511
4699
|
const bases = [scanPath, ...serviceDirs];
|
|
4512
4700
|
graph.forEachEdge((id, attrs) => {
|
|
4513
4701
|
const edge = attrs;
|
|
4514
|
-
if (edge.provenance !==
|
|
4702
|
+
if (edge.provenance !== import_types20.Provenance.EXTRACTED) return;
|
|
4515
4703
|
const evidenceFile = edge.evidence?.file;
|
|
4516
4704
|
if (!evidenceFile) return;
|
|
4517
4705
|
if (import_node_path29.default.isAbsolute(evidenceFile)) {
|
|
@@ -4522,6 +4710,7 @@ function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = []) {
|
|
|
4522
4710
|
if (!found) toDrop.push(id);
|
|
4523
4711
|
});
|
|
4524
4712
|
for (const id of toDrop) graph.dropEdge(id);
|
|
4713
|
+
dropOrphanedFileNodes(graph);
|
|
4525
4714
|
return toDrop.length;
|
|
4526
4715
|
}
|
|
4527
4716
|
|
|
@@ -4668,7 +4857,7 @@ function canonicalJson(value) {
|
|
|
4668
4857
|
init_cjs_shims();
|
|
4669
4858
|
var import_node_fs19 = require("fs");
|
|
4670
4859
|
var import_node_path31 = __toESM(require("path"), 1);
|
|
4671
|
-
var
|
|
4860
|
+
var import_types21 = require("@neat.is/types");
|
|
4672
4861
|
var SCHEMA_VERSION = 4;
|
|
4673
4862
|
function migrateV1ToV2(payload) {
|
|
4674
4863
|
const nodes = payload.graph.nodes;
|
|
@@ -4690,12 +4879,12 @@ function migrateV2ToV3(payload) {
|
|
|
4690
4879
|
for (const edge of edges) {
|
|
4691
4880
|
const attrs = edge.attributes;
|
|
4692
4881
|
if (!attrs || attrs.provenance !== "FRONTIER") continue;
|
|
4693
|
-
attrs.provenance =
|
|
4882
|
+
attrs.provenance = import_types21.Provenance.OBSERVED;
|
|
4694
4883
|
const type = typeof attrs.type === "string" ? attrs.type : void 0;
|
|
4695
4884
|
const source = typeof attrs.source === "string" ? attrs.source : void 0;
|
|
4696
4885
|
const target = typeof attrs.target === "string" ? attrs.target : void 0;
|
|
4697
4886
|
if (type && source && target) {
|
|
4698
|
-
const newId = (0,
|
|
4887
|
+
const newId = (0, import_types21.observedEdgeId)(source, target, type);
|
|
4699
4888
|
attrs.id = newId;
|
|
4700
4889
|
if (edge.key) edge.key = newId;
|
|
4701
4890
|
}
|
|
@@ -4838,7 +5027,7 @@ init_cjs_shims();
|
|
|
4838
5027
|
var import_node_fs20 = require("fs");
|
|
4839
5028
|
var import_node_os2 = __toESM(require("os"), 1);
|
|
4840
5029
|
var import_node_path33 = __toESM(require("path"), 1);
|
|
4841
|
-
var
|
|
5030
|
+
var import_types22 = require("@neat.is/types");
|
|
4842
5031
|
function neatHome() {
|
|
4843
5032
|
const override = process.env.NEAT_HOME;
|
|
4844
5033
|
if (override && override.length > 0) return import_node_path33.default.resolve(override);
|
|
@@ -4859,7 +5048,7 @@ async function readRegistry() {
|
|
|
4859
5048
|
throw err;
|
|
4860
5049
|
}
|
|
4861
5050
|
const parsed = JSON.parse(raw);
|
|
4862
|
-
return
|
|
5051
|
+
return import_types22.RegistryFileSchema.parse(parsed);
|
|
4863
5052
|
}
|
|
4864
5053
|
async function getProject(name) {
|
|
4865
5054
|
const reg = await readRegistry();
|
|
@@ -5061,11 +5250,11 @@ function registerRoutes(scope, ctx) {
|
|
|
5061
5250
|
const candidates = req.query.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
5062
5251
|
const parsed = [];
|
|
5063
5252
|
for (const c of candidates) {
|
|
5064
|
-
const r =
|
|
5253
|
+
const r = import_types23.DivergenceTypeSchema.safeParse(c);
|
|
5065
5254
|
if (!r.success) {
|
|
5066
5255
|
return reply.code(400).send({
|
|
5067
5256
|
error: `unknown divergence type "${c}"`,
|
|
5068
|
-
allowed:
|
|
5257
|
+
allowed: import_types23.DivergenceTypeSchema.options
|
|
5069
5258
|
});
|
|
5070
5259
|
}
|
|
5071
5260
|
parsed.push(r.data);
|
|
@@ -5278,7 +5467,7 @@ function registerRoutes(scope, ctx) {
|
|
|
5278
5467
|
const log = new PolicyViolationsLog(proj.paths.policyViolationsPath);
|
|
5279
5468
|
let violations = await log.readAll();
|
|
5280
5469
|
if (req.query.severity) {
|
|
5281
|
-
const sev =
|
|
5470
|
+
const sev = import_types23.PolicySeveritySchema.safeParse(req.query.severity);
|
|
5282
5471
|
if (!sev.success) {
|
|
5283
5472
|
return reply.code(400).send({
|
|
5284
5473
|
error: "invalid severity",
|
|
@@ -5295,7 +5484,7 @@ function registerRoutes(scope, ctx) {
|
|
|
5295
5484
|
scope.post("/policies/check", async (req, reply) => {
|
|
5296
5485
|
const proj = resolveProject(registry, req, reply, ctx.bootstrap);
|
|
5297
5486
|
if (!proj) return;
|
|
5298
|
-
const parsed =
|
|
5487
|
+
const parsed = import_types23.PoliciesCheckBodySchema.safeParse(req.body ?? {});
|
|
5299
5488
|
if (!parsed.success) {
|
|
5300
5489
|
return reply.code(400).send({
|
|
5301
5490
|
error: "invalid /policies/check body",
|
|
@@ -5332,14 +5521,18 @@ function registerRoutes(scope, ctx) {
|
|
|
5332
5521
|
async function buildApi(opts) {
|
|
5333
5522
|
const app = (0, import_fastify.default)({ logger: false });
|
|
5334
5523
|
await app.register(import_cors.default, { origin: true });
|
|
5524
|
+
const env = readAuthEnv();
|
|
5525
|
+
const authToken = opts.authToken ?? env.authToken;
|
|
5526
|
+
const trustProxy = opts.trustProxy ?? env.trustProxy;
|
|
5527
|
+
const publicRead = opts.publicRead ?? env.publicRead;
|
|
5335
5528
|
mountBearerAuth(app, {
|
|
5336
|
-
token:
|
|
5337
|
-
trustProxy
|
|
5338
|
-
publicRead
|
|
5529
|
+
token: authToken,
|
|
5530
|
+
trustProxy,
|
|
5531
|
+
publicRead
|
|
5339
5532
|
});
|
|
5340
5533
|
app.get("/api/config", async () => ({
|
|
5341
|
-
publicRead:
|
|
5342
|
-
authProxy:
|
|
5534
|
+
publicRead: publicRead === true,
|
|
5535
|
+
authProxy: trustProxy === true
|
|
5343
5536
|
}));
|
|
5344
5537
|
const startedAt = opts.startedAt ?? Date.now();
|
|
5345
5538
|
const registry = buildLegacyRegistry(opts);
|