@neat.is/core 0.2.8 → 0.2.10
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-HWO746IM.js → chunk-5BQWBQJR.js} +2 -2
- package/dist/chunk-DGUM43GV.js +11 -0
- package/dist/{chunk-GAYTAGEH.js → chunk-JMLZOKCS.js} +268 -146
- package/dist/chunk-JMLZOKCS.js.map +1 -0
- package/dist/{chunk-I5IMCXRO.js → chunk-QYUB3FQL.js} +2 -2
- package/dist/{chunk-FTKDVKBC.js → chunk-W7ZYJZC7.js} +285 -3
- package/dist/chunk-W7ZYJZC7.js.map +1 -0
- package/dist/cli.cjs +739 -252
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.d.cts +2 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +100 -5
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +565 -172
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +5 -4
- package/dist/neatd.cjs +373 -152
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +112 -6
- package/dist/neatd.js.map +1 -1
- package/dist/{otel-grpc-B4XBSI4W.js → otel-grpc-USFL3OI3.js} +3 -2
- package/dist/otel-grpc-USFL3OI3.js.map +1 -0
- package/dist/server.cjs +659 -264
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +4 -3
- package/dist/server.js.map +1 -1
- package/package.json +2 -2
- package/dist/chunk-FTKDVKBC.js.map +0 -1
- package/dist/chunk-GAYTAGEH.js.map +0 -1
- /package/dist/{chunk-HWO746IM.js.map → chunk-5BQWBQJR.js.map} +0 -0
- /package/dist/{otel-grpc-B4XBSI4W.js.map → chunk-DGUM43GV.js.map} +0 -0
- /package/dist/{chunk-I5IMCXRO.js.map → chunk-QYUB3FQL.js.map} +0 -0
package/dist/server.cjs
CHANGED
|
@@ -99,8 +99,8 @@ function reshapeGrpcRequest(req) {
|
|
|
99
99
|
};
|
|
100
100
|
}
|
|
101
101
|
function resolveProtoRoot() {
|
|
102
|
-
const here =
|
|
103
|
-
return
|
|
102
|
+
const here = import_node_path31.default.dirname((0, import_node_url.fileURLToPath)(importMetaUrl));
|
|
103
|
+
return import_node_path31.default.resolve(here, "..", "proto");
|
|
104
104
|
}
|
|
105
105
|
function loadTraceService() {
|
|
106
106
|
const protoRoot = resolveProtoRoot();
|
|
@@ -155,13 +155,13 @@ async function startOtelGrpcReceiver(opts) {
|
|
|
155
155
|
})
|
|
156
156
|
};
|
|
157
157
|
}
|
|
158
|
-
var import_node_url,
|
|
158
|
+
var import_node_url, import_node_path31, grpc, protoLoader;
|
|
159
159
|
var init_otel_grpc = __esm({
|
|
160
160
|
"src/otel-grpc.ts"() {
|
|
161
161
|
"use strict";
|
|
162
162
|
init_cjs_shims();
|
|
163
163
|
import_node_url = require("url");
|
|
164
|
-
|
|
164
|
+
import_node_path31 = __toESM(require("path"), 1);
|
|
165
165
|
grpc = __toESM(require("@grpc/grpc-js"), 1);
|
|
166
166
|
protoLoader = __toESM(require("@grpc/proto-loader"), 1);
|
|
167
167
|
init_otel();
|
|
@@ -258,10 +258,10 @@ function parseOtlpRequest(body) {
|
|
|
258
258
|
}
|
|
259
259
|
function loadProtobufDecoder() {
|
|
260
260
|
if (exportTraceServiceRequestType) return exportTraceServiceRequestType;
|
|
261
|
-
const here =
|
|
262
|
-
const protoRoot =
|
|
261
|
+
const here = import_node_path32.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
|
|
262
|
+
const protoRoot = import_node_path32.default.resolve(here, "..", "proto");
|
|
263
263
|
const root = new import_protobufjs.default.Root();
|
|
264
|
-
root.resolvePath = (_origin, target) =>
|
|
264
|
+
root.resolvePath = (_origin, target) => import_node_path32.default.resolve(protoRoot, target);
|
|
265
265
|
root.loadSync(
|
|
266
266
|
"opentelemetry/proto/collector/trace/v1/trace_service.proto",
|
|
267
267
|
{ keepCase: true }
|
|
@@ -353,12 +353,12 @@ async function buildOtelReceiver(opts) {
|
|
|
353
353
|
};
|
|
354
354
|
return decorated;
|
|
355
355
|
}
|
|
356
|
-
var
|
|
356
|
+
var import_node_path32, import_node_url2, import_fastify2, import_protobufjs, exportTraceServiceRequestType;
|
|
357
357
|
var init_otel = __esm({
|
|
358
358
|
"src/otel.ts"() {
|
|
359
359
|
"use strict";
|
|
360
360
|
init_cjs_shims();
|
|
361
|
-
|
|
361
|
+
import_node_path32 = __toESM(require("path"), 1);
|
|
362
362
|
import_node_url2 = require("url");
|
|
363
363
|
import_fastify2 = __toESM(require("fastify"), 1);
|
|
364
364
|
import_protobufjs = __toESM(require("protobufjs"), 1);
|
|
@@ -368,7 +368,7 @@ var init_otel = __esm({
|
|
|
368
368
|
|
|
369
369
|
// src/server.ts
|
|
370
370
|
init_cjs_shims();
|
|
371
|
-
var
|
|
371
|
+
var import_node_path34 = __toESM(require("path"), 1);
|
|
372
372
|
|
|
373
373
|
// src/graph.ts
|
|
374
374
|
init_cjs_shims();
|
|
@@ -392,12 +392,10 @@ function getGraph(project = DEFAULT_PROJECT) {
|
|
|
392
392
|
init_cjs_shims();
|
|
393
393
|
var import_fastify = __toESM(require("fastify"), 1);
|
|
394
394
|
var import_cors = __toESM(require("@fastify/cors"), 1);
|
|
395
|
-
var
|
|
395
|
+
var import_types20 = require("@neat.is/types");
|
|
396
396
|
|
|
397
|
-
// src/
|
|
397
|
+
// src/divergences.ts
|
|
398
398
|
init_cjs_shims();
|
|
399
|
-
var import_node_fs2 = require("fs");
|
|
400
|
-
var import_node_path2 = __toESM(require("path"), 1);
|
|
401
399
|
var import_types2 = require("@neat.is/types");
|
|
402
400
|
|
|
403
401
|
// src/compat.ts
|
|
@@ -713,18 +711,6 @@ function deprecatedApis() {
|
|
|
713
711
|
return currentMatrix().deprecatedApis ?? [];
|
|
714
712
|
}
|
|
715
713
|
|
|
716
|
-
// src/events.ts
|
|
717
|
-
init_cjs_shims();
|
|
718
|
-
var import_node_events = require("events");
|
|
719
|
-
var EVENT_BUS_CHANNEL = "event";
|
|
720
|
-
var NeatEventBus = class extends import_node_events.EventEmitter {
|
|
721
|
-
};
|
|
722
|
-
var eventBus = new NeatEventBus();
|
|
723
|
-
eventBus.setMaxListeners(0);
|
|
724
|
-
function emitNeatEvent(envelope) {
|
|
725
|
-
eventBus.emit(EVENT_BUS_CHANNEL, envelope);
|
|
726
|
-
}
|
|
727
|
-
|
|
728
714
|
// src/traverse.ts
|
|
729
715
|
init_cjs_shims();
|
|
730
716
|
var import_types = require("@neat.is/types");
|
|
@@ -812,19 +798,19 @@ function confidenceFromMix(edges, now = Date.now()) {
|
|
|
812
798
|
function longestIncomingWalk(graph, start, maxDepth) {
|
|
813
799
|
let best = { path: [start], edges: [] };
|
|
814
800
|
const visited = /* @__PURE__ */ new Set([start]);
|
|
815
|
-
function step(node,
|
|
816
|
-
if (
|
|
817
|
-
best = { path: [...
|
|
801
|
+
function step(node, path35, edges) {
|
|
802
|
+
if (path35.length > best.path.length) {
|
|
803
|
+
best = { path: [...path35], edges: [...edges] };
|
|
818
804
|
}
|
|
819
|
-
if (
|
|
805
|
+
if (path35.length - 1 >= maxDepth) return;
|
|
820
806
|
const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
|
|
821
807
|
for (const [srcId, edge] of incoming) {
|
|
822
808
|
if (visited.has(srcId)) continue;
|
|
823
809
|
visited.add(srcId);
|
|
824
|
-
|
|
810
|
+
path35.push(srcId);
|
|
825
811
|
edges.push(edge);
|
|
826
|
-
step(srcId,
|
|
827
|
-
|
|
812
|
+
step(srcId, path35, edges);
|
|
813
|
+
path35.pop();
|
|
828
814
|
edges.pop();
|
|
829
815
|
visited.delete(srcId);
|
|
830
816
|
}
|
|
@@ -1006,6 +992,259 @@ function getTransitiveDependencies(graph, nodeId, depth = TRANSITIVE_DEPENDENCIE
|
|
|
1006
992
|
});
|
|
1007
993
|
}
|
|
1008
994
|
|
|
995
|
+
// src/divergences.ts
|
|
996
|
+
function bucketKey(source, target, type) {
|
|
997
|
+
return `${type}|${source}|${target}`;
|
|
998
|
+
}
|
|
999
|
+
function bucketEdges(graph) {
|
|
1000
|
+
const buckets = /* @__PURE__ */ new Map();
|
|
1001
|
+
graph.forEachEdge((id, attrs) => {
|
|
1002
|
+
const e = attrs;
|
|
1003
|
+
const parsed = (0, import_types2.parseEdgeId)(id);
|
|
1004
|
+
const provenance = parsed?.provenance ?? e.provenance;
|
|
1005
|
+
const key = bucketKey(e.source, e.target, e.type);
|
|
1006
|
+
const cur = buckets.get(key) ?? { source: e.source, target: e.target, type: e.type };
|
|
1007
|
+
switch (provenance) {
|
|
1008
|
+
case import_types2.Provenance.EXTRACTED:
|
|
1009
|
+
cur.extracted = e;
|
|
1010
|
+
break;
|
|
1011
|
+
case import_types2.Provenance.OBSERVED:
|
|
1012
|
+
cur.observed = e;
|
|
1013
|
+
break;
|
|
1014
|
+
case import_types2.Provenance.INFERRED:
|
|
1015
|
+
cur.inferred = e;
|
|
1016
|
+
break;
|
|
1017
|
+
case import_types2.Provenance.FRONTIER:
|
|
1018
|
+
cur.frontier = e;
|
|
1019
|
+
break;
|
|
1020
|
+
default:
|
|
1021
|
+
if (e.provenance === import_types2.Provenance.STALE) cur.stale = e;
|
|
1022
|
+
}
|
|
1023
|
+
buckets.set(key, cur);
|
|
1024
|
+
});
|
|
1025
|
+
return buckets;
|
|
1026
|
+
}
|
|
1027
|
+
function nodeIsFrontier(graph, nodeId) {
|
|
1028
|
+
if (!graph.hasNode(nodeId)) return false;
|
|
1029
|
+
const attrs = graph.getNodeAttributes(nodeId);
|
|
1030
|
+
return attrs.type === import_types2.NodeType.FrontierNode;
|
|
1031
|
+
}
|
|
1032
|
+
function hasAnyObservedFromSource(graph, sourceId) {
|
|
1033
|
+
if (!graph.hasNode(sourceId)) return false;
|
|
1034
|
+
for (const edgeId of graph.outboundEdges(sourceId)) {
|
|
1035
|
+
const e = graph.getEdgeAttributes(edgeId);
|
|
1036
|
+
if (e.provenance === import_types2.Provenance.OBSERVED) return true;
|
|
1037
|
+
}
|
|
1038
|
+
return false;
|
|
1039
|
+
}
|
|
1040
|
+
function clampConfidence(n) {
|
|
1041
|
+
if (!Number.isFinite(n)) return 0;
|
|
1042
|
+
return Math.max(0, Math.min(1, n));
|
|
1043
|
+
}
|
|
1044
|
+
function reasonForMissingObserved(source, target, type) {
|
|
1045
|
+
return `Code declares ${source} \u2192 ${target} (${type}) but no production traffic has been observed for this edge.`;
|
|
1046
|
+
}
|
|
1047
|
+
function reasonForMissingExtracted(source, target, type) {
|
|
1048
|
+
return `Production observed ${source} \u2192 ${target} (${type}) but static analysis did not surface this edge.`;
|
|
1049
|
+
}
|
|
1050
|
+
var RECOMMENDATION_MISSING_OBSERVED = "Verify the code path is exercised in production; check feature flags or conditional branches that might gate the call.";
|
|
1051
|
+
var RECOMMENDATION_MISSING_EXTRACTED = "Likely dynamic dispatch, reflection, or a coverage gap in tree-sitter extraction. Consider an `aliases` entry on the source service or file an extractor issue.";
|
|
1052
|
+
var RECOMMENDATION_HOST_MISMATCH = "Check environment-specific config overrides \u2014 the runtime host differs from what static configuration declares.";
|
|
1053
|
+
function detectMissingDivergences(graph, bucket) {
|
|
1054
|
+
const out = [];
|
|
1055
|
+
if (bucket.extracted && !bucket.observed) {
|
|
1056
|
+
if (!nodeIsFrontier(graph, bucket.target)) {
|
|
1057
|
+
const sourceHasTraffic = hasAnyObservedFromSource(graph, bucket.source);
|
|
1058
|
+
out.push({
|
|
1059
|
+
type: "missing-observed",
|
|
1060
|
+
source: bucket.source,
|
|
1061
|
+
target: bucket.target,
|
|
1062
|
+
edgeType: bucket.type,
|
|
1063
|
+
extracted: bucket.extracted,
|
|
1064
|
+
confidence: sourceHasTraffic ? 1 : 0.5,
|
|
1065
|
+
reason: reasonForMissingObserved(bucket.source, bucket.target, bucket.type),
|
|
1066
|
+
recommendation: RECOMMENDATION_MISSING_OBSERVED
|
|
1067
|
+
});
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
if (bucket.observed && !bucket.extracted) {
|
|
1071
|
+
const cascaded = clampConfidence(confidenceForEdge(bucket.observed));
|
|
1072
|
+
out.push({
|
|
1073
|
+
type: "missing-extracted",
|
|
1074
|
+
source: bucket.source,
|
|
1075
|
+
target: bucket.target,
|
|
1076
|
+
edgeType: bucket.type,
|
|
1077
|
+
observed: bucket.observed,
|
|
1078
|
+
confidence: cascaded,
|
|
1079
|
+
reason: reasonForMissingExtracted(bucket.source, bucket.target, bucket.type),
|
|
1080
|
+
recommendation: RECOMMENDATION_MISSING_EXTRACTED
|
|
1081
|
+
});
|
|
1082
|
+
}
|
|
1083
|
+
return out;
|
|
1084
|
+
}
|
|
1085
|
+
function declaredHostFor(svc) {
|
|
1086
|
+
const raw = svc.dbConnectionTarget?.trim();
|
|
1087
|
+
if (!raw) return null;
|
|
1088
|
+
const colon = raw.lastIndexOf(":");
|
|
1089
|
+
if (colon === -1) return raw;
|
|
1090
|
+
const port = raw.slice(colon + 1);
|
|
1091
|
+
if (/^\d+$/.test(port)) return raw.slice(0, colon);
|
|
1092
|
+
return raw;
|
|
1093
|
+
}
|
|
1094
|
+
function hasExtractedConfiguredBy(graph, svcId) {
|
|
1095
|
+
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
1096
|
+
const e = graph.getEdgeAttributes(edgeId);
|
|
1097
|
+
if (e.type === import_types2.EdgeType.CONFIGURED_BY && e.provenance === import_types2.Provenance.EXTRACTED) {
|
|
1098
|
+
return true;
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
return false;
|
|
1102
|
+
}
|
|
1103
|
+
function detectHostMismatch(graph, svcId, svc) {
|
|
1104
|
+
const declaredHost = declaredHostFor(svc);
|
|
1105
|
+
if (!declaredHost) return [];
|
|
1106
|
+
if (!hasExtractedConfiguredBy(graph, svcId)) return [];
|
|
1107
|
+
const out = [];
|
|
1108
|
+
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
1109
|
+
const edge = graph.getEdgeAttributes(edgeId);
|
|
1110
|
+
if (edge.type !== import_types2.EdgeType.CONNECTS_TO) continue;
|
|
1111
|
+
if (edge.provenance !== import_types2.Provenance.OBSERVED) continue;
|
|
1112
|
+
const target = graph.getNodeAttributes(edge.target);
|
|
1113
|
+
if (target.type !== import_types2.NodeType.DatabaseNode) continue;
|
|
1114
|
+
const observedHost = target.host?.trim();
|
|
1115
|
+
if (!observedHost) continue;
|
|
1116
|
+
if (observedHost === declaredHost) continue;
|
|
1117
|
+
out.push({
|
|
1118
|
+
type: "host-mismatch",
|
|
1119
|
+
source: svcId,
|
|
1120
|
+
target: edge.target,
|
|
1121
|
+
extractedHost: declaredHost,
|
|
1122
|
+
observedHost,
|
|
1123
|
+
confidence: clampConfidence(confidenceForEdge(edge)),
|
|
1124
|
+
reason: `Config declares ${svcId} connects to ${declaredHost}; production connects to ${observedHost}.`,
|
|
1125
|
+
recommendation: RECOMMENDATION_HOST_MISMATCH
|
|
1126
|
+
});
|
|
1127
|
+
}
|
|
1128
|
+
return out;
|
|
1129
|
+
}
|
|
1130
|
+
function detectCompatDivergences(graph, svcId, svc) {
|
|
1131
|
+
const out = [];
|
|
1132
|
+
const deps = svc.dependencies ?? {};
|
|
1133
|
+
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
1134
|
+
const edge = graph.getEdgeAttributes(edgeId);
|
|
1135
|
+
if (edge.type !== import_types2.EdgeType.CONNECTS_TO) continue;
|
|
1136
|
+
if (edge.provenance !== import_types2.Provenance.OBSERVED) continue;
|
|
1137
|
+
const target = graph.getNodeAttributes(edge.target);
|
|
1138
|
+
if (target.type !== import_types2.NodeType.DatabaseNode) continue;
|
|
1139
|
+
for (const pair of compatPairs()) {
|
|
1140
|
+
if (pair.engine !== target.engine) continue;
|
|
1141
|
+
const declared = deps[pair.driver];
|
|
1142
|
+
if (!declared) continue;
|
|
1143
|
+
const result = checkCompatibility(
|
|
1144
|
+
pair.driver,
|
|
1145
|
+
declared,
|
|
1146
|
+
target.engine,
|
|
1147
|
+
target.engineVersion
|
|
1148
|
+
);
|
|
1149
|
+
if (!result.compatible && result.reason) {
|
|
1150
|
+
out.push({
|
|
1151
|
+
type: "version-mismatch",
|
|
1152
|
+
source: svcId,
|
|
1153
|
+
target: edge.target,
|
|
1154
|
+
extractedVersion: declared,
|
|
1155
|
+
observedVersion: target.engineVersion,
|
|
1156
|
+
compatibility: "incompatible",
|
|
1157
|
+
confidence: 1,
|
|
1158
|
+
reason: result.reason,
|
|
1159
|
+
recommendation: result.minDriverVersion ? `Upgrade ${pair.driver} to >= ${result.minDriverVersion}.` : `Update the ${pair.driver} driver to a version compatible with ${target.engine} ${target.engineVersion}.`
|
|
1160
|
+
});
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
for (const rule of deprecatedApis()) {
|
|
1164
|
+
const declared = deps[rule.package];
|
|
1165
|
+
if (!declared) continue;
|
|
1166
|
+
const result = checkDeprecatedApi(rule, declared);
|
|
1167
|
+
if (!result.compatible && result.reason) {
|
|
1168
|
+
const ruleRef = {
|
|
1169
|
+
kind: rule.kind ?? "deprecated-api",
|
|
1170
|
+
reason: result.reason,
|
|
1171
|
+
package: rule.package
|
|
1172
|
+
};
|
|
1173
|
+
out.push({
|
|
1174
|
+
type: "compat-violation",
|
|
1175
|
+
source: svcId,
|
|
1176
|
+
target: edge.target,
|
|
1177
|
+
rule: ruleRef,
|
|
1178
|
+
observed: edge,
|
|
1179
|
+
confidence: 1,
|
|
1180
|
+
reason: result.reason,
|
|
1181
|
+
recommendation: `Replace deprecated ${rule.package}@${declared} with a supported version.`
|
|
1182
|
+
});
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
return out;
|
|
1187
|
+
}
|
|
1188
|
+
function involvesNode(d, nodeId) {
|
|
1189
|
+
return d.source === nodeId || d.target === nodeId;
|
|
1190
|
+
}
|
|
1191
|
+
function computeDivergences(graph, opts = {}) {
|
|
1192
|
+
const all = [];
|
|
1193
|
+
const buckets = bucketEdges(graph);
|
|
1194
|
+
for (const bucket of buckets.values()) {
|
|
1195
|
+
for (const d of detectMissingDivergences(graph, bucket)) all.push(d);
|
|
1196
|
+
}
|
|
1197
|
+
graph.forEachNode((nodeId, attrs) => {
|
|
1198
|
+
const n = attrs;
|
|
1199
|
+
if (n.type !== import_types2.NodeType.ServiceNode) return;
|
|
1200
|
+
const svc = n;
|
|
1201
|
+
for (const d of detectHostMismatch(graph, nodeId, svc)) all.push(d);
|
|
1202
|
+
for (const d of detectCompatDivergences(graph, nodeId, svc)) all.push(d);
|
|
1203
|
+
});
|
|
1204
|
+
let filtered = all;
|
|
1205
|
+
if (opts.type) {
|
|
1206
|
+
const allowed = opts.type;
|
|
1207
|
+
filtered = filtered.filter((d) => allowed.has(d.type));
|
|
1208
|
+
}
|
|
1209
|
+
if (opts.minConfidence !== void 0) {
|
|
1210
|
+
const threshold = opts.minConfidence;
|
|
1211
|
+
filtered = filtered.filter((d) => d.confidence >= threshold);
|
|
1212
|
+
}
|
|
1213
|
+
if (opts.node) {
|
|
1214
|
+
const target = opts.node;
|
|
1215
|
+
filtered = filtered.filter((d) => involvesNode(d, target));
|
|
1216
|
+
}
|
|
1217
|
+
filtered.sort((a, b) => {
|
|
1218
|
+
if (b.confidence !== a.confidence) return b.confidence - a.confidence;
|
|
1219
|
+
if (a.type !== b.type) return a.type.localeCompare(b.type);
|
|
1220
|
+
if (a.source !== b.source) return a.source.localeCompare(b.source);
|
|
1221
|
+
return a.target.localeCompare(b.target);
|
|
1222
|
+
});
|
|
1223
|
+
return import_types2.DivergenceResultSchema.parse({
|
|
1224
|
+
divergences: filtered,
|
|
1225
|
+
totalAffected: filtered.length,
|
|
1226
|
+
computedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1227
|
+
});
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
// src/policy.ts
|
|
1231
|
+
init_cjs_shims();
|
|
1232
|
+
var import_node_fs2 = require("fs");
|
|
1233
|
+
var import_node_path2 = __toESM(require("path"), 1);
|
|
1234
|
+
var import_types3 = require("@neat.is/types");
|
|
1235
|
+
|
|
1236
|
+
// src/events.ts
|
|
1237
|
+
init_cjs_shims();
|
|
1238
|
+
var import_node_events = require("events");
|
|
1239
|
+
var EVENT_BUS_CHANNEL = "event";
|
|
1240
|
+
var NeatEventBus = class extends import_node_events.EventEmitter {
|
|
1241
|
+
};
|
|
1242
|
+
var eventBus = new NeatEventBus();
|
|
1243
|
+
eventBus.setMaxListeners(0);
|
|
1244
|
+
function emitNeatEvent(envelope) {
|
|
1245
|
+
eventBus.emit(EVENT_BUS_CHANNEL, envelope);
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1009
1248
|
// src/policy.ts
|
|
1010
1249
|
var DEFAULT_ACTION_BY_SEVERITY = {
|
|
1011
1250
|
info: "log",
|
|
@@ -1043,7 +1282,7 @@ var evaluateStructural = ({
|
|
|
1043
1282
|
for (const edgeId of graph.outboundEdges(id)) {
|
|
1044
1283
|
const e = graph.getEdgeAttributes(edgeId);
|
|
1045
1284
|
if (e.type !== rule.edgeType) continue;
|
|
1046
|
-
if (e.provenance ===
|
|
1285
|
+
if (e.provenance === import_types3.Provenance.FRONTIER) continue;
|
|
1047
1286
|
const target = graph.getNodeAttributes(e.target);
|
|
1048
1287
|
if (target.type === rule.toNodeType) {
|
|
1049
1288
|
satisfied = true;
|
|
@@ -1156,16 +1395,16 @@ var evaluateCompatibility = ({
|
|
|
1156
1395
|
const wantsKind = (kind) => rule.kind === void 0 || rule.kind === kind;
|
|
1157
1396
|
graph.forEachNode((svcId, attrs) => {
|
|
1158
1397
|
const a = attrs;
|
|
1159
|
-
if (a.type !==
|
|
1398
|
+
if (a.type !== import_types3.NodeType.ServiceNode) return;
|
|
1160
1399
|
const svc = a;
|
|
1161
1400
|
const deps = svc.dependencies ?? {};
|
|
1162
1401
|
if (wantsKind("driver-engine")) {
|
|
1163
1402
|
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
1164
1403
|
const e = graph.getEdgeAttributes(edgeId);
|
|
1165
|
-
if (e.type !==
|
|
1166
|
-
if (e.provenance ===
|
|
1404
|
+
if (e.type !== import_types3.EdgeType.CONNECTS_TO) continue;
|
|
1405
|
+
if (e.provenance === import_types3.Provenance.FRONTIER) continue;
|
|
1167
1406
|
const dbAttrs = graph.getNodeAttributes(e.target);
|
|
1168
|
-
if (dbAttrs.type !==
|
|
1407
|
+
if (dbAttrs.type !== import_types3.NodeType.DatabaseNode) continue;
|
|
1169
1408
|
const db = dbAttrs;
|
|
1170
1409
|
for (const pair of compatPairs()) {
|
|
1171
1410
|
if (pair.engine !== db.engine) continue;
|
|
@@ -1283,7 +1522,7 @@ async function loadPolicyFile(policyPath) {
|
|
|
1283
1522
|
throw err;
|
|
1284
1523
|
}
|
|
1285
1524
|
const json = JSON.parse(raw);
|
|
1286
|
-
const file =
|
|
1525
|
+
const file = import_types3.PolicyFileSchema.parse(json);
|
|
1287
1526
|
return file.policies;
|
|
1288
1527
|
}
|
|
1289
1528
|
var PolicyViolationsLog = class {
|
|
@@ -1333,7 +1572,7 @@ init_cjs_shims();
|
|
|
1333
1572
|
init_cjs_shims();
|
|
1334
1573
|
var import_node_fs3 = require("fs");
|
|
1335
1574
|
var import_node_path3 = __toESM(require("path"), 1);
|
|
1336
|
-
var
|
|
1575
|
+
var import_types4 = require("@neat.is/types");
|
|
1337
1576
|
var HOUR_MS = 60 * 60 * 1e3;
|
|
1338
1577
|
var DAY_MS = 24 * HOUR_MS;
|
|
1339
1578
|
var DEFAULT_STALE_THRESHOLDS = {
|
|
@@ -1389,10 +1628,10 @@ function pickAddress(span) {
|
|
|
1389
1628
|
return pickAttr(span, "server.address", "net.peer.name", "net.host.name") ?? hostFromUrl(pickAttr(span, "url.full", "http.url"));
|
|
1390
1629
|
}
|
|
1391
1630
|
function makeObservedEdgeId(type, source, target) {
|
|
1392
|
-
return (0,
|
|
1631
|
+
return (0, import_types4.observedEdgeId)(source, target, type);
|
|
1393
1632
|
}
|
|
1394
1633
|
function makeInferredEdgeId(type, source, target) {
|
|
1395
|
-
return (0,
|
|
1634
|
+
return (0, import_types4.inferredEdgeId)(source, target, type);
|
|
1396
1635
|
}
|
|
1397
1636
|
var INFERRED_CONFIDENCE = 0.6;
|
|
1398
1637
|
var STITCH_MAX_DEPTH = 2;
|
|
@@ -1423,13 +1662,13 @@ function lookupParentSpanService(traceId, parentSpanId, now) {
|
|
|
1423
1662
|
return entry.service;
|
|
1424
1663
|
}
|
|
1425
1664
|
function resolveServiceId(graph, host) {
|
|
1426
|
-
const direct = (0,
|
|
1665
|
+
const direct = (0, import_types4.serviceId)(host);
|
|
1427
1666
|
if (graph.hasNode(direct)) return direct;
|
|
1428
1667
|
let found = null;
|
|
1429
1668
|
graph.forEachNode((id, attrs) => {
|
|
1430
1669
|
if (found) return;
|
|
1431
1670
|
const a = attrs;
|
|
1432
|
-
if (a.type !==
|
|
1671
|
+
if (a.type !== import_types4.NodeType.ServiceNode) return;
|
|
1433
1672
|
if (a.name === host) {
|
|
1434
1673
|
found = id;
|
|
1435
1674
|
return;
|
|
@@ -1441,14 +1680,14 @@ function resolveServiceId(graph, host) {
|
|
|
1441
1680
|
return found;
|
|
1442
1681
|
}
|
|
1443
1682
|
function frontierIdFor(host) {
|
|
1444
|
-
return (0,
|
|
1683
|
+
return (0, import_types4.frontierId)(host);
|
|
1445
1684
|
}
|
|
1446
1685
|
function ensureServiceNode(graph, serviceName) {
|
|
1447
|
-
const id = (0,
|
|
1686
|
+
const id = (0, import_types4.serviceId)(serviceName);
|
|
1448
1687
|
if (graph.hasNode(id)) return id;
|
|
1449
1688
|
const node = {
|
|
1450
1689
|
id,
|
|
1451
|
-
type:
|
|
1690
|
+
type: import_types4.NodeType.ServiceNode,
|
|
1452
1691
|
name: serviceName,
|
|
1453
1692
|
language: "unknown",
|
|
1454
1693
|
discoveredVia: "otel"
|
|
@@ -1457,11 +1696,11 @@ function ensureServiceNode(graph, serviceName) {
|
|
|
1457
1696
|
return id;
|
|
1458
1697
|
}
|
|
1459
1698
|
function ensureDatabaseNode(graph, host, engine) {
|
|
1460
|
-
const id = (0,
|
|
1699
|
+
const id = (0, import_types4.databaseId)(host);
|
|
1461
1700
|
if (graph.hasNode(id)) return id;
|
|
1462
1701
|
const node = {
|
|
1463
1702
|
id,
|
|
1464
|
-
type:
|
|
1703
|
+
type: import_types4.NodeType.DatabaseNode,
|
|
1465
1704
|
name: host,
|
|
1466
1705
|
engine,
|
|
1467
1706
|
engineVersion: "unknown",
|
|
@@ -1481,7 +1720,7 @@ function ensureFrontierNode(graph, host, ts) {
|
|
|
1481
1720
|
}
|
|
1482
1721
|
const node = {
|
|
1483
1722
|
id,
|
|
1484
|
-
type:
|
|
1723
|
+
type: import_types4.NodeType.FrontierNode,
|
|
1485
1724
|
name: host,
|
|
1486
1725
|
host,
|
|
1487
1726
|
firstObserved: ts,
|
|
@@ -1491,12 +1730,12 @@ function ensureFrontierNode(graph, host, ts) {
|
|
|
1491
1730
|
return id;
|
|
1492
1731
|
}
|
|
1493
1732
|
function upsertFrontierEdge(graph, type, source, target, ts) {
|
|
1494
|
-
const id = (0,
|
|
1733
|
+
const id = (0, import_types4.frontierEdgeId)(source, target, type);
|
|
1495
1734
|
if (graph.hasEdge(id)) {
|
|
1496
1735
|
const existing = graph.getEdgeAttributes(id);
|
|
1497
1736
|
const updated = {
|
|
1498
1737
|
...existing,
|
|
1499
|
-
provenance:
|
|
1738
|
+
provenance: import_types4.Provenance.FRONTIER,
|
|
1500
1739
|
lastObserved: ts,
|
|
1501
1740
|
callCount: (existing.callCount ?? 0) + 1
|
|
1502
1741
|
};
|
|
@@ -1508,7 +1747,7 @@ function upsertFrontierEdge(graph, type, source, target, ts) {
|
|
|
1508
1747
|
source,
|
|
1509
1748
|
target,
|
|
1510
1749
|
type,
|
|
1511
|
-
provenance:
|
|
1750
|
+
provenance: import_types4.Provenance.FRONTIER,
|
|
1512
1751
|
confidence: 1,
|
|
1513
1752
|
lastObserved: ts,
|
|
1514
1753
|
callCount: 1
|
|
@@ -1524,7 +1763,7 @@ function upsertObservedEdge(graph, type, source, target, ts, isError = false) {
|
|
|
1524
1763
|
const newErrorCount = (existing.signal?.errorCount ?? 0) + (isError ? 1 : 0);
|
|
1525
1764
|
const updated = {
|
|
1526
1765
|
...existing,
|
|
1527
|
-
provenance:
|
|
1766
|
+
provenance: import_types4.Provenance.OBSERVED,
|
|
1528
1767
|
lastObserved: ts,
|
|
1529
1768
|
callCount: newSpanCount,
|
|
1530
1769
|
signal: {
|
|
@@ -1542,7 +1781,7 @@ function upsertObservedEdge(graph, type, source, target, ts, isError = false) {
|
|
|
1542
1781
|
source,
|
|
1543
1782
|
target,
|
|
1544
1783
|
type,
|
|
1545
|
-
provenance:
|
|
1784
|
+
provenance: import_types4.Provenance.OBSERVED,
|
|
1546
1785
|
confidence: 1,
|
|
1547
1786
|
lastObserved: ts,
|
|
1548
1787
|
callCount: 1,
|
|
@@ -1565,8 +1804,8 @@ function stitchTrace(graph, sourceServiceId, ts) {
|
|
|
1565
1804
|
const outbound = graph.outboundEdges(nodeId);
|
|
1566
1805
|
for (const edgeId of outbound) {
|
|
1567
1806
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
1568
|
-
if (edge.provenance !==
|
|
1569
|
-
if (graph.hasEdge((0,
|
|
1807
|
+
if (edge.provenance !== import_types4.Provenance.EXTRACTED) continue;
|
|
1808
|
+
if (graph.hasEdge((0, import_types4.observedEdgeId)(edge.source, edge.target, edge.type))) continue;
|
|
1570
1809
|
upsertInferredEdge(graph, edge.type, edge.source, edge.target, ts);
|
|
1571
1810
|
if (!visited.has(edge.target)) {
|
|
1572
1811
|
visited.add(edge.target);
|
|
@@ -1588,7 +1827,7 @@ function upsertInferredEdge(graph, type, source, target, ts) {
|
|
|
1588
1827
|
source,
|
|
1589
1828
|
target,
|
|
1590
1829
|
type,
|
|
1591
|
-
provenance:
|
|
1830
|
+
provenance: import_types4.Provenance.INFERRED,
|
|
1592
1831
|
confidence: INFERRED_CONFIDENCE,
|
|
1593
1832
|
lastObserved: ts
|
|
1594
1833
|
};
|
|
@@ -1609,10 +1848,10 @@ async function handleSpan(ctx, span) {
|
|
|
1609
1848
|
const host = pickAddress(span);
|
|
1610
1849
|
if (host) {
|
|
1611
1850
|
ensureDatabaseNode(ctx.graph, host, span.dbSystem);
|
|
1612
|
-
const targetId = (0,
|
|
1851
|
+
const targetId = (0, import_types4.databaseId)(host);
|
|
1613
1852
|
const result = upsertObservedEdge(
|
|
1614
1853
|
ctx.graph,
|
|
1615
|
-
|
|
1854
|
+
import_types4.EdgeType.CONNECTS_TO,
|
|
1616
1855
|
sourceId,
|
|
1617
1856
|
targetId,
|
|
1618
1857
|
ts,
|
|
@@ -1628,7 +1867,7 @@ async function handleSpan(ctx, span) {
|
|
|
1628
1867
|
if (targetId && targetId !== sourceId) {
|
|
1629
1868
|
upsertObservedEdge(
|
|
1630
1869
|
ctx.graph,
|
|
1631
|
-
|
|
1870
|
+
import_types4.EdgeType.CALLS,
|
|
1632
1871
|
sourceId,
|
|
1633
1872
|
targetId,
|
|
1634
1873
|
ts,
|
|
@@ -1639,7 +1878,7 @@ async function handleSpan(ctx, span) {
|
|
|
1639
1878
|
} else if (!targetId) {
|
|
1640
1879
|
const frontierId2 = ensureFrontierNode(ctx.graph, host, ts);
|
|
1641
1880
|
if (ctx.graph.hasNode(sourceId)) {
|
|
1642
|
-
upsertFrontierEdge(ctx.graph,
|
|
1881
|
+
upsertFrontierEdge(ctx.graph, import_types4.EdgeType.CALLS, sourceId, frontierId2, ts);
|
|
1643
1882
|
}
|
|
1644
1883
|
affectedNode = frontierId2;
|
|
1645
1884
|
resolvedViaAddress = true;
|
|
@@ -1651,7 +1890,7 @@ async function handleSpan(ctx, span) {
|
|
|
1651
1890
|
const parentId = ensureServiceNode(ctx.graph, parentService);
|
|
1652
1891
|
upsertObservedEdge(
|
|
1653
1892
|
ctx.graph,
|
|
1654
|
-
|
|
1893
|
+
import_types4.EdgeType.CALLS,
|
|
1655
1894
|
parentId,
|
|
1656
1895
|
sourceId,
|
|
1657
1896
|
ts,
|
|
@@ -1684,7 +1923,7 @@ function promoteFrontierNodes(graph, opts = {}) {
|
|
|
1684
1923
|
const aliasIndex = /* @__PURE__ */ new Map();
|
|
1685
1924
|
graph.forEachNode((id, attrs) => {
|
|
1686
1925
|
const a = attrs;
|
|
1687
|
-
if (a.type !==
|
|
1926
|
+
if (a.type !== import_types4.NodeType.ServiceNode) return;
|
|
1688
1927
|
aliasIndex.set(a.name, id);
|
|
1689
1928
|
if (a.aliases) {
|
|
1690
1929
|
for (const alias of a.aliases) aliasIndex.set(alias, id);
|
|
@@ -1693,7 +1932,7 @@ function promoteFrontierNodes(graph, opts = {}) {
|
|
|
1693
1932
|
const toPromote = [];
|
|
1694
1933
|
graph.forEachNode((id, attrs) => {
|
|
1695
1934
|
const a = attrs;
|
|
1696
|
-
if (a.type !==
|
|
1935
|
+
if (a.type !== import_types4.NodeType.FrontierNode) return;
|
|
1697
1936
|
const target = aliasIndex.get(a.host);
|
|
1698
1937
|
if (!target) return;
|
|
1699
1938
|
if (target === id) return;
|
|
@@ -1727,8 +1966,8 @@ function rewireFrontierEdges(graph, frontierId2, serviceId3) {
|
|
|
1727
1966
|
}
|
|
1728
1967
|
function rebuildEdge(graph, edge, newSource, newTarget, oldEdgeId) {
|
|
1729
1968
|
graph.dropEdge(oldEdgeId);
|
|
1730
|
-
const promotedProvenance = edge.provenance ===
|
|
1731
|
-
const newId = promotedProvenance ===
|
|
1969
|
+
const promotedProvenance = edge.provenance === import_types4.Provenance.FRONTIER ? import_types4.Provenance.OBSERVED : edge.provenance;
|
|
1970
|
+
const newId = promotedProvenance === import_types4.Provenance.OBSERVED ? (0, import_types4.observedEdgeId)(newSource, newTarget, edge.type) : promotedProvenance === import_types4.Provenance.INFERRED ? (0, import_types4.inferredEdgeId)(newSource, newTarget, edge.type) : promotedProvenance === import_types4.Provenance.EXTRACTED ? (0, import_types4.extractedEdgeId)(newSource, newTarget, edge.type) : (0, import_types4.frontierEdgeId)(newSource, newTarget, edge.type);
|
|
1732
1971
|
if (graph.hasEdge(newId)) {
|
|
1733
1972
|
const existing = graph.getEdgeAttributes(newId);
|
|
1734
1973
|
const merged = {
|
|
@@ -1763,12 +2002,12 @@ async function markStaleEdges(graph, options = {}) {
|
|
|
1763
2002
|
const project = options.project ?? DEFAULT_PROJECT;
|
|
1764
2003
|
graph.forEachEdge((id, attrs) => {
|
|
1765
2004
|
const e = attrs;
|
|
1766
|
-
if (e.provenance !==
|
|
2005
|
+
if (e.provenance !== import_types4.Provenance.OBSERVED) return;
|
|
1767
2006
|
if (!e.lastObserved) return;
|
|
1768
2007
|
const threshold = thresholdForEdgeType(e.type, thresholds);
|
|
1769
2008
|
const age = now - new Date(e.lastObserved).getTime();
|
|
1770
2009
|
if (age > threshold) {
|
|
1771
|
-
const updated = { ...e, provenance:
|
|
2010
|
+
const updated = { ...e, provenance: import_types4.Provenance.STALE, confidence: 0.3 };
|
|
1772
2011
|
graph.replaceEdgeAttributes(id, updated);
|
|
1773
2012
|
events.push({
|
|
1774
2013
|
edgeId: id,
|
|
@@ -1785,8 +2024,8 @@ async function markStaleEdges(graph, options = {}) {
|
|
|
1785
2024
|
project,
|
|
1786
2025
|
payload: {
|
|
1787
2026
|
edgeId: id,
|
|
1788
|
-
from:
|
|
1789
|
-
to:
|
|
2027
|
+
from: import_types4.Provenance.OBSERVED,
|
|
2028
|
+
to: import_types4.Provenance.STALE
|
|
1790
2029
|
}
|
|
1791
2030
|
});
|
|
1792
2031
|
}
|
|
@@ -1847,18 +2086,18 @@ async function readErrorEvents(errorsPath) {
|
|
|
1847
2086
|
|
|
1848
2087
|
// src/extract/services.ts
|
|
1849
2088
|
init_cjs_shims();
|
|
1850
|
-
var
|
|
1851
|
-
var
|
|
2089
|
+
var import_node_fs7 = require("fs");
|
|
2090
|
+
var import_node_path7 = __toESM(require("path"), 1);
|
|
1852
2091
|
var import_ignore = __toESM(require("ignore"), 1);
|
|
1853
|
-
var
|
|
1854
|
-
var
|
|
2092
|
+
var import_minimatch2 = require("minimatch");
|
|
2093
|
+
var import_types6 = require("@neat.is/types");
|
|
1855
2094
|
|
|
1856
2095
|
// src/extract/shared.ts
|
|
1857
2096
|
init_cjs_shims();
|
|
1858
2097
|
var import_node_fs4 = require("fs");
|
|
1859
2098
|
var import_node_path4 = __toESM(require("path"), 1);
|
|
1860
2099
|
var import_yaml = require("yaml");
|
|
1861
|
-
var
|
|
2100
|
+
var import_types5 = require("@neat.is/types");
|
|
1862
2101
|
var SERVICE_FILE_EXTENSIONS = /* @__PURE__ */ new Set([".js", ".mjs", ".cjs", ".ts", ".tsx", ".py"]);
|
|
1863
2102
|
var CONFIG_FILE_EXTENSIONS = /* @__PURE__ */ new Set([".yaml", ".yml"]);
|
|
1864
2103
|
var IGNORED_DIRS = /* @__PURE__ */ new Set([
|
|
@@ -1963,6 +2202,73 @@ function pythonToPackage(service) {
|
|
|
1963
2202
|
};
|
|
1964
2203
|
}
|
|
1965
2204
|
|
|
2205
|
+
// src/extract/owners.ts
|
|
2206
|
+
init_cjs_shims();
|
|
2207
|
+
var import_node_fs6 = require("fs");
|
|
2208
|
+
var import_node_path6 = __toESM(require("path"), 1);
|
|
2209
|
+
var import_minimatch = require("minimatch");
|
|
2210
|
+
async function loadCodeowners(scanPath) {
|
|
2211
|
+
const candidates = [
|
|
2212
|
+
import_node_path6.default.join(scanPath, "CODEOWNERS"),
|
|
2213
|
+
import_node_path6.default.join(scanPath, ".github", "CODEOWNERS")
|
|
2214
|
+
];
|
|
2215
|
+
for (const file of candidates) {
|
|
2216
|
+
if (await exists(file)) {
|
|
2217
|
+
const raw = await import_node_fs6.promises.readFile(file, "utf8");
|
|
2218
|
+
return parseCodeowners(raw);
|
|
2219
|
+
}
|
|
2220
|
+
}
|
|
2221
|
+
return null;
|
|
2222
|
+
}
|
|
2223
|
+
function parseCodeowners(raw) {
|
|
2224
|
+
const rules = [];
|
|
2225
|
+
for (const line of raw.split("\n")) {
|
|
2226
|
+
const trimmed = line.trim();
|
|
2227
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
2228
|
+
const match = /^(\S+)\s+(.+)$/.exec(trimmed);
|
|
2229
|
+
if (!match) continue;
|
|
2230
|
+
rules.push({ pattern: match[1], owners: match[2].trim() });
|
|
2231
|
+
}
|
|
2232
|
+
return { rules };
|
|
2233
|
+
}
|
|
2234
|
+
function matchOwner(file, repoPath) {
|
|
2235
|
+
const normalized = repoPath.split(import_node_path6.default.sep).join("/");
|
|
2236
|
+
for (const rule of file.rules) {
|
|
2237
|
+
if (matchesPattern(rule.pattern, normalized)) return rule.owners;
|
|
2238
|
+
}
|
|
2239
|
+
return null;
|
|
2240
|
+
}
|
|
2241
|
+
function matchesPattern(rawPattern, repoPath) {
|
|
2242
|
+
let pattern = rawPattern.startsWith("/") ? rawPattern.slice(1) : rawPattern;
|
|
2243
|
+
if (pattern === "*") return !repoPath.includes("/");
|
|
2244
|
+
if (pattern === "**" || pattern === "") return true;
|
|
2245
|
+
if (pattern.endsWith("/")) pattern = pattern + "**";
|
|
2246
|
+
if ((0, import_minimatch.minimatch)(repoPath, pattern, { dot: true })) return true;
|
|
2247
|
+
if (!pattern.includes("*") && (0, import_minimatch.minimatch)(repoPath, pattern + "/**", { dot: true })) return true;
|
|
2248
|
+
return false;
|
|
2249
|
+
}
|
|
2250
|
+
async function readPackageJsonAuthor(serviceDir) {
|
|
2251
|
+
const pkgPath = import_node_path6.default.join(serviceDir, "package.json");
|
|
2252
|
+
if (!await exists(pkgPath)) return null;
|
|
2253
|
+
try {
|
|
2254
|
+
const pkg = await readJson(pkgPath);
|
|
2255
|
+
if (!pkg.author) return null;
|
|
2256
|
+
if (typeof pkg.author === "string") return pkg.author;
|
|
2257
|
+
if (typeof pkg.author === "object" && typeof pkg.author.name === "string") return pkg.author.name;
|
|
2258
|
+
return null;
|
|
2259
|
+
} catch {
|
|
2260
|
+
return null;
|
|
2261
|
+
}
|
|
2262
|
+
}
|
|
2263
|
+
async function computeServiceOwner(codeowners, repoPath, serviceDir) {
|
|
2264
|
+
if (codeowners && repoPath !== void 0) {
|
|
2265
|
+
const owner = matchOwner(codeowners, repoPath);
|
|
2266
|
+
if (owner) return owner;
|
|
2267
|
+
}
|
|
2268
|
+
const author = await readPackageJsonAuthor(serviceDir);
|
|
2269
|
+
return author ?? void 0;
|
|
2270
|
+
}
|
|
2271
|
+
|
|
1966
2272
|
// src/extract/services.ts
|
|
1967
2273
|
var DEFAULT_SCAN_DEPTH = 5;
|
|
1968
2274
|
function parseScanDepth() {
|
|
@@ -1979,21 +2285,21 @@ function workspaceGlobs(pkg) {
|
|
|
1979
2285
|
return null;
|
|
1980
2286
|
}
|
|
1981
2287
|
async function loadGitignore(scanPath) {
|
|
1982
|
-
const gitignorePath =
|
|
2288
|
+
const gitignorePath = import_node_path7.default.join(scanPath, ".gitignore");
|
|
1983
2289
|
if (!await exists(gitignorePath)) return null;
|
|
1984
|
-
const raw = await
|
|
2290
|
+
const raw = await import_node_fs7.promises.readFile(gitignorePath, "utf8");
|
|
1985
2291
|
return (0, import_ignore.default)().add(raw);
|
|
1986
2292
|
}
|
|
1987
2293
|
async function walkDirs(start, scanPath, options, visit) {
|
|
1988
2294
|
async function recurse(current, depth) {
|
|
1989
2295
|
if (depth > options.maxDepth) return;
|
|
1990
|
-
const entries = await
|
|
2296
|
+
const entries = await import_node_fs7.promises.readdir(current, { withFileTypes: true }).catch(() => []);
|
|
1991
2297
|
for (const entry of entries) {
|
|
1992
2298
|
if (!entry.isDirectory()) continue;
|
|
1993
2299
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
1994
|
-
const child =
|
|
2300
|
+
const child = import_node_path7.default.join(current, entry.name);
|
|
1995
2301
|
if (options.ig) {
|
|
1996
|
-
const rel =
|
|
2302
|
+
const rel = import_node_path7.default.relative(scanPath, child).split(import_node_path7.default.sep).join("/");
|
|
1997
2303
|
if (rel && options.ig.ignores(rel + "/")) continue;
|
|
1998
2304
|
}
|
|
1999
2305
|
await visit(child);
|
|
@@ -2008,8 +2314,8 @@ async function expandWorkspaceGlobs(scanPath, globs) {
|
|
|
2008
2314
|
for (const raw of globs) {
|
|
2009
2315
|
const pattern = raw.replace(/^\.\//, "");
|
|
2010
2316
|
if (!pattern.includes("*")) {
|
|
2011
|
-
const candidate =
|
|
2012
|
-
if (await exists(
|
|
2317
|
+
const candidate = import_node_path7.default.join(scanPath, pattern);
|
|
2318
|
+
if (await exists(import_node_path7.default.join(candidate, "package.json"))) found.add(candidate);
|
|
2013
2319
|
continue;
|
|
2014
2320
|
}
|
|
2015
2321
|
const segments = pattern.split("/");
|
|
@@ -2018,13 +2324,13 @@ async function expandWorkspaceGlobs(scanPath, globs) {
|
|
|
2018
2324
|
if (seg.includes("*")) break;
|
|
2019
2325
|
staticSegments.push(seg);
|
|
2020
2326
|
}
|
|
2021
|
-
const start =
|
|
2327
|
+
const start = import_node_path7.default.join(scanPath, ...staticSegments);
|
|
2022
2328
|
if (!await exists(start)) continue;
|
|
2023
2329
|
const hasDoubleStar = pattern.includes("**");
|
|
2024
2330
|
const walkDepth = hasDoubleStar ? scanDepth : Math.max(0, segments.length - staticSegments.length - 1);
|
|
2025
2331
|
await walkDirs(start, scanPath, { maxDepth: walkDepth, ig: null }, async (dir) => {
|
|
2026
|
-
const rel =
|
|
2027
|
-
if ((0,
|
|
2332
|
+
const rel = import_node_path7.default.relative(scanPath, dir).split(import_node_path7.default.sep).join("/");
|
|
2333
|
+
if ((0, import_minimatch2.minimatch)(rel, pattern) && await exists(import_node_path7.default.join(dir, "package.json"))) {
|
|
2028
2334
|
found.add(dir);
|
|
2029
2335
|
}
|
|
2030
2336
|
});
|
|
@@ -2032,18 +2338,26 @@ async function expandWorkspaceGlobs(scanPath, globs) {
|
|
|
2032
2338
|
return [...found];
|
|
2033
2339
|
}
|
|
2034
2340
|
async function discoverNodeService(scanPath, dir) {
|
|
2035
|
-
const pkgPath =
|
|
2341
|
+
const pkgPath = import_node_path7.default.join(dir, "package.json");
|
|
2036
2342
|
if (!await exists(pkgPath)) return null;
|
|
2037
|
-
|
|
2343
|
+
let pkg;
|
|
2344
|
+
try {
|
|
2345
|
+
pkg = await readJson(pkgPath);
|
|
2346
|
+
} catch (err) {
|
|
2347
|
+
console.warn(
|
|
2348
|
+
`[neat] services skipped ${import_node_path7.default.relative(scanPath, pkgPath)}: ${err.message}`
|
|
2349
|
+
);
|
|
2350
|
+
return null;
|
|
2351
|
+
}
|
|
2038
2352
|
if (!pkg.name) return null;
|
|
2039
2353
|
const node = {
|
|
2040
|
-
id: (0,
|
|
2041
|
-
type:
|
|
2354
|
+
id: (0, import_types6.serviceId)(pkg.name),
|
|
2355
|
+
type: import_types6.NodeType.ServiceNode,
|
|
2042
2356
|
name: pkg.name,
|
|
2043
2357
|
language: "javascript",
|
|
2044
2358
|
version: pkg.version,
|
|
2045
2359
|
dependencies: pkg.dependencies ?? {},
|
|
2046
|
-
repoPath:
|
|
2360
|
+
repoPath: import_node_path7.default.relative(scanPath, dir),
|
|
2047
2361
|
...pkg.engines?.node ? { nodeEngine: pkg.engines.node } : {}
|
|
2048
2362
|
};
|
|
2049
2363
|
return { pkg, dir, node };
|
|
@@ -2053,19 +2367,28 @@ async function discoverPyService(scanPath, dir) {
|
|
|
2053
2367
|
if (!py) return null;
|
|
2054
2368
|
const pkg = pythonToPackage(py);
|
|
2055
2369
|
const node = {
|
|
2056
|
-
id: (0,
|
|
2057
|
-
type:
|
|
2370
|
+
id: (0, import_types6.serviceId)(py.name),
|
|
2371
|
+
type: import_types6.NodeType.ServiceNode,
|
|
2058
2372
|
name: py.name,
|
|
2059
2373
|
language: "python",
|
|
2060
2374
|
version: py.version,
|
|
2061
2375
|
dependencies: py.dependencies,
|
|
2062
|
-
repoPath:
|
|
2376
|
+
repoPath: import_node_path7.default.relative(scanPath, dir)
|
|
2063
2377
|
};
|
|
2064
2378
|
return { pkg, dir, node };
|
|
2065
2379
|
}
|
|
2066
2380
|
async function discoverServices(scanPath) {
|
|
2067
|
-
const rootPkgPath =
|
|
2068
|
-
|
|
2381
|
+
const rootPkgPath = import_node_path7.default.join(scanPath, "package.json");
|
|
2382
|
+
let rootPkg = null;
|
|
2383
|
+
if (await exists(rootPkgPath)) {
|
|
2384
|
+
try {
|
|
2385
|
+
rootPkg = await readJson(rootPkgPath);
|
|
2386
|
+
} catch (err) {
|
|
2387
|
+
console.warn(
|
|
2388
|
+
`[neat] services workspaces skipped ${import_node_path7.default.relative(scanPath, rootPkgPath)}: ${err.message}`
|
|
2389
|
+
);
|
|
2390
|
+
}
|
|
2391
|
+
}
|
|
2069
2392
|
const wsGlobs = rootPkg ? workspaceGlobs(rootPkg) : null;
|
|
2070
2393
|
const candidateDirs = [];
|
|
2071
2394
|
if (wsGlobs) {
|
|
@@ -2078,9 +2401,9 @@ async function discoverServices(scanPath) {
|
|
|
2078
2401
|
scanPath,
|
|
2079
2402
|
{ maxDepth: parseScanDepth(), ig },
|
|
2080
2403
|
async (dir) => {
|
|
2081
|
-
if (await exists(
|
|
2404
|
+
if (await exists(import_node_path7.default.join(dir, "package.json"))) {
|
|
2082
2405
|
candidateDirs.push(dir);
|
|
2083
|
-
} else if (await exists(
|
|
2406
|
+
} else if (await exists(import_node_path7.default.join(dir, "pyproject.toml")) || await exists(import_node_path7.default.join(dir, "requirements.txt")) || await exists(import_node_path7.default.join(dir, "setup.py"))) {
|
|
2084
2407
|
candidateDirs.push(dir);
|
|
2085
2408
|
}
|
|
2086
2409
|
}
|
|
@@ -2094,8 +2417,8 @@ async function discoverServices(scanPath) {
|
|
|
2094
2417
|
if (!service) continue;
|
|
2095
2418
|
const existingDir = seen.get(service.node.name);
|
|
2096
2419
|
if (existingDir !== void 0) {
|
|
2097
|
-
const a =
|
|
2098
|
-
const b =
|
|
2420
|
+
const a = import_node_path7.default.relative(scanPath, existingDir) || ".";
|
|
2421
|
+
const b = import_node_path7.default.relative(scanPath, dir) || ".";
|
|
2099
2422
|
console.warn(
|
|
2100
2423
|
`[neat] duplicate package name "${service.node.name}" \u2014 keeping ${a}, ignoring ${b}`
|
|
2101
2424
|
);
|
|
@@ -2104,6 +2427,11 @@ async function discoverServices(scanPath) {
|
|
|
2104
2427
|
seen.set(service.node.name, dir);
|
|
2105
2428
|
out.push(service);
|
|
2106
2429
|
}
|
|
2430
|
+
const codeowners = await loadCodeowners(scanPath);
|
|
2431
|
+
for (const service of out) {
|
|
2432
|
+
const owner = await computeServiceOwner(codeowners, service.node.repoPath, service.dir);
|
|
2433
|
+
if (owner !== void 0) service.node.owner = owner;
|
|
2434
|
+
}
|
|
2107
2435
|
return out;
|
|
2108
2436
|
}
|
|
2109
2437
|
function addServiceNodes(graph, services) {
|
|
@@ -2127,10 +2455,10 @@ function addServiceNodes(graph, services) {
|
|
|
2127
2455
|
|
|
2128
2456
|
// src/extract/aliases.ts
|
|
2129
2457
|
init_cjs_shims();
|
|
2130
|
-
var
|
|
2131
|
-
var
|
|
2458
|
+
var import_node_path8 = __toESM(require("path"), 1);
|
|
2459
|
+
var import_node_fs8 = require("fs");
|
|
2132
2460
|
var import_yaml2 = require("yaml");
|
|
2133
|
-
var
|
|
2461
|
+
var import_types7 = require("@neat.is/types");
|
|
2134
2462
|
var K8S_KINDS_WITH_HOSTNAMES = /* @__PURE__ */ new Set([
|
|
2135
2463
|
"Service",
|
|
2136
2464
|
"Deployment",
|
|
@@ -2140,7 +2468,7 @@ var K8S_KINDS_WITH_HOSTNAMES = /* @__PURE__ */ new Set([
|
|
|
2140
2468
|
function addAliases(graph, serviceId3, candidates) {
|
|
2141
2469
|
if (!graph.hasNode(serviceId3)) return;
|
|
2142
2470
|
const node = graph.getNodeAttributes(serviceId3);
|
|
2143
|
-
if (node.type !==
|
|
2471
|
+
if (node.type !== import_types7.NodeType.ServiceNode) return;
|
|
2144
2472
|
const set = new Set(node.aliases ?? []);
|
|
2145
2473
|
for (const c of candidates) {
|
|
2146
2474
|
if (!c) continue;
|
|
@@ -2155,21 +2483,29 @@ function indexServicesByName(services) {
|
|
|
2155
2483
|
const map = /* @__PURE__ */ new Map();
|
|
2156
2484
|
for (const s of services) {
|
|
2157
2485
|
map.set(s.node.name, s.node.id);
|
|
2158
|
-
map.set(
|
|
2486
|
+
map.set(import_node_path8.default.basename(s.dir), s.node.id);
|
|
2159
2487
|
}
|
|
2160
2488
|
return map;
|
|
2161
2489
|
}
|
|
2162
2490
|
async function collectComposeAliases(graph, scanPath, serviceIndex) {
|
|
2163
2491
|
let composePath = null;
|
|
2164
2492
|
for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
|
|
2165
|
-
const abs =
|
|
2493
|
+
const abs = import_node_path8.default.join(scanPath, name);
|
|
2166
2494
|
if (await exists(abs)) {
|
|
2167
2495
|
composePath = abs;
|
|
2168
2496
|
break;
|
|
2169
2497
|
}
|
|
2170
2498
|
}
|
|
2171
2499
|
if (!composePath) return;
|
|
2172
|
-
|
|
2500
|
+
let compose;
|
|
2501
|
+
try {
|
|
2502
|
+
compose = await readYaml(composePath);
|
|
2503
|
+
} catch (err) {
|
|
2504
|
+
console.warn(
|
|
2505
|
+
`[neat] aliases compose skipped ${import_node_path8.default.relative(scanPath, composePath)}: ${err.message}`
|
|
2506
|
+
);
|
|
2507
|
+
return;
|
|
2508
|
+
}
|
|
2173
2509
|
if (!compose?.services) return;
|
|
2174
2510
|
for (const [composeName, svc] of Object.entries(compose.services)) {
|
|
2175
2511
|
const serviceId3 = serviceIndex.get(composeName);
|
|
@@ -2208,9 +2544,17 @@ function parseDockerfileLabels(content) {
|
|
|
2208
2544
|
}
|
|
2209
2545
|
async function collectDockerfileAliases(graph, services) {
|
|
2210
2546
|
for (const service of services) {
|
|
2211
|
-
const dockerfilePath =
|
|
2547
|
+
const dockerfilePath = import_node_path8.default.join(service.dir, "Dockerfile");
|
|
2212
2548
|
if (!await exists(dockerfilePath)) continue;
|
|
2213
|
-
|
|
2549
|
+
let content;
|
|
2550
|
+
try {
|
|
2551
|
+
content = await import_node_fs8.promises.readFile(dockerfilePath, "utf8");
|
|
2552
|
+
} catch (err) {
|
|
2553
|
+
console.warn(
|
|
2554
|
+
`[neat] aliases dockerfile skipped ${dockerfilePath}: ${err.message}`
|
|
2555
|
+
);
|
|
2556
|
+
continue;
|
|
2557
|
+
}
|
|
2214
2558
|
const aliases = parseDockerfileLabels(content);
|
|
2215
2559
|
if (aliases.length > 0) addAliases(graph, service.node.id, aliases);
|
|
2216
2560
|
}
|
|
@@ -2218,13 +2562,13 @@ async function collectDockerfileAliases(graph, services) {
|
|
|
2218
2562
|
async function walkYamlFiles(start, depth = 0, max = 5) {
|
|
2219
2563
|
if (depth > max) return [];
|
|
2220
2564
|
const out = [];
|
|
2221
|
-
const entries = await
|
|
2565
|
+
const entries = await import_node_fs8.promises.readdir(start, { withFileTypes: true }).catch(() => []);
|
|
2222
2566
|
for (const entry of entries) {
|
|
2223
2567
|
if (entry.isDirectory()) {
|
|
2224
2568
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
2225
|
-
out.push(...await walkYamlFiles(
|
|
2226
|
-
} else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(
|
|
2227
|
-
out.push(
|
|
2569
|
+
out.push(...await walkYamlFiles(import_node_path8.default.join(start, entry.name), depth + 1, max));
|
|
2570
|
+
} else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path8.default.extname(entry.name))) {
|
|
2571
|
+
out.push(import_node_path8.default.join(start, entry.name));
|
|
2228
2572
|
}
|
|
2229
2573
|
}
|
|
2230
2574
|
return out;
|
|
@@ -2251,7 +2595,7 @@ function k8sServiceTarget(doc, byName) {
|
|
|
2251
2595
|
async function collectK8sAliases(graph, scanPath, serviceIndex) {
|
|
2252
2596
|
const files = await walkYamlFiles(scanPath);
|
|
2253
2597
|
for (const file of files) {
|
|
2254
|
-
const content = await
|
|
2598
|
+
const content = await import_node_fs8.promises.readFile(file, "utf8");
|
|
2255
2599
|
let docs;
|
|
2256
2600
|
try {
|
|
2257
2601
|
docs = (0, import_yaml2.parseAllDocuments)(content).map((d) => d.toJSON());
|
|
@@ -2276,14 +2620,14 @@ async function addServiceAliases(graph, scanPath, services) {
|
|
|
2276
2620
|
|
|
2277
2621
|
// src/extract/databases/index.ts
|
|
2278
2622
|
init_cjs_shims();
|
|
2279
|
-
var
|
|
2280
|
-
var
|
|
2623
|
+
var import_node_path16 = __toESM(require("path"), 1);
|
|
2624
|
+
var import_types8 = require("@neat.is/types");
|
|
2281
2625
|
|
|
2282
2626
|
// src/extract/databases/db-config-yaml.ts
|
|
2283
2627
|
init_cjs_shims();
|
|
2284
|
-
var
|
|
2628
|
+
var import_node_path9 = __toESM(require("path"), 1);
|
|
2285
2629
|
async function parse(serviceDir) {
|
|
2286
|
-
const yamlPath =
|
|
2630
|
+
const yamlPath = import_node_path9.default.join(serviceDir, "db-config.yaml");
|
|
2287
2631
|
if (!await exists(yamlPath)) return [];
|
|
2288
2632
|
const raw = await readYaml(yamlPath);
|
|
2289
2633
|
return [
|
|
@@ -2301,13 +2645,13 @@ var dbConfigYamlParser = { name: "db-config.yaml", parse };
|
|
|
2301
2645
|
|
|
2302
2646
|
// src/extract/databases/dotenv.ts
|
|
2303
2647
|
init_cjs_shims();
|
|
2304
|
-
var
|
|
2305
|
-
var
|
|
2648
|
+
var import_node_fs10 = require("fs");
|
|
2649
|
+
var import_node_path11 = __toESM(require("path"), 1);
|
|
2306
2650
|
|
|
2307
2651
|
// src/extract/databases/shared.ts
|
|
2308
2652
|
init_cjs_shims();
|
|
2309
|
-
var
|
|
2310
|
-
var
|
|
2653
|
+
var import_node_fs9 = require("fs");
|
|
2654
|
+
var import_node_path10 = __toESM(require("path"), 1);
|
|
2311
2655
|
function schemeToEngine(scheme) {
|
|
2312
2656
|
const s = scheme.toLowerCase().split("+")[0];
|
|
2313
2657
|
switch (s) {
|
|
@@ -2346,14 +2690,14 @@ function parseConnectionString(url) {
|
|
|
2346
2690
|
}
|
|
2347
2691
|
async function readIfExists(filePath) {
|
|
2348
2692
|
try {
|
|
2349
|
-
return await
|
|
2693
|
+
return await import_node_fs9.promises.readFile(filePath, "utf8");
|
|
2350
2694
|
} catch {
|
|
2351
2695
|
return null;
|
|
2352
2696
|
}
|
|
2353
2697
|
}
|
|
2354
2698
|
async function findFirst(serviceDir, candidates) {
|
|
2355
2699
|
for (const rel of candidates) {
|
|
2356
|
-
const abs =
|
|
2700
|
+
const abs = import_node_path10.default.join(serviceDir, rel);
|
|
2357
2701
|
const content = await readIfExists(abs);
|
|
2358
2702
|
if (content !== null) return abs;
|
|
2359
2703
|
}
|
|
@@ -2404,15 +2748,15 @@ function parseDotenvLine(line) {
|
|
|
2404
2748
|
return { key, value };
|
|
2405
2749
|
}
|
|
2406
2750
|
async function parse2(serviceDir) {
|
|
2407
|
-
const entries = await
|
|
2751
|
+
const entries = await import_node_fs10.promises.readdir(serviceDir, { withFileTypes: true }).catch(() => []);
|
|
2408
2752
|
const configs = [];
|
|
2409
2753
|
const seen = /* @__PURE__ */ new Set();
|
|
2410
2754
|
for (const entry of entries) {
|
|
2411
2755
|
if (!entry.isFile()) continue;
|
|
2412
2756
|
const match = isConfigFile(entry.name);
|
|
2413
2757
|
if (!match.match || match.fileType !== "env") continue;
|
|
2414
|
-
const filePath =
|
|
2415
|
-
const content = await
|
|
2758
|
+
const filePath = import_node_path11.default.join(serviceDir, entry.name);
|
|
2759
|
+
const content = await import_node_fs10.promises.readFile(filePath, "utf8");
|
|
2416
2760
|
for (const line of content.split("\n")) {
|
|
2417
2761
|
const parsed = parseDotenvLine(line);
|
|
2418
2762
|
if (!parsed) continue;
|
|
@@ -2431,9 +2775,9 @@ var dotenvParser = { name: ".env", parse: parse2 };
|
|
|
2431
2775
|
|
|
2432
2776
|
// src/extract/databases/prisma.ts
|
|
2433
2777
|
init_cjs_shims();
|
|
2434
|
-
var
|
|
2778
|
+
var import_node_path12 = __toESM(require("path"), 1);
|
|
2435
2779
|
async function parse3(serviceDir) {
|
|
2436
|
-
const schemaPath =
|
|
2780
|
+
const schemaPath = import_node_path12.default.join(serviceDir, "prisma", "schema.prisma");
|
|
2437
2781
|
const content = await readIfExists(schemaPath);
|
|
2438
2782
|
if (!content) return [];
|
|
2439
2783
|
const block = content.match(/datasource\s+\w+\s*\{([^}]*)\}/s);
|
|
@@ -2565,10 +2909,10 @@ var knexParser = { name: "knex", parse: parse5 };
|
|
|
2565
2909
|
|
|
2566
2910
|
// src/extract/databases/ormconfig.ts
|
|
2567
2911
|
init_cjs_shims();
|
|
2568
|
-
var
|
|
2912
|
+
var import_node_path13 = __toESM(require("path"), 1);
|
|
2569
2913
|
async function parse6(serviceDir) {
|
|
2570
2914
|
for (const candidate of ["ormconfig.json", "ormconfig.yaml", "ormconfig.yml"]) {
|
|
2571
|
-
const abs =
|
|
2915
|
+
const abs = import_node_path13.default.join(serviceDir, candidate);
|
|
2572
2916
|
if (!await exists(abs)) continue;
|
|
2573
2917
|
const raw = candidate.endsWith(".json") ? await readJson(abs) : await readYaml(abs);
|
|
2574
2918
|
const entries = Array.isArray(raw) ? raw : [raw];
|
|
@@ -2628,9 +2972,9 @@ var typeormParser = { name: "typeorm", parse: parse7 };
|
|
|
2628
2972
|
|
|
2629
2973
|
// src/extract/databases/sequelize.ts
|
|
2630
2974
|
init_cjs_shims();
|
|
2631
|
-
var
|
|
2975
|
+
var import_node_path14 = __toESM(require("path"), 1);
|
|
2632
2976
|
async function parse8(serviceDir) {
|
|
2633
|
-
const configPath =
|
|
2977
|
+
const configPath = import_node_path14.default.join(serviceDir, "config", "config.json");
|
|
2634
2978
|
if (!await exists(configPath)) return [];
|
|
2635
2979
|
const raw = await readJson(configPath);
|
|
2636
2980
|
const out = [];
|
|
@@ -2657,7 +3001,7 @@ var sequelizeParser = { name: "sequelize", parse: parse8 };
|
|
|
2657
3001
|
|
|
2658
3002
|
// src/extract/databases/docker-compose.ts
|
|
2659
3003
|
init_cjs_shims();
|
|
2660
|
-
var
|
|
3004
|
+
var import_node_path15 = __toESM(require("path"), 1);
|
|
2661
3005
|
function portFromService(svc) {
|
|
2662
3006
|
for (const raw of svc.ports ?? []) {
|
|
2663
3007
|
const str = String(raw);
|
|
@@ -2684,7 +3028,7 @@ function databaseFromEnv(svc) {
|
|
|
2684
3028
|
}
|
|
2685
3029
|
async function parse9(serviceDir) {
|
|
2686
3030
|
for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
|
|
2687
|
-
const abs =
|
|
3031
|
+
const abs = import_node_path15.default.join(serviceDir, name);
|
|
2688
3032
|
if (!await exists(abs)) continue;
|
|
2689
3033
|
const raw = await readYaml(abs);
|
|
2690
3034
|
if (!raw?.services) return [];
|
|
@@ -2725,8 +3069,8 @@ function compatibleDriversFor(engine) {
|
|
|
2725
3069
|
}
|
|
2726
3070
|
function toDatabaseNode(config) {
|
|
2727
3071
|
return {
|
|
2728
|
-
id: (0,
|
|
2729
|
-
type:
|
|
3072
|
+
id: (0, import_types8.databaseId)(config.host),
|
|
3073
|
+
type: import_types8.NodeType.DatabaseNode,
|
|
2730
3074
|
name: config.database || config.host,
|
|
2731
3075
|
engine: config.engine,
|
|
2732
3076
|
engineVersion: config.engineVersion,
|
|
@@ -2857,14 +3201,14 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
|
|
|
2857
3201
|
});
|
|
2858
3202
|
}
|
|
2859
3203
|
const edge = {
|
|
2860
|
-
id: (0,
|
|
3204
|
+
id: (0, import_types5.extractedEdgeId)(service.node.id, dbNode.id, import_types8.EdgeType.CONNECTS_TO),
|
|
2861
3205
|
source: service.node.id,
|
|
2862
3206
|
target: dbNode.id,
|
|
2863
|
-
type:
|
|
2864
|
-
provenance:
|
|
3207
|
+
type: import_types8.EdgeType.CONNECTS_TO,
|
|
3208
|
+
provenance: import_types8.Provenance.EXTRACTED,
|
|
2865
3209
|
...config.sourceFile ? {
|
|
2866
3210
|
evidence: {
|
|
2867
|
-
file:
|
|
3211
|
+
file: import_node_path16.default.relative(scanPath, config.sourceFile).split(import_node_path16.default.sep).join("/")
|
|
2868
3212
|
}
|
|
2869
3213
|
} : {}
|
|
2870
3214
|
};
|
|
@@ -2892,15 +3236,15 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
|
|
|
2892
3236
|
|
|
2893
3237
|
// src/extract/configs.ts
|
|
2894
3238
|
init_cjs_shims();
|
|
2895
|
-
var
|
|
2896
|
-
var
|
|
2897
|
-
var
|
|
3239
|
+
var import_node_fs11 = require("fs");
|
|
3240
|
+
var import_node_path17 = __toESM(require("path"), 1);
|
|
3241
|
+
var import_types9 = require("@neat.is/types");
|
|
2898
3242
|
async function walkConfigFiles(dir) {
|
|
2899
3243
|
const out = [];
|
|
2900
3244
|
async function walk(current) {
|
|
2901
|
-
const entries = await
|
|
3245
|
+
const entries = await import_node_fs11.promises.readdir(current, { withFileTypes: true });
|
|
2902
3246
|
for (const entry of entries) {
|
|
2903
|
-
const full =
|
|
3247
|
+
const full = import_node_path17.default.join(current, entry.name);
|
|
2904
3248
|
if (entry.isDirectory()) {
|
|
2905
3249
|
if (!IGNORED_DIRS.has(entry.name)) await walk(full);
|
|
2906
3250
|
} else if (entry.isFile() && isConfigFile(entry.name).match) {
|
|
@@ -2917,25 +3261,25 @@ async function addConfigNodes(graph, services, scanPath) {
|
|
|
2917
3261
|
for (const service of services) {
|
|
2918
3262
|
const configFiles = await walkConfigFiles(service.dir);
|
|
2919
3263
|
for (const file of configFiles) {
|
|
2920
|
-
const relPath =
|
|
3264
|
+
const relPath = import_node_path17.default.relative(scanPath, file);
|
|
2921
3265
|
const node = {
|
|
2922
|
-
id: (0,
|
|
2923
|
-
type:
|
|
2924
|
-
name:
|
|
3266
|
+
id: (0, import_types9.configId)(relPath),
|
|
3267
|
+
type: import_types9.NodeType.ConfigNode,
|
|
3268
|
+
name: import_node_path17.default.basename(file),
|
|
2925
3269
|
path: relPath,
|
|
2926
|
-
fileType: isConfigFile(
|
|
3270
|
+
fileType: isConfigFile(import_node_path17.default.basename(file)).fileType
|
|
2927
3271
|
};
|
|
2928
3272
|
if (!graph.hasNode(node.id)) {
|
|
2929
3273
|
graph.addNode(node.id, node);
|
|
2930
3274
|
nodesAdded++;
|
|
2931
3275
|
}
|
|
2932
3276
|
const edge = {
|
|
2933
|
-
id: (0,
|
|
3277
|
+
id: (0, import_types5.extractedEdgeId)(service.node.id, node.id, import_types9.EdgeType.CONFIGURED_BY),
|
|
2934
3278
|
source: service.node.id,
|
|
2935
3279
|
target: node.id,
|
|
2936
|
-
type:
|
|
2937
|
-
provenance:
|
|
2938
|
-
evidence: { file: relPath.split(
|
|
3280
|
+
type: import_types9.EdgeType.CONFIGURED_BY,
|
|
3281
|
+
provenance: import_types9.Provenance.EXTRACTED,
|
|
3282
|
+
evidence: { file: relPath.split(import_node_path17.default.sep).join("/") }
|
|
2939
3283
|
};
|
|
2940
3284
|
if (!graph.hasEdge(edge.id)) {
|
|
2941
3285
|
graph.addEdgeWithKey(edge.id, edge.source, edge.target, edge);
|
|
@@ -2948,29 +3292,29 @@ async function addConfigNodes(graph, services, scanPath) {
|
|
|
2948
3292
|
|
|
2949
3293
|
// src/extract/calls/index.ts
|
|
2950
3294
|
init_cjs_shims();
|
|
2951
|
-
var
|
|
3295
|
+
var import_types15 = require("@neat.is/types");
|
|
2952
3296
|
|
|
2953
3297
|
// src/extract/calls/http.ts
|
|
2954
3298
|
init_cjs_shims();
|
|
2955
|
-
var
|
|
3299
|
+
var import_node_path19 = __toESM(require("path"), 1);
|
|
2956
3300
|
var import_tree_sitter = __toESM(require("tree-sitter"), 1);
|
|
2957
3301
|
var import_tree_sitter_javascript = __toESM(require("tree-sitter-javascript"), 1);
|
|
2958
3302
|
var import_tree_sitter_python = __toESM(require("tree-sitter-python"), 1);
|
|
2959
|
-
var
|
|
3303
|
+
var import_types10 = require("@neat.is/types");
|
|
2960
3304
|
|
|
2961
3305
|
// src/extract/calls/shared.ts
|
|
2962
3306
|
init_cjs_shims();
|
|
2963
|
-
var
|
|
2964
|
-
var
|
|
3307
|
+
var import_node_fs12 = require("fs");
|
|
3308
|
+
var import_node_path18 = __toESM(require("path"), 1);
|
|
2965
3309
|
async function walkSourceFiles(dir) {
|
|
2966
3310
|
const out = [];
|
|
2967
3311
|
async function walk(current) {
|
|
2968
|
-
const entries = await
|
|
3312
|
+
const entries = await import_node_fs12.promises.readdir(current, { withFileTypes: true }).catch(() => []);
|
|
2969
3313
|
for (const entry of entries) {
|
|
2970
|
-
const full =
|
|
3314
|
+
const full = import_node_path18.default.join(current, entry.name);
|
|
2971
3315
|
if (entry.isDirectory()) {
|
|
2972
3316
|
if (!IGNORED_DIRS.has(entry.name)) await walk(full);
|
|
2973
|
-
} else if (entry.isFile() && SERVICE_FILE_EXTENSIONS.has(
|
|
3317
|
+
} else if (entry.isFile() && SERVICE_FILE_EXTENSIONS.has(import_node_path18.default.extname(entry.name))) {
|
|
2974
3318
|
out.push(full);
|
|
2975
3319
|
}
|
|
2976
3320
|
}
|
|
@@ -2983,7 +3327,7 @@ async function loadSourceFiles(dir) {
|
|
|
2983
3327
|
const out = [];
|
|
2984
3328
|
for (const p of paths) {
|
|
2985
3329
|
try {
|
|
2986
|
-
const content = await
|
|
3330
|
+
const content = await import_node_fs12.promises.readFile(p, "utf8");
|
|
2987
3331
|
out.push({ path: p, content });
|
|
2988
3332
|
} catch {
|
|
2989
3333
|
}
|
|
@@ -3039,9 +3383,9 @@ async function addHttpCallEdges(graph, services) {
|
|
|
3039
3383
|
const knownHosts = /* @__PURE__ */ new Set();
|
|
3040
3384
|
const hostToNodeId = /* @__PURE__ */ new Map();
|
|
3041
3385
|
for (const service of services) {
|
|
3042
|
-
knownHosts.add(
|
|
3386
|
+
knownHosts.add(import_node_path19.default.basename(service.dir));
|
|
3043
3387
|
knownHosts.add(service.pkg.name);
|
|
3044
|
-
hostToNodeId.set(
|
|
3388
|
+
hostToNodeId.set(import_node_path19.default.basename(service.dir), service.node.id);
|
|
3045
3389
|
hostToNodeId.set(service.pkg.name, service.node.id);
|
|
3046
3390
|
}
|
|
3047
3391
|
let edgesAdded = 0;
|
|
@@ -3049,7 +3393,7 @@ async function addHttpCallEdges(graph, services) {
|
|
|
3049
3393
|
const files = await loadSourceFiles(service.dir);
|
|
3050
3394
|
const seenTargets = /* @__PURE__ */ new Map();
|
|
3051
3395
|
for (const file of files) {
|
|
3052
|
-
const parser =
|
|
3396
|
+
const parser = import_node_path19.default.extname(file.path) === ".py" ? pyParser : jsParser;
|
|
3053
3397
|
let targets;
|
|
3054
3398
|
try {
|
|
3055
3399
|
targets = callsFromSource(file.content, parser, knownHosts);
|
|
@@ -3071,13 +3415,13 @@ async function addHttpCallEdges(graph, services) {
|
|
|
3071
3415
|
const fileContent = files.find((f) => f.path === evidenceFile.file)?.content ?? "";
|
|
3072
3416
|
const line = lineOf(fileContent, `//${evidenceFile.host}`);
|
|
3073
3417
|
const edge = {
|
|
3074
|
-
id: (0,
|
|
3418
|
+
id: (0, import_types5.extractedEdgeId)(service.node.id, targetId, import_types10.EdgeType.CALLS),
|
|
3075
3419
|
source: service.node.id,
|
|
3076
3420
|
target: targetId,
|
|
3077
|
-
type:
|
|
3078
|
-
provenance:
|
|
3421
|
+
type: import_types10.EdgeType.CALLS,
|
|
3422
|
+
provenance: import_types10.Provenance.EXTRACTED,
|
|
3079
3423
|
evidence: {
|
|
3080
|
-
file:
|
|
3424
|
+
file: import_node_path19.default.relative(service.dir, evidenceFile.file),
|
|
3081
3425
|
line,
|
|
3082
3426
|
snippet: snippet(fileContent, line)
|
|
3083
3427
|
}
|
|
@@ -3093,8 +3437,8 @@ async function addHttpCallEdges(graph, services) {
|
|
|
3093
3437
|
|
|
3094
3438
|
// src/extract/calls/kafka.ts
|
|
3095
3439
|
init_cjs_shims();
|
|
3096
|
-
var
|
|
3097
|
-
var
|
|
3440
|
+
var import_node_path20 = __toESM(require("path"), 1);
|
|
3441
|
+
var import_types11 = require("@neat.is/types");
|
|
3098
3442
|
var PRODUCER_TOPIC_RE = /(?:producer|kafkaProducer)[\s\S]{0,40}?\.send\s*\(\s*\{[\s\S]{0,200}?topic\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
3099
3443
|
var CONSUMER_TOPIC_RE = /(?:consumer|kafkaConsumer)[\s\S]{0,40}?\.(?:subscribe|run)\s*\(\s*\{[\s\S]{0,200}?topic[s]?\s*:\s*(?:\[\s*)?['"`]([^'"`]+)['"`]/g;
|
|
3100
3444
|
function findAll(re, text) {
|
|
@@ -3115,12 +3459,12 @@ function kafkaEndpointsFromFile(file, serviceDir) {
|
|
|
3115
3459
|
seen.add(key);
|
|
3116
3460
|
const line = lineOf(file.content, topic);
|
|
3117
3461
|
out.push({
|
|
3118
|
-
infraId: (0,
|
|
3462
|
+
infraId: (0, import_types11.infraId)("kafka-topic", topic),
|
|
3119
3463
|
name: topic,
|
|
3120
3464
|
kind: "kafka-topic",
|
|
3121
3465
|
edgeType,
|
|
3122
3466
|
evidence: {
|
|
3123
|
-
file:
|
|
3467
|
+
file: import_node_path20.default.relative(serviceDir, file.path),
|
|
3124
3468
|
line,
|
|
3125
3469
|
snippet: snippet(file.content, line)
|
|
3126
3470
|
}
|
|
@@ -3133,8 +3477,8 @@ function kafkaEndpointsFromFile(file, serviceDir) {
|
|
|
3133
3477
|
|
|
3134
3478
|
// src/extract/calls/redis.ts
|
|
3135
3479
|
init_cjs_shims();
|
|
3136
|
-
var
|
|
3137
|
-
var
|
|
3480
|
+
var import_node_path21 = __toESM(require("path"), 1);
|
|
3481
|
+
var import_types12 = require("@neat.is/types");
|
|
3138
3482
|
var REDIS_URL_RE = /redis(?:s)?:\/\/(?:[^@'"`\s]+@)?([^:/'"`\s]+)(?::(\d+))?/g;
|
|
3139
3483
|
function redisEndpointsFromFile(file, serviceDir) {
|
|
3140
3484
|
const out = [];
|
|
@@ -3147,12 +3491,12 @@ function redisEndpointsFromFile(file, serviceDir) {
|
|
|
3147
3491
|
seen.add(host);
|
|
3148
3492
|
const line = lineOf(file.content, host);
|
|
3149
3493
|
out.push({
|
|
3150
|
-
infraId: (0,
|
|
3494
|
+
infraId: (0, import_types12.infraId)("redis", host),
|
|
3151
3495
|
name: host,
|
|
3152
3496
|
kind: "redis",
|
|
3153
3497
|
edgeType: "CALLS",
|
|
3154
3498
|
evidence: {
|
|
3155
|
-
file:
|
|
3499
|
+
file: import_node_path21.default.relative(serviceDir, file.path),
|
|
3156
3500
|
line,
|
|
3157
3501
|
snippet: snippet(file.content, line)
|
|
3158
3502
|
}
|
|
@@ -3163,8 +3507,8 @@ function redisEndpointsFromFile(file, serviceDir) {
|
|
|
3163
3507
|
|
|
3164
3508
|
// src/extract/calls/aws.ts
|
|
3165
3509
|
init_cjs_shims();
|
|
3166
|
-
var
|
|
3167
|
-
var
|
|
3510
|
+
var import_node_path22 = __toESM(require("path"), 1);
|
|
3511
|
+
var import_types13 = require("@neat.is/types");
|
|
3168
3512
|
var S3_BUCKET_RE = /Bucket\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
3169
3513
|
var DYNAMO_TABLE_RE = /TableName\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
3170
3514
|
function hasMarker(text, markers) {
|
|
@@ -3188,12 +3532,12 @@ function awsEndpointsFromFile(file, serviceDir) {
|
|
|
3188
3532
|
seen.add(key);
|
|
3189
3533
|
const line = lineOf(file.content, name);
|
|
3190
3534
|
out.push({
|
|
3191
|
-
infraId: (0,
|
|
3535
|
+
infraId: (0, import_types13.infraId)(kind, name),
|
|
3192
3536
|
name,
|
|
3193
3537
|
kind,
|
|
3194
3538
|
edgeType: "CALLS",
|
|
3195
3539
|
evidence: {
|
|
3196
|
-
file:
|
|
3540
|
+
file: import_node_path22.default.relative(serviceDir, file.path),
|
|
3197
3541
|
line,
|
|
3198
3542
|
snippet: snippet(file.content, line)
|
|
3199
3543
|
}
|
|
@@ -3218,8 +3562,8 @@ function awsEndpointsFromFile(file, serviceDir) {
|
|
|
3218
3562
|
|
|
3219
3563
|
// src/extract/calls/grpc.ts
|
|
3220
3564
|
init_cjs_shims();
|
|
3221
|
-
var
|
|
3222
|
-
var
|
|
3565
|
+
var import_node_path23 = __toESM(require("path"), 1);
|
|
3566
|
+
var import_types14 = require("@neat.is/types");
|
|
3223
3567
|
var GRPC_CLIENT_RE = /new\s+([A-Z][A-Za-z0-9_]*)Client\s*\(\s*['"`]?([^,'"`)]+)?/g;
|
|
3224
3568
|
function isLikelyAddress(value) {
|
|
3225
3569
|
if (!value) return false;
|
|
@@ -3238,12 +3582,12 @@ function grpcEndpointsFromFile(file, serviceDir) {
|
|
|
3238
3582
|
seen.add(name);
|
|
3239
3583
|
const line = lineOf(file.content, m[0]);
|
|
3240
3584
|
out.push({
|
|
3241
|
-
infraId: (0,
|
|
3585
|
+
infraId: (0, import_types14.infraId)("grpc-service", name),
|
|
3242
3586
|
name,
|
|
3243
3587
|
kind: "grpc-service",
|
|
3244
3588
|
edgeType: "CALLS",
|
|
3245
3589
|
evidence: {
|
|
3246
|
-
file:
|
|
3590
|
+
file: import_node_path23.default.relative(serviceDir, file.path),
|
|
3247
3591
|
line,
|
|
3248
3592
|
snippet: snippet(file.content, line)
|
|
3249
3593
|
}
|
|
@@ -3256,11 +3600,11 @@ function grpcEndpointsFromFile(file, serviceDir) {
|
|
|
3256
3600
|
function edgeTypeFromEndpoint(ep) {
|
|
3257
3601
|
switch (ep.edgeType) {
|
|
3258
3602
|
case "PUBLISHES_TO":
|
|
3259
|
-
return
|
|
3603
|
+
return import_types15.EdgeType.PUBLISHES_TO;
|
|
3260
3604
|
case "CONSUMES_FROM":
|
|
3261
|
-
return
|
|
3605
|
+
return import_types15.EdgeType.CONSUMES_FROM;
|
|
3262
3606
|
default:
|
|
3263
|
-
return
|
|
3607
|
+
return import_types15.EdgeType.CALLS;
|
|
3264
3608
|
}
|
|
3265
3609
|
}
|
|
3266
3610
|
async function addExternalEndpointEdges(graph, services) {
|
|
@@ -3281,7 +3625,7 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
3281
3625
|
if (!graph.hasNode(ep.infraId)) {
|
|
3282
3626
|
const node = {
|
|
3283
3627
|
id: ep.infraId,
|
|
3284
|
-
type:
|
|
3628
|
+
type: import_types15.NodeType.InfraNode,
|
|
3285
3629
|
name: ep.name,
|
|
3286
3630
|
provider: ep.kind.startsWith("s3") || ep.kind.startsWith("dynamodb") ? "aws" : "self",
|
|
3287
3631
|
kind: ep.kind
|
|
@@ -3290,7 +3634,7 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
3290
3634
|
nodesAdded++;
|
|
3291
3635
|
}
|
|
3292
3636
|
const edgeType = edgeTypeFromEndpoint(ep);
|
|
3293
|
-
const edgeId = (0,
|
|
3637
|
+
const edgeId = (0, import_types5.extractedEdgeId)(service.node.id, ep.infraId, edgeType);
|
|
3294
3638
|
if (seenEdges.has(edgeId)) continue;
|
|
3295
3639
|
seenEdges.add(edgeId);
|
|
3296
3640
|
if (!graph.hasEdge(edgeId)) {
|
|
@@ -3299,7 +3643,7 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
3299
3643
|
source: service.node.id,
|
|
3300
3644
|
target: ep.infraId,
|
|
3301
3645
|
type: edgeType,
|
|
3302
|
-
provenance:
|
|
3646
|
+
provenance: import_types15.Provenance.EXTRACTED,
|
|
3303
3647
|
evidence: ep.evidence
|
|
3304
3648
|
};
|
|
3305
3649
|
graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
|
|
@@ -3323,16 +3667,16 @@ init_cjs_shims();
|
|
|
3323
3667
|
|
|
3324
3668
|
// src/extract/infra/docker-compose.ts
|
|
3325
3669
|
init_cjs_shims();
|
|
3326
|
-
var
|
|
3327
|
-
var
|
|
3670
|
+
var import_node_path24 = __toESM(require("path"), 1);
|
|
3671
|
+
var import_types17 = require("@neat.is/types");
|
|
3328
3672
|
|
|
3329
3673
|
// src/extract/infra/shared.ts
|
|
3330
3674
|
init_cjs_shims();
|
|
3331
|
-
var
|
|
3675
|
+
var import_types16 = require("@neat.is/types");
|
|
3332
3676
|
function makeInfraNode(kind, name, provider = "self", extras) {
|
|
3333
3677
|
return {
|
|
3334
|
-
id: (0,
|
|
3335
|
-
type:
|
|
3678
|
+
id: (0, import_types16.infraId)(kind, name),
|
|
3679
|
+
type: import_types16.NodeType.InfraNode,
|
|
3336
3680
|
name,
|
|
3337
3681
|
provider,
|
|
3338
3682
|
kind,
|
|
@@ -3361,7 +3705,7 @@ function dependsOnList(value) {
|
|
|
3361
3705
|
}
|
|
3362
3706
|
function serviceNameToServiceNode(name, services) {
|
|
3363
3707
|
for (const s of services) {
|
|
3364
|
-
if (s.node.name === name ||
|
|
3708
|
+
if (s.node.name === name || import_node_path24.default.basename(s.dir) === name) return s.node.id;
|
|
3365
3709
|
}
|
|
3366
3710
|
return null;
|
|
3367
3711
|
}
|
|
@@ -3370,16 +3714,24 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
3370
3714
|
let edgesAdded = 0;
|
|
3371
3715
|
let composePath = null;
|
|
3372
3716
|
for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
|
|
3373
|
-
const abs =
|
|
3717
|
+
const abs = import_node_path24.default.join(scanPath, name);
|
|
3374
3718
|
if (await exists(abs)) {
|
|
3375
3719
|
composePath = abs;
|
|
3376
3720
|
break;
|
|
3377
3721
|
}
|
|
3378
3722
|
}
|
|
3379
3723
|
if (!composePath) return { nodesAdded, edgesAdded };
|
|
3380
|
-
|
|
3724
|
+
let compose;
|
|
3725
|
+
try {
|
|
3726
|
+
compose = await readYaml(composePath);
|
|
3727
|
+
} catch (err) {
|
|
3728
|
+
console.warn(
|
|
3729
|
+
`[neat] infra docker-compose skipped ${import_node_path24.default.relative(scanPath, composePath)}: ${err.message}`
|
|
3730
|
+
);
|
|
3731
|
+
return { nodesAdded, edgesAdded };
|
|
3732
|
+
}
|
|
3381
3733
|
if (!compose?.services) return { nodesAdded, edgesAdded };
|
|
3382
|
-
const evidenceFile =
|
|
3734
|
+
const evidenceFile = import_node_path24.default.relative(scanPath, composePath).split(import_node_path24.default.sep).join("/");
|
|
3383
3735
|
const composeNameToNodeId = /* @__PURE__ */ new Map();
|
|
3384
3736
|
for (const [composeName, svc] of Object.entries(compose.services)) {
|
|
3385
3737
|
const matchedServiceId = serviceNameToServiceNode(composeName, services);
|
|
@@ -3401,14 +3753,14 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
3401
3753
|
for (const dep of dependsOnList(svc.depends_on)) {
|
|
3402
3754
|
const targetId = composeNameToNodeId.get(dep);
|
|
3403
3755
|
if (!targetId) continue;
|
|
3404
|
-
const edgeId = (0,
|
|
3756
|
+
const edgeId = (0, import_types5.extractedEdgeId)(sourceId, targetId, import_types17.EdgeType.DEPENDS_ON);
|
|
3405
3757
|
if (graph.hasEdge(edgeId)) continue;
|
|
3406
3758
|
const edge = {
|
|
3407
3759
|
id: edgeId,
|
|
3408
3760
|
source: sourceId,
|
|
3409
3761
|
target: targetId,
|
|
3410
|
-
type:
|
|
3411
|
-
provenance:
|
|
3762
|
+
type: import_types17.EdgeType.DEPENDS_ON,
|
|
3763
|
+
provenance: import_types17.Provenance.EXTRACTED,
|
|
3412
3764
|
evidence: { file: evidenceFile }
|
|
3413
3765
|
};
|
|
3414
3766
|
graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
|
|
@@ -3420,9 +3772,9 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
3420
3772
|
|
|
3421
3773
|
// src/extract/infra/dockerfile.ts
|
|
3422
3774
|
init_cjs_shims();
|
|
3423
|
-
var
|
|
3424
|
-
var
|
|
3425
|
-
var
|
|
3775
|
+
var import_node_path25 = __toESM(require("path"), 1);
|
|
3776
|
+
var import_node_fs13 = require("fs");
|
|
3777
|
+
var import_types18 = require("@neat.is/types");
|
|
3426
3778
|
function runtimeImage(content) {
|
|
3427
3779
|
const lines = content.split("\n");
|
|
3428
3780
|
let last = null;
|
|
@@ -3441,9 +3793,17 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
3441
3793
|
let nodesAdded = 0;
|
|
3442
3794
|
let edgesAdded = 0;
|
|
3443
3795
|
for (const service of services) {
|
|
3444
|
-
const dockerfilePath =
|
|
3796
|
+
const dockerfilePath = import_node_path25.default.join(service.dir, "Dockerfile");
|
|
3445
3797
|
if (!await exists(dockerfilePath)) continue;
|
|
3446
|
-
|
|
3798
|
+
let content;
|
|
3799
|
+
try {
|
|
3800
|
+
content = await import_node_fs13.promises.readFile(dockerfilePath, "utf8");
|
|
3801
|
+
} catch (err) {
|
|
3802
|
+
console.warn(
|
|
3803
|
+
`[neat] infra dockerfile skipped ${import_node_path25.default.relative(scanPath, dockerfilePath)}: ${err.message}`
|
|
3804
|
+
);
|
|
3805
|
+
continue;
|
|
3806
|
+
}
|
|
3447
3807
|
const image = runtimeImage(content);
|
|
3448
3808
|
if (!image) continue;
|
|
3449
3809
|
const node = makeInfraNode("container-image", image);
|
|
@@ -3451,16 +3811,16 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
3451
3811
|
graph.addNode(node.id, node);
|
|
3452
3812
|
nodesAdded++;
|
|
3453
3813
|
}
|
|
3454
|
-
const edgeId = (0,
|
|
3814
|
+
const edgeId = (0, import_types5.extractedEdgeId)(service.node.id, node.id, import_types18.EdgeType.RUNS_ON);
|
|
3455
3815
|
if (!graph.hasEdge(edgeId)) {
|
|
3456
3816
|
const edge = {
|
|
3457
3817
|
id: edgeId,
|
|
3458
3818
|
source: service.node.id,
|
|
3459
3819
|
target: node.id,
|
|
3460
|
-
type:
|
|
3461
|
-
provenance:
|
|
3820
|
+
type: import_types18.EdgeType.RUNS_ON,
|
|
3821
|
+
provenance: import_types18.Provenance.EXTRACTED,
|
|
3462
3822
|
evidence: {
|
|
3463
|
-
file:
|
|
3823
|
+
file: import_node_path25.default.relative(scanPath, dockerfilePath).split(import_node_path25.default.sep).join("/")
|
|
3464
3824
|
}
|
|
3465
3825
|
};
|
|
3466
3826
|
graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
|
|
@@ -3472,19 +3832,19 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
3472
3832
|
|
|
3473
3833
|
// src/extract/infra/terraform.ts
|
|
3474
3834
|
init_cjs_shims();
|
|
3475
|
-
var
|
|
3476
|
-
var
|
|
3835
|
+
var import_node_fs14 = require("fs");
|
|
3836
|
+
var import_node_path26 = __toESM(require("path"), 1);
|
|
3477
3837
|
var RESOURCE_RE = /resource\s+"(aws_[A-Za-z0-9_]+)"\s+"([A-Za-z0-9_-]+)"/g;
|
|
3478
3838
|
async function walkTfFiles(start, depth = 0, max = 5) {
|
|
3479
3839
|
if (depth > max) return [];
|
|
3480
3840
|
const out = [];
|
|
3481
|
-
const entries = await
|
|
3841
|
+
const entries = await import_node_fs14.promises.readdir(start, { withFileTypes: true }).catch(() => []);
|
|
3482
3842
|
for (const entry of entries) {
|
|
3483
3843
|
if (entry.isDirectory()) {
|
|
3484
3844
|
if (IGNORED_DIRS.has(entry.name) || entry.name === ".terraform") continue;
|
|
3485
|
-
out.push(...await walkTfFiles(
|
|
3845
|
+
out.push(...await walkTfFiles(import_node_path26.default.join(start, entry.name), depth + 1, max));
|
|
3486
3846
|
} else if (entry.isFile() && entry.name.endsWith(".tf")) {
|
|
3487
|
-
out.push(
|
|
3847
|
+
out.push(import_node_path26.default.join(start, entry.name));
|
|
3488
3848
|
}
|
|
3489
3849
|
}
|
|
3490
3850
|
return out;
|
|
@@ -3493,7 +3853,7 @@ async function addTerraformResources(graph, scanPath) {
|
|
|
3493
3853
|
let nodesAdded = 0;
|
|
3494
3854
|
const files = await walkTfFiles(scanPath);
|
|
3495
3855
|
for (const file of files) {
|
|
3496
|
-
const content = await
|
|
3856
|
+
const content = await import_node_fs14.promises.readFile(file, "utf8");
|
|
3497
3857
|
RESOURCE_RE.lastIndex = 0;
|
|
3498
3858
|
let m;
|
|
3499
3859
|
while ((m = RESOURCE_RE.exec(content)) !== null) {
|
|
@@ -3511,8 +3871,8 @@ async function addTerraformResources(graph, scanPath) {
|
|
|
3511
3871
|
|
|
3512
3872
|
// src/extract/infra/k8s.ts
|
|
3513
3873
|
init_cjs_shims();
|
|
3514
|
-
var
|
|
3515
|
-
var
|
|
3874
|
+
var import_node_fs15 = require("fs");
|
|
3875
|
+
var import_node_path27 = __toESM(require("path"), 1);
|
|
3516
3876
|
var import_yaml3 = require("yaml");
|
|
3517
3877
|
var K8S_KIND_TO_INFRA_KIND = {
|
|
3518
3878
|
Service: "k8s-service",
|
|
@@ -3526,13 +3886,13 @@ var K8S_KIND_TO_INFRA_KIND = {
|
|
|
3526
3886
|
async function walkYamlFiles2(start, depth = 0, max = 5) {
|
|
3527
3887
|
if (depth > max) return [];
|
|
3528
3888
|
const out = [];
|
|
3529
|
-
const entries = await
|
|
3889
|
+
const entries = await import_node_fs15.promises.readdir(start, { withFileTypes: true }).catch(() => []);
|
|
3530
3890
|
for (const entry of entries) {
|
|
3531
3891
|
if (entry.isDirectory()) {
|
|
3532
3892
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
3533
|
-
out.push(...await walkYamlFiles2(
|
|
3534
|
-
} else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(
|
|
3535
|
-
out.push(
|
|
3893
|
+
out.push(...await walkYamlFiles2(import_node_path27.default.join(start, entry.name), depth + 1, max));
|
|
3894
|
+
} else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path27.default.extname(entry.name))) {
|
|
3895
|
+
out.push(import_node_path27.default.join(start, entry.name));
|
|
3536
3896
|
}
|
|
3537
3897
|
}
|
|
3538
3898
|
return out;
|
|
@@ -3541,7 +3901,7 @@ async function addK8sResources(graph, scanPath) {
|
|
|
3541
3901
|
let nodesAdded = 0;
|
|
3542
3902
|
const files = await walkYamlFiles2(scanPath);
|
|
3543
3903
|
for (const file of files) {
|
|
3544
|
-
const content = await
|
|
3904
|
+
const content = await import_node_fs15.promises.readFile(file, "utf8");
|
|
3545
3905
|
let docs;
|
|
3546
3906
|
try {
|
|
3547
3907
|
docs = (0, import_yaml3.parseAllDocuments)(content).map((d) => d.toJSON());
|
|
@@ -3607,7 +3967,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
3607
3967
|
|
|
3608
3968
|
// src/diff.ts
|
|
3609
3969
|
init_cjs_shims();
|
|
3610
|
-
var
|
|
3970
|
+
var import_node_fs16 = require("fs");
|
|
3611
3971
|
async function loadSnapshotForDiff(target) {
|
|
3612
3972
|
if (/^https?:\/\//i.test(target)) {
|
|
3613
3973
|
const res = await fetch(target);
|
|
@@ -3616,7 +3976,7 @@ async function loadSnapshotForDiff(target) {
|
|
|
3616
3976
|
}
|
|
3617
3977
|
return await res.json();
|
|
3618
3978
|
}
|
|
3619
|
-
const raw = await
|
|
3979
|
+
const raw = await import_node_fs16.promises.readFile(target, "utf8");
|
|
3620
3980
|
return JSON.parse(raw);
|
|
3621
3981
|
}
|
|
3622
3982
|
function indexEntries(entries) {
|
|
@@ -3684,23 +4044,23 @@ function canonicalJson(value) {
|
|
|
3684
4044
|
|
|
3685
4045
|
// src/projects.ts
|
|
3686
4046
|
init_cjs_shims();
|
|
3687
|
-
var
|
|
4047
|
+
var import_node_path28 = __toESM(require("path"), 1);
|
|
3688
4048
|
function pathsForProject(project, baseDir) {
|
|
3689
4049
|
if (project === DEFAULT_PROJECT) {
|
|
3690
4050
|
return {
|
|
3691
|
-
snapshotPath:
|
|
3692
|
-
errorsPath:
|
|
3693
|
-
staleEventsPath:
|
|
3694
|
-
embeddingsCachePath:
|
|
3695
|
-
policyViolationsPath:
|
|
4051
|
+
snapshotPath: import_node_path28.default.join(baseDir, "graph.json"),
|
|
4052
|
+
errorsPath: import_node_path28.default.join(baseDir, "errors.ndjson"),
|
|
4053
|
+
staleEventsPath: import_node_path28.default.join(baseDir, "stale-events.ndjson"),
|
|
4054
|
+
embeddingsCachePath: import_node_path28.default.join(baseDir, "embeddings.json"),
|
|
4055
|
+
policyViolationsPath: import_node_path28.default.join(baseDir, "policy-violations.ndjson")
|
|
3696
4056
|
};
|
|
3697
4057
|
}
|
|
3698
4058
|
return {
|
|
3699
|
-
snapshotPath:
|
|
3700
|
-
errorsPath:
|
|
3701
|
-
staleEventsPath:
|
|
3702
|
-
embeddingsCachePath:
|
|
3703
|
-
policyViolationsPath:
|
|
4059
|
+
snapshotPath: import_node_path28.default.join(baseDir, `${project}.json`),
|
|
4060
|
+
errorsPath: import_node_path28.default.join(baseDir, `errors.${project}.ndjson`),
|
|
4061
|
+
staleEventsPath: import_node_path28.default.join(baseDir, `stale-events.${project}.ndjson`),
|
|
4062
|
+
embeddingsCachePath: import_node_path28.default.join(baseDir, `embeddings.${project}.json`),
|
|
4063
|
+
policyViolationsPath: import_node_path28.default.join(baseDir, `policy-violations.${project}.ndjson`)
|
|
3704
4064
|
};
|
|
3705
4065
|
}
|
|
3706
4066
|
var Projects = class {
|
|
@@ -3740,23 +4100,23 @@ function parseExtraProjects(raw) {
|
|
|
3740
4100
|
|
|
3741
4101
|
// src/registry.ts
|
|
3742
4102
|
init_cjs_shims();
|
|
3743
|
-
var
|
|
4103
|
+
var import_node_fs17 = require("fs");
|
|
3744
4104
|
var import_node_os2 = __toESM(require("os"), 1);
|
|
3745
|
-
var
|
|
3746
|
-
var
|
|
4105
|
+
var import_node_path29 = __toESM(require("path"), 1);
|
|
4106
|
+
var import_types19 = require("@neat.is/types");
|
|
3747
4107
|
function neatHome() {
|
|
3748
4108
|
const override = process.env.NEAT_HOME;
|
|
3749
|
-
if (override && override.length > 0) return
|
|
3750
|
-
return
|
|
4109
|
+
if (override && override.length > 0) return import_node_path29.default.resolve(override);
|
|
4110
|
+
return import_node_path29.default.join(import_node_os2.default.homedir(), ".neat");
|
|
3751
4111
|
}
|
|
3752
4112
|
function registryPath() {
|
|
3753
|
-
return
|
|
4113
|
+
return import_node_path29.default.join(neatHome(), "projects.json");
|
|
3754
4114
|
}
|
|
3755
4115
|
async function readRegistry() {
|
|
3756
4116
|
const file = registryPath();
|
|
3757
4117
|
let raw;
|
|
3758
4118
|
try {
|
|
3759
|
-
raw = await
|
|
4119
|
+
raw = await import_node_fs17.promises.readFile(file, "utf8");
|
|
3760
4120
|
} catch (err) {
|
|
3761
4121
|
if (err.code === "ENOENT") {
|
|
3762
4122
|
return { version: 1, projects: [] };
|
|
@@ -3764,7 +4124,7 @@ async function readRegistry() {
|
|
|
3764
4124
|
throw err;
|
|
3765
4125
|
}
|
|
3766
4126
|
const parsed = JSON.parse(raw);
|
|
3767
|
-
return
|
|
4127
|
+
return import_types19.RegistryFileSchema.parse(parsed);
|
|
3768
4128
|
}
|
|
3769
4129
|
async function listProjects() {
|
|
3770
4130
|
const reg = await readRegistry();
|
|
@@ -3936,6 +4296,41 @@ function registerRoutes(scope, ctx) {
|
|
|
3936
4296
|
}
|
|
3937
4297
|
return getTransitiveDependencies(proj.graph, id, depth);
|
|
3938
4298
|
});
|
|
4299
|
+
scope.get("/graph/divergences", async (req, reply) => {
|
|
4300
|
+
const proj = resolveProject(registry, req, reply);
|
|
4301
|
+
if (!proj) return;
|
|
4302
|
+
let typeFilter;
|
|
4303
|
+
if (req.query.type) {
|
|
4304
|
+
const candidates = req.query.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
4305
|
+
const parsed = [];
|
|
4306
|
+
for (const c of candidates) {
|
|
4307
|
+
const r = import_types20.DivergenceTypeSchema.safeParse(c);
|
|
4308
|
+
if (!r.success) {
|
|
4309
|
+
return reply.code(400).send({
|
|
4310
|
+
error: `unknown divergence type "${c}"`,
|
|
4311
|
+
allowed: import_types20.DivergenceTypeSchema.options
|
|
4312
|
+
});
|
|
4313
|
+
}
|
|
4314
|
+
parsed.push(r.data);
|
|
4315
|
+
}
|
|
4316
|
+
typeFilter = new Set(parsed);
|
|
4317
|
+
}
|
|
4318
|
+
let minConfidence;
|
|
4319
|
+
if (req.query.minConfidence !== void 0) {
|
|
4320
|
+
const n = Number(req.query.minConfidence);
|
|
4321
|
+
if (!Number.isFinite(n) || n < 0 || n > 1) {
|
|
4322
|
+
return reply.code(400).send({
|
|
4323
|
+
error: "minConfidence must be a number in [0, 1]"
|
|
4324
|
+
});
|
|
4325
|
+
}
|
|
4326
|
+
minConfidence = n;
|
|
4327
|
+
}
|
|
4328
|
+
return computeDivergences(proj.graph, {
|
|
4329
|
+
...typeFilter ? { type: typeFilter } : {},
|
|
4330
|
+
...minConfidence !== void 0 ? { minConfidence } : {},
|
|
4331
|
+
...req.query.node ? { node: req.query.node } : {}
|
|
4332
|
+
});
|
|
4333
|
+
});
|
|
3939
4334
|
scope.get("/incidents", async (req, reply) => {
|
|
3940
4335
|
const proj = resolveProject(registry, req, reply);
|
|
3941
4336
|
if (!proj) return;
|
|
@@ -4089,7 +4484,7 @@ function registerRoutes(scope, ctx) {
|
|
|
4089
4484
|
const log = new PolicyViolationsLog(proj.paths.policyViolationsPath);
|
|
4090
4485
|
let violations = await log.readAll();
|
|
4091
4486
|
if (req.query.severity) {
|
|
4092
|
-
const sev =
|
|
4487
|
+
const sev = import_types20.PolicySeveritySchema.safeParse(req.query.severity);
|
|
4093
4488
|
if (!sev.success) {
|
|
4094
4489
|
return reply.code(400).send({
|
|
4095
4490
|
error: "invalid severity",
|
|
@@ -4106,7 +4501,7 @@ function registerRoutes(scope, ctx) {
|
|
|
4106
4501
|
scope.post("/policies/check", async (req, reply) => {
|
|
4107
4502
|
const proj = resolveProject(registry, req, reply);
|
|
4108
4503
|
if (!proj) return;
|
|
4109
|
-
const parsed =
|
|
4504
|
+
const parsed = import_types20.PoliciesCheckBodySchema.safeParse(req.body ?? {});
|
|
4110
4505
|
if (!parsed.success) {
|
|
4111
4506
|
return reply.code(400).send({
|
|
4112
4507
|
error: "invalid /policies/check body",
|
|
@@ -4192,8 +4587,8 @@ async function buildApi(opts) {
|
|
|
4192
4587
|
|
|
4193
4588
|
// src/persist.ts
|
|
4194
4589
|
init_cjs_shims();
|
|
4195
|
-
var
|
|
4196
|
-
var
|
|
4590
|
+
var import_node_fs18 = require("fs");
|
|
4591
|
+
var import_node_path30 = __toESM(require("path"), 1);
|
|
4197
4592
|
var SCHEMA_VERSION = 2;
|
|
4198
4593
|
function migrateV1ToV2(payload) {
|
|
4199
4594
|
const nodes = payload.graph.nodes;
|
|
@@ -4207,7 +4602,7 @@ function migrateV1ToV2(payload) {
|
|
|
4207
4602
|
return { ...payload, schemaVersion: 2 };
|
|
4208
4603
|
}
|
|
4209
4604
|
async function ensureDir(filePath) {
|
|
4210
|
-
await
|
|
4605
|
+
await import_node_fs18.promises.mkdir(import_node_path30.default.dirname(filePath), { recursive: true });
|
|
4211
4606
|
}
|
|
4212
4607
|
async function saveGraphToDisk(graph, outPath) {
|
|
4213
4608
|
await ensureDir(outPath);
|
|
@@ -4217,13 +4612,13 @@ async function saveGraphToDisk(graph, outPath) {
|
|
|
4217
4612
|
graph: graph.export()
|
|
4218
4613
|
};
|
|
4219
4614
|
const tmp = `${outPath}.tmp`;
|
|
4220
|
-
await
|
|
4221
|
-
await
|
|
4615
|
+
await import_node_fs18.promises.writeFile(tmp, JSON.stringify(payload), "utf8");
|
|
4616
|
+
await import_node_fs18.promises.rename(tmp, outPath);
|
|
4222
4617
|
}
|
|
4223
4618
|
async function loadGraphFromDisk(graph, outPath) {
|
|
4224
4619
|
let raw;
|
|
4225
4620
|
try {
|
|
4226
|
-
raw = await
|
|
4621
|
+
raw = await import_node_fs18.promises.readFile(outPath, "utf8");
|
|
4227
4622
|
} catch (err) {
|
|
4228
4623
|
if (err.code === "ENOENT") return;
|
|
4229
4624
|
throw err;
|
|
@@ -4280,8 +4675,8 @@ init_otel_grpc();
|
|
|
4280
4675
|
|
|
4281
4676
|
// src/search.ts
|
|
4282
4677
|
init_cjs_shims();
|
|
4283
|
-
var
|
|
4284
|
-
var
|
|
4678
|
+
var import_node_fs19 = require("fs");
|
|
4679
|
+
var import_node_path33 = __toESM(require("path"), 1);
|
|
4285
4680
|
var import_node_crypto = require("crypto");
|
|
4286
4681
|
var DEFAULT_LIMIT = 10;
|
|
4287
4682
|
var NOMIC_DIM = 768;
|
|
@@ -4411,7 +4806,7 @@ async function pickEmbedder() {
|
|
|
4411
4806
|
}
|
|
4412
4807
|
async function readCache(cachePath) {
|
|
4413
4808
|
try {
|
|
4414
|
-
const raw = await
|
|
4809
|
+
const raw = await import_node_fs19.promises.readFile(cachePath, "utf8");
|
|
4415
4810
|
const parsed = JSON.parse(raw);
|
|
4416
4811
|
if (parsed.version !== 1) return null;
|
|
4417
4812
|
return parsed;
|
|
@@ -4420,8 +4815,8 @@ async function readCache(cachePath) {
|
|
|
4420
4815
|
}
|
|
4421
4816
|
}
|
|
4422
4817
|
async function writeCache(cachePath, cache) {
|
|
4423
|
-
await
|
|
4424
|
-
await
|
|
4818
|
+
await import_node_fs19.promises.mkdir(import_node_path33.default.dirname(cachePath), { recursive: true });
|
|
4819
|
+
await import_node_fs19.promises.writeFile(cachePath, JSON.stringify(cache));
|
|
4425
4820
|
}
|
|
4426
4821
|
var VectorIndex = class {
|
|
4427
4822
|
constructor(embedder, cachePath) {
|
|
@@ -4600,14 +4995,14 @@ async function bootProject(registry, name, scanPath, baseDir) {
|
|
|
4600
4995
|
async function main() {
|
|
4601
4996
|
const baseDirEnv = process.env.NEAT_OUT_DIR;
|
|
4602
4997
|
const legacyOutPath = process.env.NEAT_OUT_PATH;
|
|
4603
|
-
const baseDir = baseDirEnv ?
|
|
4604
|
-
const defaultScanPath =
|
|
4998
|
+
const baseDir = baseDirEnv ? import_node_path34.default.resolve(baseDirEnv) : legacyOutPath ? import_node_path34.default.resolve(import_node_path34.default.dirname(legacyOutPath)) : import_node_path34.default.resolve("./neat-out");
|
|
4999
|
+
const defaultScanPath = import_node_path34.default.resolve(process.env.NEAT_SCAN_PATH ?? "./demo");
|
|
4605
5000
|
const registry = new Projects();
|
|
4606
5001
|
await bootProject(registry, DEFAULT_PROJECT, defaultScanPath, baseDir);
|
|
4607
5002
|
for (const name of parseExtraProjects(process.env.NEAT_PROJECTS)) {
|
|
4608
5003
|
const envKey = `NEAT_PROJECT_SCAN_PATH_${name.toUpperCase().replace(/[^A-Z0-9]/g, "_")}`;
|
|
4609
5004
|
const projectScan = process.env[envKey];
|
|
4610
|
-
await bootProject(registry, name, projectScan ?
|
|
5005
|
+
await bootProject(registry, name, projectScan ? import_node_path34.default.resolve(projectScan) : void 0, baseDir);
|
|
4611
5006
|
}
|
|
4612
5007
|
const host = process.env.HOST ?? "0.0.0.0";
|
|
4613
5008
|
const port = Number(process.env.PORT ?? 8080);
|