@neat.is/core 0.4.26-dev.20260703 → 0.4.27-dev.20260705
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-2LNICOVU.js → chunk-A3322JYS.js} +29 -2
- package/dist/chunk-A3322JYS.js.map +1 -0
- package/dist/{chunk-4OR4RQEO.js → chunk-BIY46Q6U.js} +13 -1
- package/dist/chunk-BIY46Q6U.js.map +1 -0
- package/dist/{chunk-WZYH5DVG.js → chunk-QM6BMPVJ.js} +419 -218
- package/dist/chunk-QM6BMPVJ.js.map +1 -0
- package/dist/{chunk-C5NCCKPZ.js → chunk-UV5WSM7M.js} +2 -2
- package/dist/{chunk-S7TDPQDD.js → chunk-XV4D7A3Z.js} +105 -6
- package/dist/chunk-XV4D7A3Z.js.map +1 -0
- package/dist/cli.cjs +732 -456
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +49 -6
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +587 -273
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +97 -1
- package/dist/index.d.ts +97 -1
- package/dist/index.js +4 -4
- package/dist/neatd.cjs +600 -286
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +3 -3
- package/dist/{otel-grpc-LFYZDSK3.js → otel-grpc-ET5Z6KI6.js} +3 -3
- package/dist/server.cjs +410 -182
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +4 -4
- package/package.json +4 -2
- package/dist/chunk-2LNICOVU.js.map +0 -1
- package/dist/chunk-4OR4RQEO.js.map +0 -1
- package/dist/chunk-S7TDPQDD.js.map +0 -1
- package/dist/chunk-WZYH5DVG.js.map +0 -1
- /package/dist/{chunk-C5NCCKPZ.js.map → chunk-UV5WSM7M.js.map} +0 -0
- /package/dist/{otel-grpc-LFYZDSK3.js.map → otel-grpc-ET5Z6KI6.js.map} +0 -0
package/dist/neatd.cjs
CHANGED
|
@@ -58,9 +58,9 @@ function mountBearerAuth(app, opts) {
|
|
|
58
58
|
const suffixes = [...DEFAULT_UNAUTH_SUFFIXES, ...opts.extraUnauthenticatedSuffixes ?? []];
|
|
59
59
|
const publicRead = opts.publicRead === true;
|
|
60
60
|
app.addHook("preHandler", (req2, reply, done) => {
|
|
61
|
-
const
|
|
61
|
+
const path48 = (req2.url.split("?")[0] ?? "").replace(/\/+$/, "");
|
|
62
62
|
for (const suffix of suffixes) {
|
|
63
|
-
if (
|
|
63
|
+
if (path48 === suffix || path48.endsWith(suffix)) {
|
|
64
64
|
done();
|
|
65
65
|
return;
|
|
66
66
|
}
|
|
@@ -190,8 +190,8 @@ function reshapeGrpcRequest(req2) {
|
|
|
190
190
|
};
|
|
191
191
|
}
|
|
192
192
|
function resolveProtoRoot() {
|
|
193
|
-
const here =
|
|
194
|
-
return
|
|
193
|
+
const here = import_node_path42.default.dirname((0, import_node_url.fileURLToPath)(importMetaUrl));
|
|
194
|
+
return import_node_path42.default.resolve(here, "..", "proto");
|
|
195
195
|
}
|
|
196
196
|
function loadTraceService() {
|
|
197
197
|
const protoRoot = resolveProtoRoot();
|
|
@@ -259,13 +259,13 @@ async function startOtelGrpcReceiver(opts) {
|
|
|
259
259
|
})
|
|
260
260
|
};
|
|
261
261
|
}
|
|
262
|
-
var import_node_url,
|
|
262
|
+
var import_node_url, import_node_path42, import_node_crypto2, grpc, protoLoader;
|
|
263
263
|
var init_otel_grpc = __esm({
|
|
264
264
|
"src/otel-grpc.ts"() {
|
|
265
265
|
"use strict";
|
|
266
266
|
init_cjs_shims();
|
|
267
267
|
import_node_url = require("url");
|
|
268
|
-
|
|
268
|
+
import_node_path42 = __toESM(require("path"), 1);
|
|
269
269
|
import_node_crypto2 = require("crypto");
|
|
270
270
|
grpc = __toESM(require("@grpc/grpc-js"), 1);
|
|
271
271
|
protoLoader = __toESM(require("@grpc/proto-loader"), 1);
|
|
@@ -345,6 +345,29 @@ function messagingDestinationOf(attrs) {
|
|
|
345
345
|
}
|
|
346
346
|
return void 0;
|
|
347
347
|
}
|
|
348
|
+
function hasWebsocketUpgradeHeader(attrs) {
|
|
349
|
+
const v = attrs["http.request.header.upgrade"];
|
|
350
|
+
const matches = (s) => typeof s === "string" && s.trim().toLowerCase() === "websocket";
|
|
351
|
+
if (Array.isArray(v)) return v.some(matches);
|
|
352
|
+
return matches(v);
|
|
353
|
+
}
|
|
354
|
+
function websocketChannelPathOf(attrs) {
|
|
355
|
+
const route = attrs["http.route"];
|
|
356
|
+
if (typeof route === "string" && route.length > 0) return route;
|
|
357
|
+
for (const key of ["url.path", "http.target"]) {
|
|
358
|
+
const v = attrs[key];
|
|
359
|
+
if (typeof v === "string" && v.length > 0) {
|
|
360
|
+
const q = v.indexOf("?");
|
|
361
|
+
const path48 = q === -1 ? v : v.slice(0, q);
|
|
362
|
+
if (path48.length > 0) return path48;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
return void 0;
|
|
366
|
+
}
|
|
367
|
+
function websocketChannelOf(attrs) {
|
|
368
|
+
if (!hasWebsocketUpgradeHeader(attrs)) return void 0;
|
|
369
|
+
return websocketChannelPathOf(attrs);
|
|
370
|
+
}
|
|
348
371
|
function parseOtlpRequest(body) {
|
|
349
372
|
const out = [];
|
|
350
373
|
for (const rs of body.resourceSpans ?? []) {
|
|
@@ -375,6 +398,10 @@ function parseOtlpRequest(body) {
|
|
|
375
398
|
messagingDestination: messagingDestinationOf(attrs),
|
|
376
399
|
graphqlOperationName: typeof attrs["graphql.operation.name"] === "string" && attrs["graphql.operation.name"].length > 0 ? attrs["graphql.operation.name"] : void 0,
|
|
377
400
|
graphqlOperationType: typeof attrs["graphql.operation.type"] === "string" && attrs["graphql.operation.type"].length > 0 ? attrs["graphql.operation.type"] : void 0,
|
|
401
|
+
rpcSystem: typeof attrs["rpc.system"] === "string" && attrs["rpc.system"].length > 0 ? attrs["rpc.system"] : void 0,
|
|
402
|
+
rpcService: typeof attrs["rpc.service"] === "string" && attrs["rpc.service"].length > 0 ? attrs["rpc.service"] : void 0,
|
|
403
|
+
rpcMethod: typeof attrs["rpc.method"] === "string" && attrs["rpc.method"].length > 0 ? attrs["rpc.method"] : void 0,
|
|
404
|
+
websocketChannel: websocketChannelOf(attrs),
|
|
378
405
|
statusCode: span.status?.code,
|
|
379
406
|
errorMessage: span.status?.message,
|
|
380
407
|
exception: extractExceptionFromEvents(span.events)
|
|
@@ -386,10 +413,10 @@ function parseOtlpRequest(body) {
|
|
|
386
413
|
return out;
|
|
387
414
|
}
|
|
388
415
|
function loadProtoRoot() {
|
|
389
|
-
const here =
|
|
390
|
-
const protoRoot =
|
|
416
|
+
const here = import_node_path43.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
|
|
417
|
+
const protoRoot = import_node_path43.default.resolve(here, "..", "proto");
|
|
391
418
|
const root = new import_protobufjs.default.Root();
|
|
392
|
-
root.resolvePath = (_origin, target) =>
|
|
419
|
+
root.resolvePath = (_origin, target) => import_node_path43.default.resolve(protoRoot, target);
|
|
393
420
|
root.loadSync(
|
|
394
421
|
"opentelemetry/proto/collector/trace/v1/trace_service.proto",
|
|
395
422
|
{ keepCase: true }
|
|
@@ -617,12 +644,12 @@ async function listenSteppingOtlp(app, requestedPort, host) {
|
|
|
617
644
|
}
|
|
618
645
|
}
|
|
619
646
|
}
|
|
620
|
-
var
|
|
647
|
+
var import_node_path43, import_node_url2, import_fastify2, import_protobufjs, ENV_ATTR_CANONICAL, ENV_ATTR_COMPAT, ENV_FALLBACK, exportTraceServiceRequestType, exportTraceServiceResponseType, cachedProtobufResponseBody, OTLP_STEP_ATTEMPTS, OTLP_STEP_STRIDE;
|
|
621
648
|
var init_otel = __esm({
|
|
622
649
|
"src/otel.ts"() {
|
|
623
650
|
"use strict";
|
|
624
651
|
init_cjs_shims();
|
|
625
|
-
|
|
652
|
+
import_node_path43 = __toESM(require("path"), 1);
|
|
626
653
|
import_node_url2 = require("url");
|
|
627
654
|
import_fastify2 = __toESM(require("fastify"), 1);
|
|
628
655
|
import_protobufjs = __toESM(require("protobufjs"), 1);
|
|
@@ -645,14 +672,14 @@ __export(neatd_exports, {
|
|
|
645
672
|
});
|
|
646
673
|
module.exports = __toCommonJS(neatd_exports);
|
|
647
674
|
init_cjs_shims();
|
|
648
|
-
var
|
|
649
|
-
var
|
|
675
|
+
var import_node_fs28 = require("fs");
|
|
676
|
+
var import_node_path47 = __toESM(require("path"), 1);
|
|
650
677
|
var import_node_module2 = require("module");
|
|
651
678
|
|
|
652
679
|
// src/daemon.ts
|
|
653
680
|
init_cjs_shims();
|
|
654
|
-
var
|
|
655
|
-
var
|
|
681
|
+
var import_node_fs26 = require("fs");
|
|
682
|
+
var import_node_path45 = __toESM(require("path"), 1);
|
|
656
683
|
var import_node_module = require("module");
|
|
657
684
|
|
|
658
685
|
// src/graph.ts
|
|
@@ -1182,19 +1209,19 @@ function confidenceFromMix(edges, now = Date.now()) {
|
|
|
1182
1209
|
function longestIncomingWalk(graph, start, maxDepth) {
|
|
1183
1210
|
let best = { path: [start], edges: [] };
|
|
1184
1211
|
const visited = /* @__PURE__ */ new Set([start]);
|
|
1185
|
-
function step(node,
|
|
1186
|
-
if (
|
|
1187
|
-
best = { path: [...
|
|
1212
|
+
function step(node, path48, edges) {
|
|
1213
|
+
if (path48.length > best.path.length) {
|
|
1214
|
+
best = { path: [...path48], edges: [...edges] };
|
|
1188
1215
|
}
|
|
1189
|
-
if (
|
|
1216
|
+
if (path48.length - 1 >= maxDepth) return;
|
|
1190
1217
|
const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
|
|
1191
1218
|
for (const [srcId, edge] of incoming) {
|
|
1192
1219
|
if (visited.has(srcId)) continue;
|
|
1193
1220
|
visited.add(srcId);
|
|
1194
|
-
|
|
1221
|
+
path48.push(srcId);
|
|
1195
1222
|
edges.push(edge);
|
|
1196
|
-
step(srcId,
|
|
1197
|
-
|
|
1223
|
+
step(srcId, path48, edges);
|
|
1224
|
+
path48.pop();
|
|
1198
1225
|
edges.pop();
|
|
1199
1226
|
visited.delete(srcId);
|
|
1200
1227
|
}
|
|
@@ -1388,26 +1415,26 @@ function dominantFailingCall(graph, serviceId4, visited) {
|
|
|
1388
1415
|
return best;
|
|
1389
1416
|
}
|
|
1390
1417
|
function followFailingCallChain(graph, originServiceId, maxDepth) {
|
|
1391
|
-
const
|
|
1418
|
+
const path48 = [originServiceId];
|
|
1392
1419
|
const edges = [];
|
|
1393
1420
|
const visited = /* @__PURE__ */ new Set([originServiceId]);
|
|
1394
1421
|
let current = originServiceId;
|
|
1395
1422
|
for (let depth = 0; depth < maxDepth; depth++) {
|
|
1396
1423
|
const hop = dominantFailingCall(graph, current, visited);
|
|
1397
1424
|
if (!hop) break;
|
|
1398
|
-
|
|
1425
|
+
path48.push(hop.nextService);
|
|
1399
1426
|
edges.push(hop.edge);
|
|
1400
1427
|
visited.add(hop.nextService);
|
|
1401
1428
|
current = hop.nextService;
|
|
1402
1429
|
}
|
|
1403
1430
|
if (edges.length === 0) return null;
|
|
1404
|
-
return { path:
|
|
1431
|
+
return { path: path48, edges, culprit: current };
|
|
1405
1432
|
}
|
|
1406
1433
|
function crossServiceRootCause(graph, originId, incidents, errorEvent) {
|
|
1407
1434
|
const chain = followFailingCallChain(graph, originId, ROOT_CAUSE_MAX_DEPTH);
|
|
1408
1435
|
if (!chain) return null;
|
|
1409
1436
|
const culprit = chain.culprit;
|
|
1410
|
-
const
|
|
1437
|
+
const path48 = [...chain.path];
|
|
1411
1438
|
const edgeProvenances = chain.edges.map((e) => e.provenance);
|
|
1412
1439
|
const baseConfidence = confidenceFromMix(chain.edges);
|
|
1413
1440
|
const confidence = Math.max(0, Math.min(1, baseConfidence * INCIDENT_ROOT_CAUSE_CONFIDENCE));
|
|
@@ -1415,14 +1442,14 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
|
|
|
1415
1442
|
if (loc) {
|
|
1416
1443
|
let rootCauseNode = culprit;
|
|
1417
1444
|
if (loc.fileNode) {
|
|
1418
|
-
|
|
1445
|
+
path48.push(loc.fileNode);
|
|
1419
1446
|
edgeProvenances.push(import_types.Provenance.OBSERVED);
|
|
1420
1447
|
rootCauseNode = loc.fileNode;
|
|
1421
1448
|
}
|
|
1422
1449
|
return import_types.RootCauseResultSchema.parse({
|
|
1423
1450
|
rootCauseNode,
|
|
1424
1451
|
rootCauseReason: loc.rootCauseReason,
|
|
1425
|
-
traversalPath:
|
|
1452
|
+
traversalPath: path48,
|
|
1426
1453
|
edgeProvenances,
|
|
1427
1454
|
confidence,
|
|
1428
1455
|
...loc.fixRecommendation ? { fixRecommendation: loc.fixRecommendation } : {}
|
|
@@ -1434,7 +1461,7 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
|
|
|
1434
1461
|
return import_types.RootCauseResultSchema.parse({
|
|
1435
1462
|
rootCauseNode: culprit,
|
|
1436
1463
|
rootCauseReason: `${culpritName} is failing downstream calls (${errs} observed error${errs === 1 ? "" : "s"})`,
|
|
1437
|
-
traversalPath:
|
|
1464
|
+
traversalPath: path48,
|
|
1438
1465
|
edgeProvenances,
|
|
1439
1466
|
confidence,
|
|
1440
1467
|
fixRecommendation: `Inspect ${culpritName}'s failing handler`
|
|
@@ -2384,6 +2411,40 @@ function ensureGraphqlOperationNode(graph, serviceName, operationType, operation
|
|
|
2384
2411
|
graph.addNode(id, node);
|
|
2385
2412
|
return id;
|
|
2386
2413
|
}
|
|
2414
|
+
function spanServesGrpcMethod(kind) {
|
|
2415
|
+
return kind !== WIRE_SPAN_KIND_CLIENT && kind !== WIRE_SPAN_KIND_PRODUCER && kind !== WIRE_SPAN_KIND_CONSUMER;
|
|
2416
|
+
}
|
|
2417
|
+
function ensureGrpcMethodNode(graph, rpcService, rpcMethod) {
|
|
2418
|
+
const id = (0, import_types3.grpcMethodId)(rpcService, rpcMethod);
|
|
2419
|
+
if (graph.hasNode(id)) return id;
|
|
2420
|
+
const node = {
|
|
2421
|
+
id,
|
|
2422
|
+
type: import_types3.NodeType.GrpcMethodNode,
|
|
2423
|
+
name: `${rpcService}/${rpcMethod}`,
|
|
2424
|
+
rpcService,
|
|
2425
|
+
rpcMethod,
|
|
2426
|
+
discoveredVia: "otel"
|
|
2427
|
+
};
|
|
2428
|
+
graph.addNode(id, node);
|
|
2429
|
+
return id;
|
|
2430
|
+
}
|
|
2431
|
+
function spanServesWebsocketChannel(kind) {
|
|
2432
|
+
return kind !== WIRE_SPAN_KIND_CLIENT && kind !== WIRE_SPAN_KIND_PRODUCER && kind !== WIRE_SPAN_KIND_CONSUMER;
|
|
2433
|
+
}
|
|
2434
|
+
function ensureWebsocketChannelNode(graph, serviceName, channel) {
|
|
2435
|
+
const id = (0, import_types3.websocketChannelId)(serviceName, channel);
|
|
2436
|
+
if (graph.hasNode(id)) return id;
|
|
2437
|
+
const node = {
|
|
2438
|
+
id,
|
|
2439
|
+
type: import_types3.NodeType.WebSocketChannelNode,
|
|
2440
|
+
name: channel,
|
|
2441
|
+
service: serviceName,
|
|
2442
|
+
channel,
|
|
2443
|
+
discoveredVia: "otel"
|
|
2444
|
+
};
|
|
2445
|
+
graph.addNode(id, node);
|
|
2446
|
+
return id;
|
|
2447
|
+
}
|
|
2387
2448
|
function messagingDestinationKind(system) {
|
|
2388
2449
|
return `${system}-topic`;
|
|
2389
2450
|
}
|
|
@@ -2819,6 +2880,34 @@ async function handleSpan(ctx, span) {
|
|
|
2819
2880
|
callSiteEvidence
|
|
2820
2881
|
);
|
|
2821
2882
|
if (result) affectedNode = targetId;
|
|
2883
|
+
} else if (span.rpcSystem === "grpc" && span.rpcService && span.rpcMethod && spanServesGrpcMethod(span.kind)) {
|
|
2884
|
+
const targetId = ensureGrpcMethodNode(ctx.graph, span.rpcService, span.rpcMethod);
|
|
2885
|
+
const result = upsertObservedEdge(
|
|
2886
|
+
ctx.graph,
|
|
2887
|
+
import_types3.EdgeType.CONTAINS,
|
|
2888
|
+
observedSource(),
|
|
2889
|
+
targetId,
|
|
2890
|
+
ts,
|
|
2891
|
+
isError,
|
|
2892
|
+
callSiteEvidence
|
|
2893
|
+
);
|
|
2894
|
+
if (result) affectedNode = targetId;
|
|
2895
|
+
} else if (span.websocketChannel && spanServesWebsocketChannel(span.kind)) {
|
|
2896
|
+
const targetId = ensureWebsocketChannelNode(
|
|
2897
|
+
ctx.graph,
|
|
2898
|
+
span.service,
|
|
2899
|
+
span.websocketChannel
|
|
2900
|
+
);
|
|
2901
|
+
const result = upsertObservedEdge(
|
|
2902
|
+
ctx.graph,
|
|
2903
|
+
import_types3.EdgeType.CONNECTS_TO,
|
|
2904
|
+
observedSource(),
|
|
2905
|
+
targetId,
|
|
2906
|
+
ts,
|
|
2907
|
+
isError,
|
|
2908
|
+
callSiteEvidence
|
|
2909
|
+
);
|
|
2910
|
+
if (result) affectedNode = targetId;
|
|
2822
2911
|
} else {
|
|
2823
2912
|
const host = pickAddress(span);
|
|
2824
2913
|
let resolvedViaAddress = false;
|
|
@@ -5386,17 +5475,138 @@ async function addRoutes(graph, services) {
|
|
|
5386
5475
|
return { nodesAdded, edgesAdded };
|
|
5387
5476
|
}
|
|
5388
5477
|
|
|
5478
|
+
// src/extract/proto.ts
|
|
5479
|
+
init_cjs_shims();
|
|
5480
|
+
var import_node_fs15 = require("fs");
|
|
5481
|
+
var import_node_path23 = __toESM(require("path"), 1);
|
|
5482
|
+
var import_types12 = require("@neat.is/types");
|
|
5483
|
+
var PROTO_EXTENSION = ".proto";
|
|
5484
|
+
function packageOf(content) {
|
|
5485
|
+
const m = content.match(/^\s*package\s+([A-Za-z_][A-Za-z0-9_.]*)\s*;/m);
|
|
5486
|
+
return m ? m[1] : null;
|
|
5487
|
+
}
|
|
5488
|
+
function lineAt(content, offset) {
|
|
5489
|
+
return content.slice(0, offset).split("\n").length;
|
|
5490
|
+
}
|
|
5491
|
+
function grpcMethodsFromProto(content, fqPackage) {
|
|
5492
|
+
const out = [];
|
|
5493
|
+
const serviceRe = /\bservice\s+([A-Za-z_][A-Za-z0-9_]*)\s*\{/g;
|
|
5494
|
+
let sm;
|
|
5495
|
+
while ((sm = serviceRe.exec(content)) !== null) {
|
|
5496
|
+
const serviceName = sm[1];
|
|
5497
|
+
const rpcService = fqPackage ? `${fqPackage}.${serviceName}` : serviceName;
|
|
5498
|
+
const bodyStart = serviceRe.lastIndex;
|
|
5499
|
+
let depth = 1;
|
|
5500
|
+
let i = bodyStart;
|
|
5501
|
+
for (; i < content.length && depth > 0; i++) {
|
|
5502
|
+
const ch = content[i];
|
|
5503
|
+
if (ch === "{") depth++;
|
|
5504
|
+
else if (ch === "}") depth--;
|
|
5505
|
+
}
|
|
5506
|
+
const body = content.slice(bodyStart, i - 1);
|
|
5507
|
+
const rpcRe = /\brpc\s+([A-Za-z_][A-Za-z0-9_]*)\s*\(/g;
|
|
5508
|
+
let rm;
|
|
5509
|
+
while ((rm = rpcRe.exec(body)) !== null) {
|
|
5510
|
+
const rpcMethod = rm[1];
|
|
5511
|
+
const line = lineAt(content, bodyStart + rm.index);
|
|
5512
|
+
out.push({ rpcService, rpcMethod, line });
|
|
5513
|
+
}
|
|
5514
|
+
serviceRe.lastIndex = i;
|
|
5515
|
+
}
|
|
5516
|
+
return out;
|
|
5517
|
+
}
|
|
5518
|
+
async function walkProtoFiles(dir) {
|
|
5519
|
+
const out = [];
|
|
5520
|
+
async function walk3(current) {
|
|
5521
|
+
const entries = await import_node_fs15.promises.readdir(current, { withFileTypes: true }).catch(() => []);
|
|
5522
|
+
for (const entry2 of entries) {
|
|
5523
|
+
const full = import_node_path23.default.join(current, entry2.name);
|
|
5524
|
+
if (entry2.isDirectory()) {
|
|
5525
|
+
if (IGNORED_DIRS.has(entry2.name)) continue;
|
|
5526
|
+
if (await isPythonVenvDir(full)) continue;
|
|
5527
|
+
await walk3(full);
|
|
5528
|
+
} else if (entry2.isFile() && import_node_path23.default.extname(entry2.name) === PROTO_EXTENSION) {
|
|
5529
|
+
out.push(full);
|
|
5530
|
+
}
|
|
5531
|
+
}
|
|
5532
|
+
}
|
|
5533
|
+
await walk3(dir);
|
|
5534
|
+
return out;
|
|
5535
|
+
}
|
|
5536
|
+
async function addGrpcMethods(graph, services) {
|
|
5537
|
+
let nodesAdded = 0;
|
|
5538
|
+
let edgesAdded = 0;
|
|
5539
|
+
for (const service of services) {
|
|
5540
|
+
const protoPaths = await walkProtoFiles(service.dir);
|
|
5541
|
+
for (const protoPath of protoPaths) {
|
|
5542
|
+
if (isTestPath(protoPath)) continue;
|
|
5543
|
+
const relFile = toPosix2(import_node_path23.default.relative(service.dir, protoPath));
|
|
5544
|
+
let content;
|
|
5545
|
+
try {
|
|
5546
|
+
content = await import_node_fs15.promises.readFile(protoPath, "utf8");
|
|
5547
|
+
} catch (err) {
|
|
5548
|
+
recordExtractionError("proto extraction", protoPath, err);
|
|
5549
|
+
continue;
|
|
5550
|
+
}
|
|
5551
|
+
let methods;
|
|
5552
|
+
try {
|
|
5553
|
+
methods = grpcMethodsFromProto(content, packageOf(content));
|
|
5554
|
+
} catch (err) {
|
|
5555
|
+
recordExtractionError("proto extraction", protoPath, err);
|
|
5556
|
+
continue;
|
|
5557
|
+
}
|
|
5558
|
+
if (methods.length === 0) continue;
|
|
5559
|
+
for (const method of methods) {
|
|
5560
|
+
const mid = (0, import_types12.grpcMethodId)(method.rpcService, method.rpcMethod);
|
|
5561
|
+
if (!graph.hasNode(mid)) {
|
|
5562
|
+
const node = {
|
|
5563
|
+
id: mid,
|
|
5564
|
+
type: import_types12.NodeType.GrpcMethodNode,
|
|
5565
|
+
name: `${method.rpcService}/${method.rpcMethod}`,
|
|
5566
|
+
rpcService: method.rpcService,
|
|
5567
|
+
rpcMethod: method.rpcMethod,
|
|
5568
|
+
path: relFile,
|
|
5569
|
+
line: method.line,
|
|
5570
|
+
discoveredVia: "static"
|
|
5571
|
+
};
|
|
5572
|
+
graph.addNode(mid, node);
|
|
5573
|
+
nodesAdded++;
|
|
5574
|
+
}
|
|
5575
|
+
const containsId = (0, import_types12.extractedEdgeId)(service.node.id, mid, import_types12.EdgeType.CONTAINS);
|
|
5576
|
+
if (!graph.hasEdge(containsId)) {
|
|
5577
|
+
const edge = {
|
|
5578
|
+
id: containsId,
|
|
5579
|
+
source: service.node.id,
|
|
5580
|
+
target: mid,
|
|
5581
|
+
type: import_types12.EdgeType.CONTAINS,
|
|
5582
|
+
provenance: import_types12.Provenance.EXTRACTED,
|
|
5583
|
+
confidence: (0, import_types12.confidenceForExtracted)("structural"),
|
|
5584
|
+
evidence: {
|
|
5585
|
+
file: relFile,
|
|
5586
|
+
line: method.line,
|
|
5587
|
+
snippet: snippet(content, method.line)
|
|
5588
|
+
}
|
|
5589
|
+
};
|
|
5590
|
+
graph.addEdgeWithKey(containsId, service.node.id, mid, edge);
|
|
5591
|
+
edgesAdded++;
|
|
5592
|
+
}
|
|
5593
|
+
}
|
|
5594
|
+
}
|
|
5595
|
+
}
|
|
5596
|
+
return { nodesAdded, edgesAdded };
|
|
5597
|
+
}
|
|
5598
|
+
|
|
5389
5599
|
// src/extract/calls/index.ts
|
|
5390
5600
|
init_cjs_shims();
|
|
5391
|
-
var
|
|
5601
|
+
var import_types20 = require("@neat.is/types");
|
|
5392
5602
|
|
|
5393
5603
|
// src/extract/calls/http.ts
|
|
5394
5604
|
init_cjs_shims();
|
|
5395
|
-
var
|
|
5605
|
+
var import_node_path24 = __toESM(require("path"), 1);
|
|
5396
5606
|
var import_tree_sitter3 = __toESM(require("tree-sitter"), 1);
|
|
5397
5607
|
var import_tree_sitter_javascript3 = __toESM(require("tree-sitter-javascript"), 1);
|
|
5398
5608
|
var import_tree_sitter_python2 = __toESM(require("tree-sitter-python"), 1);
|
|
5399
|
-
var
|
|
5609
|
+
var import_types13 = require("@neat.is/types");
|
|
5400
5610
|
var STRING_LITERAL_NODE_TYPES = /* @__PURE__ */ new Set(["string_fragment", "string_content"]);
|
|
5401
5611
|
var JSX_EXTERNAL_LINK_TAGS = /* @__PURE__ */ new Set(["a", "Link", "NavLink", "ExternalLink", "Anchor"]);
|
|
5402
5612
|
function isInsideJsxExternalLink(node) {
|
|
@@ -5466,7 +5676,7 @@ async function addHttpCallEdges(graph, services) {
|
|
|
5466
5676
|
const seen = /* @__PURE__ */ new Set();
|
|
5467
5677
|
for (const file of files) {
|
|
5468
5678
|
if (isTestPath(file.path)) continue;
|
|
5469
|
-
const parser =
|
|
5679
|
+
const parser = import_node_path24.default.extname(file.path) === ".py" ? pyParser : jsParser;
|
|
5470
5680
|
let sites;
|
|
5471
5681
|
try {
|
|
5472
5682
|
sites = callsFromSource(file.content, parser, knownHosts);
|
|
@@ -5475,14 +5685,14 @@ async function addHttpCallEdges(graph, services) {
|
|
|
5475
5685
|
continue;
|
|
5476
5686
|
}
|
|
5477
5687
|
if (sites.length === 0) continue;
|
|
5478
|
-
const relFile = toPosix2(
|
|
5688
|
+
const relFile = toPosix2(import_node_path24.default.relative(service.dir, file.path));
|
|
5479
5689
|
for (const site of sites) {
|
|
5480
5690
|
const targetId = hostToNodeId.get(site.host);
|
|
5481
5691
|
if (!targetId || targetId === service.node.id) continue;
|
|
5482
5692
|
const dedupKey = `${relFile}|${targetId}`;
|
|
5483
5693
|
if (seen.has(dedupKey)) continue;
|
|
5484
5694
|
seen.add(dedupKey);
|
|
5485
|
-
const confidence = (0,
|
|
5695
|
+
const confidence = (0, import_types13.confidenceForExtracted)("url-literal-service-target");
|
|
5486
5696
|
const ev = {
|
|
5487
5697
|
file: relFile,
|
|
5488
5698
|
line: site.line,
|
|
@@ -5496,25 +5706,25 @@ async function addHttpCallEdges(graph, services) {
|
|
|
5496
5706
|
);
|
|
5497
5707
|
nodesAdded += n;
|
|
5498
5708
|
edgesAdded += e;
|
|
5499
|
-
if (!(0,
|
|
5709
|
+
if (!(0, import_types13.passesExtractedFloor)(confidence)) {
|
|
5500
5710
|
noteExtractedDropped({
|
|
5501
5711
|
source: fileNodeId,
|
|
5502
5712
|
target: targetId,
|
|
5503
|
-
type:
|
|
5713
|
+
type: import_types13.EdgeType.CALLS,
|
|
5504
5714
|
confidence,
|
|
5505
5715
|
confidenceKind: "url-literal-service-target",
|
|
5506
5716
|
evidence: ev
|
|
5507
5717
|
});
|
|
5508
5718
|
continue;
|
|
5509
5719
|
}
|
|
5510
|
-
const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, targetId,
|
|
5720
|
+
const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, targetId, import_types13.EdgeType.CALLS);
|
|
5511
5721
|
if (!graph.hasEdge(edgeId)) {
|
|
5512
5722
|
const edge = {
|
|
5513
5723
|
id: edgeId,
|
|
5514
5724
|
source: fileNodeId,
|
|
5515
5725
|
target: targetId,
|
|
5516
|
-
type:
|
|
5517
|
-
provenance:
|
|
5726
|
+
type: import_types13.EdgeType.CALLS,
|
|
5727
|
+
provenance: import_types13.Provenance.EXTRACTED,
|
|
5518
5728
|
confidence,
|
|
5519
5729
|
evidence: ev
|
|
5520
5730
|
};
|
|
@@ -5529,10 +5739,10 @@ async function addHttpCallEdges(graph, services) {
|
|
|
5529
5739
|
|
|
5530
5740
|
// src/extract/calls/route-match.ts
|
|
5531
5741
|
init_cjs_shims();
|
|
5532
|
-
var
|
|
5742
|
+
var import_node_path25 = __toESM(require("path"), 1);
|
|
5533
5743
|
var import_tree_sitter4 = __toESM(require("tree-sitter"), 1);
|
|
5534
5744
|
var import_tree_sitter_javascript4 = __toESM(require("tree-sitter-javascript"), 1);
|
|
5535
|
-
var
|
|
5745
|
+
var import_types14 = require("@neat.is/types");
|
|
5536
5746
|
var PARSE_CHUNK4 = 16384;
|
|
5537
5747
|
function parseSource4(parser, source) {
|
|
5538
5748
|
return parser.parse(
|
|
@@ -5697,9 +5907,9 @@ function buildRouteIndex(graph) {
|
|
|
5697
5907
|
const index = /* @__PURE__ */ new Map();
|
|
5698
5908
|
graph.forEachNode((_id, attrs) => {
|
|
5699
5909
|
const node = attrs;
|
|
5700
|
-
if (node.type !==
|
|
5910
|
+
if (node.type !== import_types14.NodeType.RouteNode) return;
|
|
5701
5911
|
const route = attrs;
|
|
5702
|
-
const owner = (0,
|
|
5912
|
+
const owner = (0, import_types14.serviceId)(route.service);
|
|
5703
5913
|
const entry2 = {
|
|
5704
5914
|
method: route.method.toUpperCase(),
|
|
5705
5915
|
normalizedPath: normalizePathTemplate(route.pathTemplate),
|
|
@@ -5728,7 +5938,7 @@ async function addRouteCallEdges(graph, services) {
|
|
|
5728
5938
|
const seen = /* @__PURE__ */ new Set();
|
|
5729
5939
|
for (const file of files) {
|
|
5730
5940
|
if (isTestPath(file.path)) continue;
|
|
5731
|
-
if (!JS_CLIENT_EXTENSIONS.has(
|
|
5941
|
+
if (!JS_CLIENT_EXTENSIONS.has(import_node_path25.default.extname(file.path))) continue;
|
|
5732
5942
|
let sites;
|
|
5733
5943
|
try {
|
|
5734
5944
|
sites = clientCallSitesFromSource(file.content, jsParser, knownHosts);
|
|
@@ -5737,7 +5947,7 @@ async function addRouteCallEdges(graph, services) {
|
|
|
5737
5947
|
continue;
|
|
5738
5948
|
}
|
|
5739
5949
|
if (sites.length === 0) continue;
|
|
5740
|
-
const relFile = toPosix2(
|
|
5950
|
+
const relFile = toPosix2(import_node_path25.default.relative(service.dir, file.path));
|
|
5741
5951
|
for (const site of sites) {
|
|
5742
5952
|
const serverServiceId = hostToNodeId.get(site.host);
|
|
5743
5953
|
if (!serverServiceId || serverServiceId === service.node.id) continue;
|
|
@@ -5757,7 +5967,7 @@ async function addRouteCallEdges(graph, services) {
|
|
|
5757
5967
|
);
|
|
5758
5968
|
nodesAdded += n;
|
|
5759
5969
|
edgesAdded += e;
|
|
5760
|
-
const confidence = (0,
|
|
5970
|
+
const confidence = (0, import_types14.confidenceForExtracted)("verified-call-site");
|
|
5761
5971
|
const ev = {
|
|
5762
5972
|
file: relFile,
|
|
5763
5973
|
line: site.line,
|
|
@@ -5765,25 +5975,25 @@ async function addRouteCallEdges(graph, services) {
|
|
|
5765
5975
|
method: site.method ?? match.method,
|
|
5766
5976
|
pathTemplate: site.pathTemplate
|
|
5767
5977
|
};
|
|
5768
|
-
if (!(0,
|
|
5978
|
+
if (!(0, import_types14.passesExtractedFloor)(confidence)) {
|
|
5769
5979
|
noteExtractedDropped({
|
|
5770
5980
|
source: fileNodeId,
|
|
5771
5981
|
target: match.routeNodeId,
|
|
5772
|
-
type:
|
|
5982
|
+
type: import_types14.EdgeType.CALLS,
|
|
5773
5983
|
confidence,
|
|
5774
5984
|
confidenceKind: "verified-call-site",
|
|
5775
5985
|
evidence: ev
|
|
5776
5986
|
});
|
|
5777
5987
|
continue;
|
|
5778
5988
|
}
|
|
5779
|
-
const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, match.routeNodeId,
|
|
5989
|
+
const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, match.routeNodeId, import_types14.EdgeType.CALLS);
|
|
5780
5990
|
if (!graph.hasEdge(edgeId)) {
|
|
5781
5991
|
const edge = {
|
|
5782
5992
|
id: edgeId,
|
|
5783
5993
|
source: fileNodeId,
|
|
5784
5994
|
target: match.routeNodeId,
|
|
5785
|
-
type:
|
|
5786
|
-
provenance:
|
|
5995
|
+
type: import_types14.EdgeType.CALLS,
|
|
5996
|
+
provenance: import_types14.Provenance.EXTRACTED,
|
|
5787
5997
|
confidence,
|
|
5788
5998
|
evidence: ev
|
|
5789
5999
|
};
|
|
@@ -5798,8 +6008,8 @@ async function addRouteCallEdges(graph, services) {
|
|
|
5798
6008
|
|
|
5799
6009
|
// src/extract/calls/kafka.ts
|
|
5800
6010
|
init_cjs_shims();
|
|
5801
|
-
var
|
|
5802
|
-
var
|
|
6011
|
+
var import_node_path26 = __toESM(require("path"), 1);
|
|
6012
|
+
var import_types15 = require("@neat.is/types");
|
|
5803
6013
|
var PRODUCER_TOPIC_RE = /(?:producer|kafkaProducer)[\s\S]{0,40}?\.send\s*\(\s*\{[\s\S]{0,200}?topic\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
5804
6014
|
var CONSUMER_TOPIC_RE = /(?:consumer|kafkaConsumer)[\s\S]{0,40}?\.(?:subscribe|run)\s*\(\s*\{[\s\S]{0,200}?topic[s]?\s*:\s*(?:\[\s*)?['"`]([^'"`]+)['"`]/g;
|
|
5805
6015
|
function findAll(re, text) {
|
|
@@ -5820,7 +6030,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
|
|
|
5820
6030
|
seen.add(key);
|
|
5821
6031
|
const line = lineOf(file.content, topic);
|
|
5822
6032
|
out.push({
|
|
5823
|
-
infraId: (0,
|
|
6033
|
+
infraId: (0, import_types15.infraId)("kafka-topic", topic),
|
|
5824
6034
|
name: topic,
|
|
5825
6035
|
kind: "kafka-topic",
|
|
5826
6036
|
edgeType,
|
|
@@ -5829,7 +6039,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
|
|
|
5829
6039
|
// tier (ADR-066).
|
|
5830
6040
|
confidenceKind: "verified-call-site",
|
|
5831
6041
|
evidence: {
|
|
5832
|
-
file:
|
|
6042
|
+
file: import_node_path26.default.relative(serviceDir, file.path),
|
|
5833
6043
|
line,
|
|
5834
6044
|
snippet: snippet(file.content, line)
|
|
5835
6045
|
}
|
|
@@ -5842,8 +6052,8 @@ function kafkaEndpointsFromFile(file, serviceDir) {
|
|
|
5842
6052
|
|
|
5843
6053
|
// src/extract/calls/redis.ts
|
|
5844
6054
|
init_cjs_shims();
|
|
5845
|
-
var
|
|
5846
|
-
var
|
|
6055
|
+
var import_node_path27 = __toESM(require("path"), 1);
|
|
6056
|
+
var import_types16 = require("@neat.is/types");
|
|
5847
6057
|
var REDIS_URL_RE = /redis(?:s)?:\/\/(?:[^@'"`\s]+@)?([^:/'"`\s]+)(?::(\d+))?/g;
|
|
5848
6058
|
function redisEndpointsFromFile(file, serviceDir) {
|
|
5849
6059
|
const out = [];
|
|
@@ -5856,7 +6066,7 @@ function redisEndpointsFromFile(file, serviceDir) {
|
|
|
5856
6066
|
seen.add(host);
|
|
5857
6067
|
const line = lineOf(file.content, host);
|
|
5858
6068
|
out.push({
|
|
5859
|
-
infraId: (0,
|
|
6069
|
+
infraId: (0, import_types16.infraId)("redis", host),
|
|
5860
6070
|
name: host,
|
|
5861
6071
|
kind: "redis",
|
|
5862
6072
|
edgeType: "CALLS",
|
|
@@ -5865,7 +6075,7 @@ function redisEndpointsFromFile(file, serviceDir) {
|
|
|
5865
6075
|
// support tier (ADR-066).
|
|
5866
6076
|
confidenceKind: "url-with-structural-support",
|
|
5867
6077
|
evidence: {
|
|
5868
|
-
file:
|
|
6078
|
+
file: import_node_path27.default.relative(serviceDir, file.path),
|
|
5869
6079
|
line,
|
|
5870
6080
|
snippet: snippet(file.content, line)
|
|
5871
6081
|
}
|
|
@@ -5876,8 +6086,8 @@ function redisEndpointsFromFile(file, serviceDir) {
|
|
|
5876
6086
|
|
|
5877
6087
|
// src/extract/calls/aws.ts
|
|
5878
6088
|
init_cjs_shims();
|
|
5879
|
-
var
|
|
5880
|
-
var
|
|
6089
|
+
var import_node_path28 = __toESM(require("path"), 1);
|
|
6090
|
+
var import_types17 = require("@neat.is/types");
|
|
5881
6091
|
var S3_BUCKET_RE = /Bucket\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
5882
6092
|
var DYNAMO_TABLE_RE = /TableName\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
5883
6093
|
function hasMarker(text, markers) {
|
|
@@ -5901,7 +6111,7 @@ function awsEndpointsFromFile(file, serviceDir) {
|
|
|
5901
6111
|
seen.add(key);
|
|
5902
6112
|
const line = lineOf(file.content, name);
|
|
5903
6113
|
out.push({
|
|
5904
|
-
infraId: (0,
|
|
6114
|
+
infraId: (0, import_types17.infraId)(kind, name),
|
|
5905
6115
|
name,
|
|
5906
6116
|
kind,
|
|
5907
6117
|
edgeType: "CALLS",
|
|
@@ -5910,7 +6120,7 @@ function awsEndpointsFromFile(file, serviceDir) {
|
|
|
5910
6120
|
// (ADR-066).
|
|
5911
6121
|
confidenceKind: "verified-call-site",
|
|
5912
6122
|
evidence: {
|
|
5913
|
-
file:
|
|
6123
|
+
file: import_node_path28.default.relative(serviceDir, file.path),
|
|
5914
6124
|
line,
|
|
5915
6125
|
snippet: snippet(file.content, line)
|
|
5916
6126
|
}
|
|
@@ -5935,8 +6145,8 @@ function awsEndpointsFromFile(file, serviceDir) {
|
|
|
5935
6145
|
|
|
5936
6146
|
// src/extract/calls/grpc.ts
|
|
5937
6147
|
init_cjs_shims();
|
|
5938
|
-
var
|
|
5939
|
-
var
|
|
6148
|
+
var import_node_path29 = __toESM(require("path"), 1);
|
|
6149
|
+
var import_types18 = require("@neat.is/types");
|
|
5940
6150
|
var GRPC_CLIENT_RE = /new\s+([A-Z][A-Za-z0-9_]*)Client\s*\(\s*['"`]?([^,'"`)]+)?/g;
|
|
5941
6151
|
var AWS_SDK_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@aws-sdk\/client-([a-z0-9-]+)['"`]/g;
|
|
5942
6152
|
var GRPC_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@grpc\/grpc-js['"`]|_grpc_pb['"`]/;
|
|
@@ -5985,7 +6195,7 @@ function grpcEndpointsFromFile(file, serviceDir) {
|
|
|
5985
6195
|
const { kind } = classified;
|
|
5986
6196
|
const line = lineOf(file.content, m[0]);
|
|
5987
6197
|
out.push({
|
|
5988
|
-
infraId: (0,
|
|
6198
|
+
infraId: (0, import_types18.infraId)(kind, name),
|
|
5989
6199
|
name,
|
|
5990
6200
|
kind,
|
|
5991
6201
|
edgeType: "CALLS",
|
|
@@ -5994,7 +6204,7 @@ function grpcEndpointsFromFile(file, serviceDir) {
|
|
|
5994
6204
|
// tier (ADR-066).
|
|
5995
6205
|
confidenceKind: "verified-call-site",
|
|
5996
6206
|
evidence: {
|
|
5997
|
-
file:
|
|
6207
|
+
file: import_node_path29.default.relative(serviceDir, file.path),
|
|
5998
6208
|
line,
|
|
5999
6209
|
snippet: snippet(file.content, line)
|
|
6000
6210
|
}
|
|
@@ -6005,8 +6215,8 @@ function grpcEndpointsFromFile(file, serviceDir) {
|
|
|
6005
6215
|
|
|
6006
6216
|
// src/extract/calls/supabase.ts
|
|
6007
6217
|
init_cjs_shims();
|
|
6008
|
-
var
|
|
6009
|
-
var
|
|
6218
|
+
var import_node_path30 = __toESM(require("path"), 1);
|
|
6219
|
+
var import_types19 = require("@neat.is/types");
|
|
6010
6220
|
var SUPABASE_JS_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@supabase\/supabase-js['"`]/;
|
|
6011
6221
|
var SUPABASE_SSR_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@supabase\/ssr['"`]/;
|
|
6012
6222
|
var SUPABASE_CLIENT_RE = /\b(createClient|createServerClient|createBrowserClient)\s*\(\s*(?:['"`]([^'"`]*)['"`])?/g;
|
|
@@ -6044,7 +6254,7 @@ function supabaseEndpointsFromFile(file, serviceDir) {
|
|
|
6044
6254
|
seen.add(name);
|
|
6045
6255
|
const line = lineOf(file.content, m[0]);
|
|
6046
6256
|
out.push({
|
|
6047
|
-
infraId: (0,
|
|
6257
|
+
infraId: (0, import_types19.infraId)("supabase", name),
|
|
6048
6258
|
name,
|
|
6049
6259
|
kind: "supabase",
|
|
6050
6260
|
edgeType: "CALLS",
|
|
@@ -6054,7 +6264,7 @@ function supabaseEndpointsFromFile(file, serviceDir) {
|
|
|
6054
6264
|
// tier (ADR-066), the same grade aws.ts / grpc.ts emit at.
|
|
6055
6265
|
confidenceKind: "verified-call-site",
|
|
6056
6266
|
evidence: {
|
|
6057
|
-
file:
|
|
6267
|
+
file: import_node_path30.default.relative(serviceDir, file.path),
|
|
6058
6268
|
line,
|
|
6059
6269
|
snippet: snippet(file.content, line)
|
|
6060
6270
|
}
|
|
@@ -6067,11 +6277,11 @@ function supabaseEndpointsFromFile(file, serviceDir) {
|
|
|
6067
6277
|
function edgeTypeFromEndpoint(ep) {
|
|
6068
6278
|
switch (ep.edgeType) {
|
|
6069
6279
|
case "PUBLISHES_TO":
|
|
6070
|
-
return
|
|
6280
|
+
return import_types20.EdgeType.PUBLISHES_TO;
|
|
6071
6281
|
case "CONSUMES_FROM":
|
|
6072
|
-
return
|
|
6282
|
+
return import_types20.EdgeType.CONSUMES_FROM;
|
|
6073
6283
|
default:
|
|
6074
|
-
return
|
|
6284
|
+
return import_types20.EdgeType.CALLS;
|
|
6075
6285
|
}
|
|
6076
6286
|
}
|
|
6077
6287
|
function isAwsKind(kind) {
|
|
@@ -6099,7 +6309,7 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
6099
6309
|
if (!graph.hasNode(ep.infraId)) {
|
|
6100
6310
|
const node = {
|
|
6101
6311
|
id: ep.infraId,
|
|
6102
|
-
type:
|
|
6312
|
+
type: import_types20.NodeType.InfraNode,
|
|
6103
6313
|
name: ep.name,
|
|
6104
6314
|
// #238 — `aws-*` covers AWS-SDK client kinds (aws-s3, aws-dynamodb,
|
|
6105
6315
|
// aws-cognito-identity-provider, …); `s3-` / `dynamodb-` cover the
|
|
@@ -6111,7 +6321,7 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
6111
6321
|
nodesAdded++;
|
|
6112
6322
|
}
|
|
6113
6323
|
const edgeType = edgeTypeFromEndpoint(ep);
|
|
6114
|
-
const confidence = (0,
|
|
6324
|
+
const confidence = (0, import_types20.confidenceForExtracted)(ep.confidenceKind);
|
|
6115
6325
|
const relFile = toPosix2(ep.evidence.file);
|
|
6116
6326
|
const { fileNodeId, nodesAdded: n, edgesAdded: e } = ensureFileNode(
|
|
6117
6327
|
graph,
|
|
@@ -6121,7 +6331,7 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
6121
6331
|
);
|
|
6122
6332
|
nodesAdded += n;
|
|
6123
6333
|
edgesAdded += e;
|
|
6124
|
-
if (!(0,
|
|
6334
|
+
if (!(0, import_types20.passesExtractedFloor)(confidence)) {
|
|
6125
6335
|
noteExtractedDropped({
|
|
6126
6336
|
source: fileNodeId,
|
|
6127
6337
|
target: ep.infraId,
|
|
@@ -6141,7 +6351,7 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
6141
6351
|
source: fileNodeId,
|
|
6142
6352
|
target: ep.infraId,
|
|
6143
6353
|
type: edgeType,
|
|
6144
|
-
provenance:
|
|
6354
|
+
provenance: import_types20.Provenance.EXTRACTED,
|
|
6145
6355
|
confidence,
|
|
6146
6356
|
evidence: ep.evidence
|
|
6147
6357
|
};
|
|
@@ -6167,16 +6377,16 @@ init_cjs_shims();
|
|
|
6167
6377
|
|
|
6168
6378
|
// src/extract/infra/docker-compose.ts
|
|
6169
6379
|
init_cjs_shims();
|
|
6170
|
-
var
|
|
6171
|
-
var
|
|
6380
|
+
var import_node_path31 = __toESM(require("path"), 1);
|
|
6381
|
+
var import_types22 = require("@neat.is/types");
|
|
6172
6382
|
|
|
6173
6383
|
// src/extract/infra/shared.ts
|
|
6174
6384
|
init_cjs_shims();
|
|
6175
|
-
var
|
|
6385
|
+
var import_types21 = require("@neat.is/types");
|
|
6176
6386
|
function makeInfraNode(kind, name, provider = "self", extras) {
|
|
6177
6387
|
return {
|
|
6178
|
-
id: (0,
|
|
6179
|
-
type:
|
|
6388
|
+
id: (0, import_types21.infraId)(kind, name),
|
|
6389
|
+
type: import_types21.NodeType.InfraNode,
|
|
6180
6390
|
name,
|
|
6181
6391
|
provider,
|
|
6182
6392
|
kind,
|
|
@@ -6205,7 +6415,7 @@ function dependsOnList(value) {
|
|
|
6205
6415
|
}
|
|
6206
6416
|
function serviceNameToServiceNode(name, services) {
|
|
6207
6417
|
for (const s of services) {
|
|
6208
|
-
if (s.node.name === name ||
|
|
6418
|
+
if (s.node.name === name || import_node_path31.default.basename(s.dir) === name) return s.node.id;
|
|
6209
6419
|
}
|
|
6210
6420
|
return null;
|
|
6211
6421
|
}
|
|
@@ -6214,7 +6424,7 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
6214
6424
|
let edgesAdded = 0;
|
|
6215
6425
|
let composePath = null;
|
|
6216
6426
|
for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
|
|
6217
|
-
const abs =
|
|
6427
|
+
const abs = import_node_path31.default.join(scanPath, name);
|
|
6218
6428
|
if (await exists(abs)) {
|
|
6219
6429
|
composePath = abs;
|
|
6220
6430
|
break;
|
|
@@ -6227,13 +6437,13 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
6227
6437
|
} catch (err) {
|
|
6228
6438
|
recordExtractionError(
|
|
6229
6439
|
"infra docker-compose",
|
|
6230
|
-
|
|
6440
|
+
import_node_path31.default.relative(scanPath, composePath),
|
|
6231
6441
|
err
|
|
6232
6442
|
);
|
|
6233
6443
|
return { nodesAdded, edgesAdded };
|
|
6234
6444
|
}
|
|
6235
6445
|
if (!compose?.services) return { nodesAdded, edgesAdded };
|
|
6236
|
-
const evidenceFile =
|
|
6446
|
+
const evidenceFile = import_node_path31.default.relative(scanPath, composePath).split(import_node_path31.default.sep).join("/");
|
|
6237
6447
|
const composeNameToNodeId = /* @__PURE__ */ new Map();
|
|
6238
6448
|
for (const [composeName, svc] of Object.entries(compose.services)) {
|
|
6239
6449
|
const matchedServiceId = serviceNameToServiceNode(composeName, services);
|
|
@@ -6255,15 +6465,15 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
6255
6465
|
for (const dep of dependsOnList(svc.depends_on)) {
|
|
6256
6466
|
const targetId = composeNameToNodeId.get(dep);
|
|
6257
6467
|
if (!targetId) continue;
|
|
6258
|
-
const edgeId = (0, import_types4.extractedEdgeId)(sourceId, targetId,
|
|
6468
|
+
const edgeId = (0, import_types4.extractedEdgeId)(sourceId, targetId, import_types22.EdgeType.DEPENDS_ON);
|
|
6259
6469
|
if (graph.hasEdge(edgeId)) continue;
|
|
6260
6470
|
const edge = {
|
|
6261
6471
|
id: edgeId,
|
|
6262
6472
|
source: sourceId,
|
|
6263
6473
|
target: targetId,
|
|
6264
|
-
type:
|
|
6265
|
-
provenance:
|
|
6266
|
-
confidence: (0,
|
|
6474
|
+
type: import_types22.EdgeType.DEPENDS_ON,
|
|
6475
|
+
provenance: import_types22.Provenance.EXTRACTED,
|
|
6476
|
+
confidence: (0, import_types22.confidenceForExtracted)("structural"),
|
|
6267
6477
|
evidence: { file: evidenceFile }
|
|
6268
6478
|
};
|
|
6269
6479
|
graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
|
|
@@ -6275,9 +6485,9 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
6275
6485
|
|
|
6276
6486
|
// src/extract/infra/dockerfile.ts
|
|
6277
6487
|
init_cjs_shims();
|
|
6278
|
-
var
|
|
6279
|
-
var
|
|
6280
|
-
var
|
|
6488
|
+
var import_node_path32 = __toESM(require("path"), 1);
|
|
6489
|
+
var import_node_fs16 = require("fs");
|
|
6490
|
+
var import_types23 = require("@neat.is/types");
|
|
6281
6491
|
function readDockerfile(content) {
|
|
6282
6492
|
let image = null;
|
|
6283
6493
|
const ports = [];
|
|
@@ -6306,15 +6516,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
6306
6516
|
let nodesAdded = 0;
|
|
6307
6517
|
let edgesAdded = 0;
|
|
6308
6518
|
for (const service of services) {
|
|
6309
|
-
const dockerfilePath =
|
|
6519
|
+
const dockerfilePath = import_node_path32.default.join(service.dir, "Dockerfile");
|
|
6310
6520
|
if (!await exists(dockerfilePath)) continue;
|
|
6311
6521
|
let content;
|
|
6312
6522
|
try {
|
|
6313
|
-
content = await
|
|
6523
|
+
content = await import_node_fs16.promises.readFile(dockerfilePath, "utf8");
|
|
6314
6524
|
} catch (err) {
|
|
6315
6525
|
recordExtractionError(
|
|
6316
6526
|
"infra dockerfile",
|
|
6317
|
-
|
|
6527
|
+
import_node_path32.default.relative(scanPath, dockerfilePath),
|
|
6318
6528
|
err
|
|
6319
6529
|
);
|
|
6320
6530
|
continue;
|
|
@@ -6326,8 +6536,8 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
6326
6536
|
graph.addNode(node.id, node);
|
|
6327
6537
|
nodesAdded++;
|
|
6328
6538
|
}
|
|
6329
|
-
const relDockerfile = toPosix2(
|
|
6330
|
-
const evidenceFile = toPosix2(
|
|
6539
|
+
const relDockerfile = toPosix2(import_node_path32.default.relative(service.dir, dockerfilePath));
|
|
6540
|
+
const evidenceFile = toPosix2(import_node_path32.default.relative(scanPath, dockerfilePath));
|
|
6331
6541
|
const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
|
|
6332
6542
|
graph,
|
|
6333
6543
|
service.pkg.name,
|
|
@@ -6336,15 +6546,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
6336
6546
|
);
|
|
6337
6547
|
nodesAdded += fn;
|
|
6338
6548
|
edgesAdded += fe;
|
|
6339
|
-
const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, node.id,
|
|
6549
|
+
const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, node.id, import_types23.EdgeType.RUNS_ON);
|
|
6340
6550
|
if (!graph.hasEdge(edgeId)) {
|
|
6341
6551
|
const edge = {
|
|
6342
6552
|
id: edgeId,
|
|
6343
6553
|
source: fileNodeId,
|
|
6344
6554
|
target: node.id,
|
|
6345
|
-
type:
|
|
6346
|
-
provenance:
|
|
6347
|
-
confidence: (0,
|
|
6555
|
+
type: import_types23.EdgeType.RUNS_ON,
|
|
6556
|
+
provenance: import_types23.Provenance.EXTRACTED,
|
|
6557
|
+
confidence: (0, import_types23.confidenceForExtracted)("structural"),
|
|
6348
6558
|
evidence: {
|
|
6349
6559
|
file: evidenceFile,
|
|
6350
6560
|
...facts.entrypoint ? { snippet: facts.entrypoint.slice(0, 120) } : {}
|
|
@@ -6359,15 +6569,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
6359
6569
|
graph.addNode(portNode.id, portNode);
|
|
6360
6570
|
nodesAdded++;
|
|
6361
6571
|
}
|
|
6362
|
-
const portEdgeId = (0, import_types4.extractedEdgeId)(fileNodeId, portNode.id,
|
|
6572
|
+
const portEdgeId = (0, import_types4.extractedEdgeId)(fileNodeId, portNode.id, import_types23.EdgeType.CONNECTS_TO);
|
|
6363
6573
|
if (graph.hasEdge(portEdgeId)) continue;
|
|
6364
6574
|
const portEdge = {
|
|
6365
6575
|
id: portEdgeId,
|
|
6366
6576
|
source: fileNodeId,
|
|
6367
6577
|
target: portNode.id,
|
|
6368
|
-
type:
|
|
6369
|
-
provenance:
|
|
6370
|
-
confidence: (0,
|
|
6578
|
+
type: import_types23.EdgeType.CONNECTS_TO,
|
|
6579
|
+
provenance: import_types23.Provenance.EXTRACTED,
|
|
6580
|
+
confidence: (0, import_types23.confidenceForExtracted)("structural"),
|
|
6371
6581
|
evidence: { file: evidenceFile, snippet: `EXPOSE ${port}` }
|
|
6372
6582
|
};
|
|
6373
6583
|
graph.addEdgeWithKey(portEdgeId, portEdge.source, portEdge.target, portEdge);
|
|
@@ -6379,23 +6589,23 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
6379
6589
|
|
|
6380
6590
|
// src/extract/infra/terraform.ts
|
|
6381
6591
|
init_cjs_shims();
|
|
6382
|
-
var
|
|
6383
|
-
var
|
|
6384
|
-
var
|
|
6592
|
+
var import_node_fs17 = require("fs");
|
|
6593
|
+
var import_node_path33 = __toESM(require("path"), 1);
|
|
6594
|
+
var import_types24 = require("@neat.is/types");
|
|
6385
6595
|
var RESOURCE_RE = /resource\s+"(aws_[A-Za-z0-9_]+)"\s+"([A-Za-z0-9_-]+)"/g;
|
|
6386
6596
|
var REFERENCE_RE = /(?<![\w.])(aws_[A-Za-z0-9_]+)\.([A-Za-z0-9_-]+)/g;
|
|
6387
6597
|
async function walkTfFiles(start, depth = 0, max = 5) {
|
|
6388
6598
|
if (depth > max) return [];
|
|
6389
6599
|
const out = [];
|
|
6390
|
-
const entries = await
|
|
6600
|
+
const entries = await import_node_fs17.promises.readdir(start, { withFileTypes: true }).catch(() => []);
|
|
6391
6601
|
for (const entry2 of entries) {
|
|
6392
6602
|
if (entry2.isDirectory()) {
|
|
6393
6603
|
if (IGNORED_DIRS.has(entry2.name) || entry2.name === ".terraform") continue;
|
|
6394
|
-
const child =
|
|
6604
|
+
const child = import_node_path33.default.join(start, entry2.name);
|
|
6395
6605
|
if (await isPythonVenvDir(child)) continue;
|
|
6396
6606
|
out.push(...await walkTfFiles(child, depth + 1, max));
|
|
6397
6607
|
} else if (entry2.isFile() && entry2.name.endsWith(".tf")) {
|
|
6398
|
-
out.push(
|
|
6608
|
+
out.push(import_node_path33.default.join(start, entry2.name));
|
|
6399
6609
|
}
|
|
6400
6610
|
}
|
|
6401
6611
|
return out;
|
|
@@ -6414,7 +6624,7 @@ function blockBody(content, from) {
|
|
|
6414
6624
|
}
|
|
6415
6625
|
return null;
|
|
6416
6626
|
}
|
|
6417
|
-
function
|
|
6627
|
+
function lineAt2(content, index) {
|
|
6418
6628
|
let line = 1;
|
|
6419
6629
|
for (let i = 0; i < index && i < content.length; i++) {
|
|
6420
6630
|
if (content[i] === "\n") line++;
|
|
@@ -6426,8 +6636,8 @@ async function addTerraformResources(graph, scanPath) {
|
|
|
6426
6636
|
let edgesAdded = 0;
|
|
6427
6637
|
const files = await walkTfFiles(scanPath);
|
|
6428
6638
|
for (const file of files) {
|
|
6429
|
-
const content = await
|
|
6430
|
-
const evidenceFile = toPosix2(
|
|
6639
|
+
const content = await import_node_fs17.promises.readFile(file, "utf8");
|
|
6640
|
+
const evidenceFile = toPosix2(import_node_path33.default.relative(scanPath, file));
|
|
6431
6641
|
const resources = [];
|
|
6432
6642
|
const byKey = /* @__PURE__ */ new Map();
|
|
6433
6643
|
RESOURCE_RE.lastIndex = 0;
|
|
@@ -6462,16 +6672,16 @@ async function addTerraformResources(graph, scanPath) {
|
|
|
6462
6672
|
if (!target) continue;
|
|
6463
6673
|
if (seen.has(target.nodeId)) continue;
|
|
6464
6674
|
seen.add(target.nodeId);
|
|
6465
|
-
const edgeId = (0, import_types4.extractedEdgeId)(resource.nodeId, target.nodeId,
|
|
6675
|
+
const edgeId = (0, import_types4.extractedEdgeId)(resource.nodeId, target.nodeId, import_types24.EdgeType.DEPENDS_ON);
|
|
6466
6676
|
if (graph.hasEdge(edgeId)) continue;
|
|
6467
|
-
const line =
|
|
6677
|
+
const line = lineAt2(content, resource.bodyOffset + ref.index);
|
|
6468
6678
|
const edge = {
|
|
6469
6679
|
id: edgeId,
|
|
6470
6680
|
source: resource.nodeId,
|
|
6471
6681
|
target: target.nodeId,
|
|
6472
|
-
type:
|
|
6473
|
-
provenance:
|
|
6474
|
-
confidence: (0,
|
|
6682
|
+
type: import_types24.EdgeType.DEPENDS_ON,
|
|
6683
|
+
provenance: import_types24.Provenance.EXTRACTED,
|
|
6684
|
+
confidence: (0, import_types24.confidenceForExtracted)("structural"),
|
|
6475
6685
|
evidence: { file: evidenceFile, line, snippet: key }
|
|
6476
6686
|
};
|
|
6477
6687
|
graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
|
|
@@ -6484,8 +6694,8 @@ async function addTerraformResources(graph, scanPath) {
|
|
|
6484
6694
|
|
|
6485
6695
|
// src/extract/infra/k8s.ts
|
|
6486
6696
|
init_cjs_shims();
|
|
6487
|
-
var
|
|
6488
|
-
var
|
|
6697
|
+
var import_node_fs18 = require("fs");
|
|
6698
|
+
var import_node_path34 = __toESM(require("path"), 1);
|
|
6489
6699
|
var import_yaml3 = require("yaml");
|
|
6490
6700
|
var K8S_KIND_TO_INFRA_KIND = {
|
|
6491
6701
|
Service: "k8s-service",
|
|
@@ -6499,15 +6709,15 @@ var K8S_KIND_TO_INFRA_KIND = {
|
|
|
6499
6709
|
async function walkYamlFiles2(start, depth = 0, max = 5) {
|
|
6500
6710
|
if (depth > max) return [];
|
|
6501
6711
|
const out = [];
|
|
6502
|
-
const entries = await
|
|
6712
|
+
const entries = await import_node_fs18.promises.readdir(start, { withFileTypes: true }).catch(() => []);
|
|
6503
6713
|
for (const entry2 of entries) {
|
|
6504
6714
|
if (entry2.isDirectory()) {
|
|
6505
6715
|
if (IGNORED_DIRS.has(entry2.name)) continue;
|
|
6506
|
-
const child =
|
|
6716
|
+
const child = import_node_path34.default.join(start, entry2.name);
|
|
6507
6717
|
if (await isPythonVenvDir(child)) continue;
|
|
6508
6718
|
out.push(...await walkYamlFiles2(child, depth + 1, max));
|
|
6509
|
-
} else if (entry2.isFile() && CONFIG_FILE_EXTENSIONS.has(
|
|
6510
|
-
out.push(
|
|
6719
|
+
} else if (entry2.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path34.default.extname(entry2.name))) {
|
|
6720
|
+
out.push(import_node_path34.default.join(start, entry2.name));
|
|
6511
6721
|
}
|
|
6512
6722
|
}
|
|
6513
6723
|
return out;
|
|
@@ -6516,7 +6726,7 @@ async function addK8sResources(graph, scanPath) {
|
|
|
6516
6726
|
let nodesAdded = 0;
|
|
6517
6727
|
const files = await walkYamlFiles2(scanPath);
|
|
6518
6728
|
for (const file of files) {
|
|
6519
|
-
const content = await
|
|
6729
|
+
const content = await import_node_fs18.promises.readFile(file, "utf8");
|
|
6520
6730
|
let docs;
|
|
6521
6731
|
try {
|
|
6522
6732
|
docs = (0, import_yaml3.parseAllDocuments)(content).map((d) => d.toJSON());
|
|
@@ -6551,17 +6761,17 @@ async function addInfra(graph, scanPath, services) {
|
|
|
6551
6761
|
}
|
|
6552
6762
|
|
|
6553
6763
|
// src/extract/index.ts
|
|
6554
|
-
var
|
|
6764
|
+
var import_node_path36 = __toESM(require("path"), 1);
|
|
6555
6765
|
|
|
6556
6766
|
// src/extract/retire.ts
|
|
6557
6767
|
init_cjs_shims();
|
|
6558
|
-
var
|
|
6559
|
-
var
|
|
6560
|
-
var
|
|
6768
|
+
var import_node_fs19 = require("fs");
|
|
6769
|
+
var import_node_path35 = __toESM(require("path"), 1);
|
|
6770
|
+
var import_types25 = require("@neat.is/types");
|
|
6561
6771
|
function dropOrphanedFileNodes(graph) {
|
|
6562
6772
|
const orphans = [];
|
|
6563
6773
|
graph.forEachNode((id, attrs) => {
|
|
6564
|
-
if (attrs.type !==
|
|
6774
|
+
if (attrs.type !== import_types25.NodeType.FileNode) return;
|
|
6565
6775
|
if (graph.inboundEdges(id).length === 0 && graph.outboundEdges(id).length === 0) {
|
|
6566
6776
|
orphans.push(id);
|
|
6567
6777
|
}
|
|
@@ -6574,14 +6784,14 @@ function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = []) {
|
|
|
6574
6784
|
const bases = [scanPath, ...serviceDirs];
|
|
6575
6785
|
graph.forEachEdge((id, attrs) => {
|
|
6576
6786
|
const edge = attrs;
|
|
6577
|
-
if (edge.provenance !==
|
|
6787
|
+
if (edge.provenance !== import_types25.Provenance.EXTRACTED) return;
|
|
6578
6788
|
const evidenceFile = edge.evidence?.file;
|
|
6579
6789
|
if (!evidenceFile) return;
|
|
6580
|
-
if (
|
|
6581
|
-
if (!(0,
|
|
6790
|
+
if (import_node_path35.default.isAbsolute(evidenceFile)) {
|
|
6791
|
+
if (!(0, import_node_fs19.existsSync)(evidenceFile)) toDrop.push(id);
|
|
6582
6792
|
return;
|
|
6583
6793
|
}
|
|
6584
|
-
const found = bases.some((base) => (0,
|
|
6794
|
+
const found = bases.some((base) => (0, import_node_fs19.existsSync)(import_node_path35.default.join(base, evidenceFile)));
|
|
6585
6795
|
if (!found) toDrop.push(id);
|
|
6586
6796
|
});
|
|
6587
6797
|
for (const id of toDrop) graph.dropEdge(id);
|
|
@@ -6601,6 +6811,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
6601
6811
|
const phase2 = await addDatabasesAndCompat(graph, services, scanPath);
|
|
6602
6812
|
const phase3 = await addConfigNodes(graph, services, scanPath);
|
|
6603
6813
|
const routePhase = await addRoutes(graph, services);
|
|
6814
|
+
const grpcPhase = await addGrpcMethods(graph, services);
|
|
6604
6815
|
const phase4 = await addCallEdges(graph, services);
|
|
6605
6816
|
const phase5 = await addInfra(graph, scanPath, services);
|
|
6606
6817
|
const ghostsRetired = retireExtractedEdgesByMissingFile(
|
|
@@ -6622,7 +6833,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
6622
6833
|
}
|
|
6623
6834
|
const droppedEntries = drainDroppedExtracted();
|
|
6624
6835
|
if (isRejectedLogEnabled() && opts.errorsPath && droppedEntries.length > 0) {
|
|
6625
|
-
const rejectedPath =
|
|
6836
|
+
const rejectedPath = import_node_path36.default.join(import_node_path36.default.dirname(opts.errorsPath), "rejected.ndjson");
|
|
6626
6837
|
try {
|
|
6627
6838
|
await writeRejectedExtracted(droppedEntries, rejectedPath);
|
|
6628
6839
|
} catch (err) {
|
|
@@ -6632,8 +6843,8 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
6632
6843
|
}
|
|
6633
6844
|
}
|
|
6634
6845
|
const result = {
|
|
6635
|
-
nodesAdded: phase1Nodes + fileEnum.nodesAdded + importGraph.nodesAdded + phase2.nodesAdded + phase3.nodesAdded + routePhase.nodesAdded + phase4.nodesAdded + phase5.nodesAdded,
|
|
6636
|
-
edgesAdded: fileEnum.edgesAdded + importGraph.edgesAdded + phase2.edgesAdded + phase3.edgesAdded + routePhase.edgesAdded + phase4.edgesAdded + phase5.edgesAdded,
|
|
6846
|
+
nodesAdded: phase1Nodes + fileEnum.nodesAdded + importGraph.nodesAdded + phase2.nodesAdded + phase3.nodesAdded + routePhase.nodesAdded + grpcPhase.nodesAdded + phase4.nodesAdded + phase5.nodesAdded,
|
|
6847
|
+
edgesAdded: fileEnum.edgesAdded + importGraph.edgesAdded + phase2.edgesAdded + phase3.edgesAdded + routePhase.edgesAdded + grpcPhase.edgesAdded + phase4.edgesAdded + phase5.edgesAdded,
|
|
6637
6848
|
frontiersPromoted,
|
|
6638
6849
|
extractionErrors: errorEntries.length,
|
|
6639
6850
|
errorEntries,
|
|
@@ -6656,9 +6867,9 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
6656
6867
|
|
|
6657
6868
|
// src/persist.ts
|
|
6658
6869
|
init_cjs_shims();
|
|
6659
|
-
var
|
|
6660
|
-
var
|
|
6661
|
-
var
|
|
6870
|
+
var import_node_fs20 = require("fs");
|
|
6871
|
+
var import_node_path37 = __toESM(require("path"), 1);
|
|
6872
|
+
var import_types26 = require("@neat.is/types");
|
|
6662
6873
|
var SCHEMA_VERSION = 4;
|
|
6663
6874
|
function migrateV1ToV2(payload) {
|
|
6664
6875
|
const nodes = payload.graph.nodes;
|
|
@@ -6680,12 +6891,12 @@ function migrateV2ToV3(payload) {
|
|
|
6680
6891
|
for (const edge of edges) {
|
|
6681
6892
|
const attrs = edge.attributes;
|
|
6682
6893
|
if (!attrs || attrs.provenance !== "FRONTIER") continue;
|
|
6683
|
-
attrs.provenance =
|
|
6894
|
+
attrs.provenance = import_types26.Provenance.OBSERVED;
|
|
6684
6895
|
const type = typeof attrs.type === "string" ? attrs.type : void 0;
|
|
6685
6896
|
const source = typeof attrs.source === "string" ? attrs.source : void 0;
|
|
6686
6897
|
const target = typeof attrs.target === "string" ? attrs.target : void 0;
|
|
6687
6898
|
if (type && source && target) {
|
|
6688
|
-
const newId = (0,
|
|
6899
|
+
const newId = (0, import_types26.observedEdgeId)(source, target, type);
|
|
6689
6900
|
attrs.id = newId;
|
|
6690
6901
|
if (edge.key) edge.key = newId;
|
|
6691
6902
|
}
|
|
@@ -6694,7 +6905,7 @@ function migrateV2ToV3(payload) {
|
|
|
6694
6905
|
return { ...payload, schemaVersion: 3 };
|
|
6695
6906
|
}
|
|
6696
6907
|
async function ensureDir(filePath) {
|
|
6697
|
-
await
|
|
6908
|
+
await import_node_fs20.promises.mkdir(import_node_path37.default.dirname(filePath), { recursive: true });
|
|
6698
6909
|
}
|
|
6699
6910
|
async function saveGraphToDisk(graph, outPath) {
|
|
6700
6911
|
await ensureDir(outPath);
|
|
@@ -6704,13 +6915,13 @@ async function saveGraphToDisk(graph, outPath) {
|
|
|
6704
6915
|
graph: graph.export()
|
|
6705
6916
|
};
|
|
6706
6917
|
const tmp = `${outPath}.tmp`;
|
|
6707
|
-
await
|
|
6708
|
-
await
|
|
6918
|
+
await import_node_fs20.promises.writeFile(tmp, JSON.stringify(payload), "utf8");
|
|
6919
|
+
await import_node_fs20.promises.rename(tmp, outPath);
|
|
6709
6920
|
}
|
|
6710
6921
|
async function loadGraphFromDisk(graph, outPath) {
|
|
6711
6922
|
let raw;
|
|
6712
6923
|
try {
|
|
6713
|
-
raw = await
|
|
6924
|
+
raw = await import_node_fs20.promises.readFile(outPath, "utf8");
|
|
6714
6925
|
} catch (err) {
|
|
6715
6926
|
if (err.code === "ENOENT") return;
|
|
6716
6927
|
throw err;
|
|
@@ -6775,23 +6986,23 @@ function startPersistLoop(graph, outPath, opts = {}) {
|
|
|
6775
6986
|
|
|
6776
6987
|
// src/projects.ts
|
|
6777
6988
|
init_cjs_shims();
|
|
6778
|
-
var
|
|
6989
|
+
var import_node_path38 = __toESM(require("path"), 1);
|
|
6779
6990
|
function pathsForProject(project, baseDir) {
|
|
6780
6991
|
if (project === DEFAULT_PROJECT) {
|
|
6781
6992
|
return {
|
|
6782
|
-
snapshotPath:
|
|
6783
|
-
errorsPath:
|
|
6784
|
-
staleEventsPath:
|
|
6785
|
-
embeddingsCachePath:
|
|
6786
|
-
policyViolationsPath:
|
|
6993
|
+
snapshotPath: import_node_path38.default.join(baseDir, "graph.json"),
|
|
6994
|
+
errorsPath: import_node_path38.default.join(baseDir, "errors.ndjson"),
|
|
6995
|
+
staleEventsPath: import_node_path38.default.join(baseDir, "stale-events.ndjson"),
|
|
6996
|
+
embeddingsCachePath: import_node_path38.default.join(baseDir, "embeddings.json"),
|
|
6997
|
+
policyViolationsPath: import_node_path38.default.join(baseDir, "policy-violations.ndjson")
|
|
6787
6998
|
};
|
|
6788
6999
|
}
|
|
6789
7000
|
return {
|
|
6790
|
-
snapshotPath:
|
|
6791
|
-
errorsPath:
|
|
6792
|
-
staleEventsPath:
|
|
6793
|
-
embeddingsCachePath:
|
|
6794
|
-
policyViolationsPath:
|
|
7001
|
+
snapshotPath: import_node_path38.default.join(baseDir, `${project}.json`),
|
|
7002
|
+
errorsPath: import_node_path38.default.join(baseDir, `errors.${project}.ndjson`),
|
|
7003
|
+
staleEventsPath: import_node_path38.default.join(baseDir, `stale-events.${project}.ndjson`),
|
|
7004
|
+
embeddingsCachePath: import_node_path38.default.join(baseDir, `embeddings.${project}.json`),
|
|
7005
|
+
policyViolationsPath: import_node_path38.default.join(baseDir, `policy-violations.${project}.ndjson`)
|
|
6795
7006
|
};
|
|
6796
7007
|
}
|
|
6797
7008
|
var Projects = class {
|
|
@@ -6829,19 +7040,19 @@ var Projects = class {
|
|
|
6829
7040
|
init_cjs_shims();
|
|
6830
7041
|
var import_fastify = __toESM(require("fastify"), 1);
|
|
6831
7042
|
var import_cors = __toESM(require("@fastify/cors"), 1);
|
|
6832
|
-
var
|
|
7043
|
+
var import_types29 = require("@neat.is/types");
|
|
6833
7044
|
|
|
6834
7045
|
// src/extend/index.ts
|
|
6835
7046
|
init_cjs_shims();
|
|
6836
|
-
var
|
|
6837
|
-
var
|
|
7047
|
+
var import_node_fs22 = require("fs");
|
|
7048
|
+
var import_node_path40 = __toESM(require("path"), 1);
|
|
6838
7049
|
var import_node_os2 = __toESM(require("os"), 1);
|
|
6839
7050
|
var import_instrumentation_registry = require("@neat.is/instrumentation-registry");
|
|
6840
7051
|
|
|
6841
7052
|
// src/installers/package-manager.ts
|
|
6842
7053
|
init_cjs_shims();
|
|
6843
|
-
var
|
|
6844
|
-
var
|
|
7054
|
+
var import_node_fs21 = require("fs");
|
|
7055
|
+
var import_node_path39 = __toESM(require("path"), 1);
|
|
6845
7056
|
var import_node_child_process = require("child_process");
|
|
6846
7057
|
var LOCKFILE_PRIORITY = [
|
|
6847
7058
|
{ lockfile: "bun.lockb", pm: "bun", args: ["install", "--no-summary"] },
|
|
@@ -6856,29 +7067,29 @@ var LOCKFILE_PRIORITY = [
|
|
|
6856
7067
|
var NPM_FALLBACK_ARGS = ["install", "--no-audit", "--no-fund", "--prefer-offline"];
|
|
6857
7068
|
async function exists2(p) {
|
|
6858
7069
|
try {
|
|
6859
|
-
await
|
|
7070
|
+
await import_node_fs21.promises.access(p);
|
|
6860
7071
|
return true;
|
|
6861
7072
|
} catch {
|
|
6862
7073
|
return false;
|
|
6863
7074
|
}
|
|
6864
7075
|
}
|
|
6865
7076
|
async function detectPackageManager(serviceDir) {
|
|
6866
|
-
let dir =
|
|
7077
|
+
let dir = import_node_path39.default.resolve(serviceDir);
|
|
6867
7078
|
const stops = /* @__PURE__ */ new Set();
|
|
6868
7079
|
for (let i = 0; i < 64; i++) {
|
|
6869
7080
|
if (stops.has(dir)) break;
|
|
6870
7081
|
stops.add(dir);
|
|
6871
7082
|
for (const candidate of LOCKFILE_PRIORITY) {
|
|
6872
|
-
const lockPath =
|
|
7083
|
+
const lockPath = import_node_path39.default.join(dir, candidate.lockfile);
|
|
6873
7084
|
if (await exists2(lockPath)) {
|
|
6874
7085
|
return { pm: candidate.pm, cwd: dir, args: [...candidate.args] };
|
|
6875
7086
|
}
|
|
6876
7087
|
}
|
|
6877
|
-
const parent =
|
|
7088
|
+
const parent = import_node_path39.default.dirname(dir);
|
|
6878
7089
|
if (parent === dir) break;
|
|
6879
7090
|
dir = parent;
|
|
6880
7091
|
}
|
|
6881
|
-
return { pm: "npm", cwd:
|
|
7092
|
+
return { pm: "npm", cwd: import_node_path39.default.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
|
|
6882
7093
|
}
|
|
6883
7094
|
async function runPackageManagerInstall(cmd) {
|
|
6884
7095
|
return new Promise((resolve) => {
|
|
@@ -6920,30 +7131,30 @@ ${err.message}`
|
|
|
6920
7131
|
// src/extend/index.ts
|
|
6921
7132
|
async function fileExists2(p) {
|
|
6922
7133
|
try {
|
|
6923
|
-
await
|
|
7134
|
+
await import_node_fs22.promises.access(p);
|
|
6924
7135
|
return true;
|
|
6925
7136
|
} catch {
|
|
6926
7137
|
return false;
|
|
6927
7138
|
}
|
|
6928
7139
|
}
|
|
6929
7140
|
async function readPackageJson(scanPath) {
|
|
6930
|
-
const pkgPath =
|
|
6931
|
-
const raw = await
|
|
7141
|
+
const pkgPath = import_node_path40.default.join(scanPath, "package.json");
|
|
7142
|
+
const raw = await import_node_fs22.promises.readFile(pkgPath, "utf8");
|
|
6932
7143
|
return JSON.parse(raw);
|
|
6933
7144
|
}
|
|
6934
7145
|
async function findHookFiles(scanPath) {
|
|
6935
|
-
const entries = await
|
|
7146
|
+
const entries = await import_node_fs22.promises.readdir(scanPath);
|
|
6936
7147
|
return entries.filter(
|
|
6937
7148
|
(e) => (e.startsWith("instrumentation") || e.startsWith("otel-init")) && /\.(ts|js)$/.test(e)
|
|
6938
7149
|
).sort();
|
|
6939
7150
|
}
|
|
6940
7151
|
function extendLogPath() {
|
|
6941
|
-
return process.env.NEAT_EXTEND_LOG ??
|
|
7152
|
+
return process.env.NEAT_EXTEND_LOG ?? import_node_path40.default.join(import_node_os2.default.homedir(), ".neat", "extend-log.ndjson");
|
|
6942
7153
|
}
|
|
6943
7154
|
async function appendExtendLog(entry2) {
|
|
6944
7155
|
const logPath = extendLogPath();
|
|
6945
|
-
await
|
|
6946
|
-
await
|
|
7156
|
+
await import_node_fs22.promises.mkdir(import_node_path40.default.dirname(logPath), { recursive: true });
|
|
7157
|
+
await import_node_fs22.promises.appendFile(logPath, JSON.stringify(entry2) + "\n", "utf8");
|
|
6947
7158
|
}
|
|
6948
7159
|
function splicedContent(fileContent, snippet2) {
|
|
6949
7160
|
if (fileContent.includes("__INSTRUMENTATION_BLOCK__")) {
|
|
@@ -7001,7 +7212,7 @@ function lookupInstrumentation(library, installedVersion) {
|
|
|
7001
7212
|
}
|
|
7002
7213
|
async function describeProjectInstrumentation(ctx) {
|
|
7003
7214
|
const hookFiles = await findHookFiles(ctx.scanPath);
|
|
7004
|
-
const envNeat = await fileExists2(
|
|
7215
|
+
const envNeat = await fileExists2(import_node_path40.default.join(ctx.scanPath, ".env.neat"));
|
|
7005
7216
|
const registryInstrPackages = new Set(
|
|
7006
7217
|
(0, import_instrumentation_registry.list)().map((e) => e.instrumentation_package).filter((p) => !!p)
|
|
7007
7218
|
);
|
|
@@ -7023,31 +7234,31 @@ async function applyExtension(ctx, args, options) {
|
|
|
7023
7234
|
);
|
|
7024
7235
|
}
|
|
7025
7236
|
for (const file of hookFiles) {
|
|
7026
|
-
const content = await
|
|
7237
|
+
const content = await import_node_fs22.promises.readFile(import_node_path40.default.join(ctx.scanPath, file), "utf8");
|
|
7027
7238
|
if (content.includes(args.registration_snippet)) {
|
|
7028
7239
|
return { library: args.library, filesTouched: [], depsAdded: [], installOutput: "", alreadyApplied: true };
|
|
7029
7240
|
}
|
|
7030
7241
|
}
|
|
7031
7242
|
const primaryFile = hookFiles[0];
|
|
7032
|
-
const primaryPath =
|
|
7243
|
+
const primaryPath = import_node_path40.default.join(ctx.scanPath, primaryFile);
|
|
7033
7244
|
const filesTouched = [];
|
|
7034
7245
|
const depsAdded = [];
|
|
7035
|
-
const pkgPath =
|
|
7246
|
+
const pkgPath = import_node_path40.default.join(ctx.scanPath, "package.json");
|
|
7036
7247
|
const pkg = await readPackageJson(ctx.scanPath);
|
|
7037
7248
|
if (!(pkg.dependencies ?? {})[args.instrumentation_package]) {
|
|
7038
7249
|
pkg.dependencies = { ...pkg.dependencies ?? {}, [args.instrumentation_package]: args.version };
|
|
7039
|
-
await
|
|
7250
|
+
await import_node_fs22.promises.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
|
|
7040
7251
|
filesTouched.push("package.json");
|
|
7041
7252
|
depsAdded.push(`${args.instrumentation_package}@${args.version}`);
|
|
7042
7253
|
}
|
|
7043
|
-
const hookContent = await
|
|
7254
|
+
const hookContent = await import_node_fs22.promises.readFile(primaryPath, "utf8");
|
|
7044
7255
|
const patched = splicedContent(hookContent, args.registration_snippet);
|
|
7045
7256
|
if (!patched) {
|
|
7046
7257
|
throw new Error(
|
|
7047
7258
|
`Could not find instrumentation insertion point in ${primaryFile}. Expected __INSTRUMENTATION_BLOCK__, instrumentations.push(, or new NodeSDK(.`
|
|
7048
7259
|
);
|
|
7049
7260
|
}
|
|
7050
|
-
await
|
|
7261
|
+
await import_node_fs22.promises.writeFile(primaryPath, patched, "utf8");
|
|
7051
7262
|
filesTouched.push(primaryFile);
|
|
7052
7263
|
const cmd = await detectPackageManager(ctx.scanPath);
|
|
7053
7264
|
const installer = options?.runInstall ?? runPackageManagerInstall;
|
|
@@ -7078,7 +7289,7 @@ async function dryRunExtension(ctx, args) {
|
|
|
7078
7289
|
};
|
|
7079
7290
|
}
|
|
7080
7291
|
for (const file of hookFiles) {
|
|
7081
|
-
const content = await
|
|
7292
|
+
const content = await import_node_fs22.promises.readFile(import_node_path40.default.join(ctx.scanPath, file), "utf8");
|
|
7082
7293
|
if (content.includes(args.registration_snippet)) {
|
|
7083
7294
|
return {
|
|
7084
7295
|
library: args.library,
|
|
@@ -7100,7 +7311,7 @@ async function dryRunExtension(ctx, args) {
|
|
|
7100
7311
|
depsToAdd.push(`${args.instrumentation_package}@${args.version}`);
|
|
7101
7312
|
filesTouched.push("package.json");
|
|
7102
7313
|
}
|
|
7103
|
-
const hookContent = await
|
|
7314
|
+
const hookContent = await import_node_fs22.promises.readFile(import_node_path40.default.join(ctx.scanPath, primaryFile), "utf8");
|
|
7104
7315
|
const patched = splicedContent(hookContent, args.registration_snippet);
|
|
7105
7316
|
if (patched) {
|
|
7106
7317
|
filesTouched.push(primaryFile);
|
|
@@ -7115,28 +7326,28 @@ async function rollbackExtension(ctx, args) {
|
|
|
7115
7326
|
if (!await fileExists2(logPath)) {
|
|
7116
7327
|
return { undone: false, message: "no apply found for library" };
|
|
7117
7328
|
}
|
|
7118
|
-
const raw = await
|
|
7329
|
+
const raw = await import_node_fs22.promises.readFile(logPath, "utf8");
|
|
7119
7330
|
const entries = raw.trim().split("\n").filter(Boolean).map((line) => JSON.parse(line));
|
|
7120
7331
|
const match = [...entries].reverse().find((e) => e.project === ctx.project && e.library === args.library);
|
|
7121
7332
|
if (!match) {
|
|
7122
7333
|
return { undone: false, message: "no apply found for library" };
|
|
7123
7334
|
}
|
|
7124
|
-
const pkgPath =
|
|
7335
|
+
const pkgPath = import_node_path40.default.join(ctx.scanPath, "package.json");
|
|
7125
7336
|
if (await fileExists2(pkgPath)) {
|
|
7126
7337
|
const pkg = await readPackageJson(ctx.scanPath);
|
|
7127
7338
|
if (pkg.dependencies?.[match.instrumentation_package]) {
|
|
7128
7339
|
const { [match.instrumentation_package]: _removed, ...rest } = pkg.dependencies;
|
|
7129
7340
|
pkg.dependencies = rest;
|
|
7130
|
-
await
|
|
7341
|
+
await import_node_fs22.promises.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
|
|
7131
7342
|
}
|
|
7132
7343
|
}
|
|
7133
7344
|
const hookFiles = await findHookFiles(ctx.scanPath);
|
|
7134
7345
|
for (const file of hookFiles) {
|
|
7135
|
-
const filePath =
|
|
7136
|
-
const content = await
|
|
7346
|
+
const filePath = import_node_path40.default.join(ctx.scanPath, file);
|
|
7347
|
+
const content = await import_node_fs22.promises.readFile(filePath, "utf8");
|
|
7137
7348
|
if (content.includes(match.registration_snippet)) {
|
|
7138
7349
|
const filtered = content.split("\n").filter((line) => !line.includes(match.registration_snippet)).join("\n");
|
|
7139
|
-
await
|
|
7350
|
+
await import_node_fs22.promises.writeFile(filePath, filtered, "utf8");
|
|
7140
7351
|
break;
|
|
7141
7352
|
}
|
|
7142
7353
|
}
|
|
@@ -7148,7 +7359,7 @@ async function rollbackExtension(ctx, args) {
|
|
|
7148
7359
|
|
|
7149
7360
|
// src/divergences.ts
|
|
7150
7361
|
init_cjs_shims();
|
|
7151
|
-
var
|
|
7362
|
+
var import_types27 = require("@neat.is/types");
|
|
7152
7363
|
function bucketKey(source, target, type) {
|
|
7153
7364
|
return `${type}|${source}|${target}`;
|
|
7154
7365
|
}
|
|
@@ -7156,22 +7367,22 @@ function bucketEdges(graph) {
|
|
|
7156
7367
|
const buckets = /* @__PURE__ */ new Map();
|
|
7157
7368
|
graph.forEachEdge((id, attrs) => {
|
|
7158
7369
|
const e = attrs;
|
|
7159
|
-
const parsed = (0,
|
|
7370
|
+
const parsed = (0, import_types27.parseEdgeId)(id);
|
|
7160
7371
|
const provenance = parsed?.provenance ?? e.provenance;
|
|
7161
7372
|
const key = bucketKey(e.source, e.target, e.type);
|
|
7162
7373
|
const cur = buckets.get(key) ?? { source: e.source, target: e.target, type: e.type };
|
|
7163
7374
|
switch (provenance) {
|
|
7164
|
-
case
|
|
7375
|
+
case import_types27.Provenance.EXTRACTED:
|
|
7165
7376
|
cur.extracted = e;
|
|
7166
7377
|
break;
|
|
7167
|
-
case
|
|
7378
|
+
case import_types27.Provenance.OBSERVED:
|
|
7168
7379
|
cur.observed = e;
|
|
7169
7380
|
break;
|
|
7170
|
-
case
|
|
7381
|
+
case import_types27.Provenance.INFERRED:
|
|
7171
7382
|
cur.inferred = e;
|
|
7172
7383
|
break;
|
|
7173
7384
|
default:
|
|
7174
|
-
if (e.provenance ===
|
|
7385
|
+
if (e.provenance === import_types27.Provenance.STALE) cur.stale = e;
|
|
7175
7386
|
}
|
|
7176
7387
|
buckets.set(key, cur);
|
|
7177
7388
|
});
|
|
@@ -7180,7 +7391,12 @@ function bucketEdges(graph) {
|
|
|
7180
7391
|
function nodeIsFrontier(graph, nodeId) {
|
|
7181
7392
|
if (!graph.hasNode(nodeId)) return false;
|
|
7182
7393
|
const attrs = graph.getNodeAttributes(nodeId);
|
|
7183
|
-
return attrs.type ===
|
|
7394
|
+
return attrs.type === import_types27.NodeType.FrontierNode;
|
|
7395
|
+
}
|
|
7396
|
+
function nodeIsWebsocketChannel(graph, nodeId) {
|
|
7397
|
+
if (!graph.hasNode(nodeId)) return false;
|
|
7398
|
+
const attrs = graph.getNodeAttributes(nodeId);
|
|
7399
|
+
return attrs.type === import_types27.NodeType.WebSocketChannelNode;
|
|
7184
7400
|
}
|
|
7185
7401
|
function clampConfidence(n) {
|
|
7186
7402
|
if (!Number.isFinite(n)) return 0;
|
|
@@ -7200,14 +7416,14 @@ function gradedConfidence(edge) {
|
|
|
7200
7416
|
return clampConfidence(confidenceForEdge(edge));
|
|
7201
7417
|
}
|
|
7202
7418
|
var OBSERVABLE_EDGE_TYPES = /* @__PURE__ */ new Set([
|
|
7203
|
-
|
|
7204
|
-
|
|
7205
|
-
|
|
7206
|
-
|
|
7419
|
+
import_types27.EdgeType.CALLS,
|
|
7420
|
+
import_types27.EdgeType.CONNECTS_TO,
|
|
7421
|
+
import_types27.EdgeType.PUBLISHES_TO,
|
|
7422
|
+
import_types27.EdgeType.CONSUMES_FROM
|
|
7207
7423
|
]);
|
|
7208
7424
|
function detectMissingDivergences(graph, bucket) {
|
|
7209
7425
|
const out = [];
|
|
7210
|
-
if (bucket.type ===
|
|
7426
|
+
if (bucket.type === import_types27.EdgeType.CONTAINS) return out;
|
|
7211
7427
|
if (bucket.extracted && !bucket.observed && OBSERVABLE_EDGE_TYPES.has(bucket.type)) {
|
|
7212
7428
|
if (!nodeIsFrontier(graph, bucket.target)) {
|
|
7213
7429
|
out.push({
|
|
@@ -7222,7 +7438,7 @@ function detectMissingDivergences(graph, bucket) {
|
|
|
7222
7438
|
});
|
|
7223
7439
|
}
|
|
7224
7440
|
}
|
|
7225
|
-
if (bucket.observed && !bucket.extracted) {
|
|
7441
|
+
if (bucket.observed && !bucket.extracted && !nodeIsWebsocketChannel(graph, bucket.target)) {
|
|
7226
7442
|
out.push({
|
|
7227
7443
|
type: "missing-extracted",
|
|
7228
7444
|
source: bucket.source,
|
|
@@ -7248,7 +7464,7 @@ function declaredHostFor(svc) {
|
|
|
7248
7464
|
function hasExtractedConfiguredBy(graph, svcId) {
|
|
7249
7465
|
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
7250
7466
|
const e = graph.getEdgeAttributes(edgeId);
|
|
7251
|
-
if (e.type ===
|
|
7467
|
+
if (e.type === import_types27.EdgeType.CONFIGURED_BY && e.provenance === import_types27.Provenance.EXTRACTED) {
|
|
7252
7468
|
return true;
|
|
7253
7469
|
}
|
|
7254
7470
|
}
|
|
@@ -7261,10 +7477,10 @@ function detectHostMismatch(graph, svcId, svc) {
|
|
|
7261
7477
|
const out = [];
|
|
7262
7478
|
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
7263
7479
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
7264
|
-
if (edge.type !==
|
|
7265
|
-
if (edge.provenance !==
|
|
7480
|
+
if (edge.type !== import_types27.EdgeType.CONNECTS_TO) continue;
|
|
7481
|
+
if (edge.provenance !== import_types27.Provenance.OBSERVED) continue;
|
|
7266
7482
|
const target = graph.getNodeAttributes(edge.target);
|
|
7267
|
-
if (target.type !==
|
|
7483
|
+
if (target.type !== import_types27.NodeType.DatabaseNode) continue;
|
|
7268
7484
|
const observedHost = target.host?.trim();
|
|
7269
7485
|
if (!observedHost) continue;
|
|
7270
7486
|
if (observedHost === declaredHost) continue;
|
|
@@ -7286,10 +7502,10 @@ function detectCompatDivergences(graph, svcId, svc) {
|
|
|
7286
7502
|
const deps = svc.dependencies ?? {};
|
|
7287
7503
|
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
7288
7504
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
7289
|
-
if (edge.type !==
|
|
7290
|
-
if (edge.provenance !==
|
|
7505
|
+
if (edge.type !== import_types27.EdgeType.CONNECTS_TO) continue;
|
|
7506
|
+
if (edge.provenance !== import_types27.Provenance.OBSERVED) continue;
|
|
7291
7507
|
const target = graph.getNodeAttributes(edge.target);
|
|
7292
|
-
if (target.type !==
|
|
7508
|
+
if (target.type !== import_types27.NodeType.DatabaseNode) continue;
|
|
7293
7509
|
for (const pair of compatPairs()) {
|
|
7294
7510
|
if (pair.engine !== target.engine) continue;
|
|
7295
7511
|
const declared = deps[pair.driver];
|
|
@@ -7348,7 +7564,7 @@ function suppressHostMismatchHalves(all) {
|
|
|
7348
7564
|
for (const d of all) {
|
|
7349
7565
|
if (d.type !== "host-mismatch") continue;
|
|
7350
7566
|
observedHalf.add(`${d.source}->${d.target}`);
|
|
7351
|
-
declaredHalf.add((0,
|
|
7567
|
+
declaredHalf.add((0, import_types27.databaseId)(d.extractedHost));
|
|
7352
7568
|
}
|
|
7353
7569
|
if (observedHalf.size === 0) return all;
|
|
7354
7570
|
return all.filter((d) => {
|
|
@@ -7367,7 +7583,7 @@ function computeDivergences(graph, opts = {}) {
|
|
|
7367
7583
|
}
|
|
7368
7584
|
graph.forEachNode((nodeId, attrs) => {
|
|
7369
7585
|
const n = attrs;
|
|
7370
|
-
if (n.type !==
|
|
7586
|
+
if (n.type !== import_types27.NodeType.ServiceNode) return;
|
|
7371
7587
|
const svc = n;
|
|
7372
7588
|
for (const d of detectHostMismatch(graph, nodeId, svc)) all.push(d);
|
|
7373
7589
|
for (const d of detectCompatDivergences(graph, nodeId, svc)) all.push(d);
|
|
@@ -7401,7 +7617,7 @@ function computeDivergences(graph, opts = {}) {
|
|
|
7401
7617
|
if (a.source !== b.source) return a.source.localeCompare(b.source);
|
|
7402
7618
|
return a.target.localeCompare(b.target);
|
|
7403
7619
|
});
|
|
7404
|
-
return
|
|
7620
|
+
return import_types27.DivergenceResultSchema.parse({
|
|
7405
7621
|
divergences: filtered,
|
|
7406
7622
|
totalAffected: filtered.length,
|
|
7407
7623
|
computedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -7410,7 +7626,7 @@ function computeDivergences(graph, opts = {}) {
|
|
|
7410
7626
|
|
|
7411
7627
|
// src/diff.ts
|
|
7412
7628
|
init_cjs_shims();
|
|
7413
|
-
var
|
|
7629
|
+
var import_node_fs23 = require("fs");
|
|
7414
7630
|
async function loadSnapshotForDiff(target) {
|
|
7415
7631
|
if (/^https?:\/\//i.test(target)) {
|
|
7416
7632
|
const res = await fetch(target);
|
|
@@ -7419,7 +7635,7 @@ async function loadSnapshotForDiff(target) {
|
|
|
7419
7635
|
}
|
|
7420
7636
|
return await res.json();
|
|
7421
7637
|
}
|
|
7422
|
-
const raw = await
|
|
7638
|
+
const raw = await import_node_fs23.promises.readFile(target, "utf8");
|
|
7423
7639
|
return JSON.parse(raw);
|
|
7424
7640
|
}
|
|
7425
7641
|
function indexEntries(entries) {
|
|
@@ -7487,25 +7703,25 @@ function canonicalJson(value) {
|
|
|
7487
7703
|
|
|
7488
7704
|
// src/registry.ts
|
|
7489
7705
|
init_cjs_shims();
|
|
7490
|
-
var
|
|
7706
|
+
var import_node_fs24 = require("fs");
|
|
7491
7707
|
var import_node_os3 = __toESM(require("os"), 1);
|
|
7492
|
-
var
|
|
7493
|
-
var
|
|
7708
|
+
var import_node_path41 = __toESM(require("path"), 1);
|
|
7709
|
+
var import_types28 = require("@neat.is/types");
|
|
7494
7710
|
var LOCK_TIMEOUT_MS = 5e3;
|
|
7495
7711
|
var LOCK_RETRY_MS = 50;
|
|
7496
7712
|
function neatHome() {
|
|
7497
7713
|
const override = process.env.NEAT_HOME;
|
|
7498
|
-
if (override && override.length > 0) return
|
|
7499
|
-
return
|
|
7714
|
+
if (override && override.length > 0) return import_node_path41.default.resolve(override);
|
|
7715
|
+
return import_node_path41.default.join(import_node_os3.default.homedir(), ".neat");
|
|
7500
7716
|
}
|
|
7501
7717
|
function registryPath() {
|
|
7502
|
-
return
|
|
7718
|
+
return import_node_path41.default.join(neatHome(), "projects.json");
|
|
7503
7719
|
}
|
|
7504
7720
|
function registryLockPath() {
|
|
7505
|
-
return
|
|
7721
|
+
return import_node_path41.default.join(neatHome(), "projects.json.lock");
|
|
7506
7722
|
}
|
|
7507
7723
|
function daemonPidPath() {
|
|
7508
|
-
return
|
|
7724
|
+
return import_node_path41.default.join(neatHome(), "neatd.pid");
|
|
7509
7725
|
}
|
|
7510
7726
|
function isPidAliveDefault(pid) {
|
|
7511
7727
|
try {
|
|
@@ -7517,7 +7733,7 @@ function isPidAliveDefault(pid) {
|
|
|
7517
7733
|
}
|
|
7518
7734
|
async function readPidFile(file) {
|
|
7519
7735
|
try {
|
|
7520
|
-
const raw = await
|
|
7736
|
+
const raw = await import_node_fs24.promises.readFile(file, "utf8");
|
|
7521
7737
|
const pid = Number.parseInt(raw.trim(), 10);
|
|
7522
7738
|
return Number.isInteger(pid) && pid > 0 ? pid : void 0;
|
|
7523
7739
|
} catch {
|
|
@@ -7565,24 +7781,24 @@ function lockHolderMessage(holder, lockPath, timeoutMs) {
|
|
|
7565
7781
|
}
|
|
7566
7782
|
}
|
|
7567
7783
|
async function writeAtomically(target, contents) {
|
|
7568
|
-
await
|
|
7784
|
+
await import_node_fs24.promises.mkdir(import_node_path41.default.dirname(target), { recursive: true });
|
|
7569
7785
|
const tmp = `${target}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
|
|
7570
|
-
const fd = await
|
|
7786
|
+
const fd = await import_node_fs24.promises.open(tmp, "w");
|
|
7571
7787
|
try {
|
|
7572
7788
|
await fd.writeFile(contents, "utf8");
|
|
7573
7789
|
await fd.sync();
|
|
7574
7790
|
} finally {
|
|
7575
7791
|
await fd.close();
|
|
7576
7792
|
}
|
|
7577
|
-
await
|
|
7793
|
+
await import_node_fs24.promises.rename(tmp, target);
|
|
7578
7794
|
}
|
|
7579
7795
|
async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaultLockHolderProbe) {
|
|
7580
7796
|
const deadline = Date.now() + timeoutMs;
|
|
7581
|
-
await
|
|
7797
|
+
await import_node_fs24.promises.mkdir(import_node_path41.default.dirname(lockPath), { recursive: true });
|
|
7582
7798
|
let probedHolder = false;
|
|
7583
7799
|
while (true) {
|
|
7584
7800
|
try {
|
|
7585
|
-
const fd = await
|
|
7801
|
+
const fd = await import_node_fs24.promises.open(lockPath, "wx");
|
|
7586
7802
|
try {
|
|
7587
7803
|
await fd.writeFile(`${process.pid}
|
|
7588
7804
|
`, "utf8");
|
|
@@ -7607,7 +7823,7 @@ async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaul
|
|
|
7607
7823
|
}
|
|
7608
7824
|
}
|
|
7609
7825
|
async function releaseLock(lockPath) {
|
|
7610
|
-
await
|
|
7826
|
+
await import_node_fs24.promises.unlink(lockPath).catch(() => {
|
|
7611
7827
|
});
|
|
7612
7828
|
}
|
|
7613
7829
|
async function withLock(fn) {
|
|
@@ -7623,7 +7839,7 @@ async function readRegistry() {
|
|
|
7623
7839
|
const file = registryPath();
|
|
7624
7840
|
let raw;
|
|
7625
7841
|
try {
|
|
7626
|
-
raw = await
|
|
7842
|
+
raw = await import_node_fs24.promises.readFile(file, "utf8");
|
|
7627
7843
|
} catch (err) {
|
|
7628
7844
|
if (err.code === "ENOENT") {
|
|
7629
7845
|
return { version: 1, projects: [] };
|
|
@@ -7631,10 +7847,10 @@ async function readRegistry() {
|
|
|
7631
7847
|
throw err;
|
|
7632
7848
|
}
|
|
7633
7849
|
const parsed = JSON.parse(raw);
|
|
7634
|
-
return
|
|
7850
|
+
return import_types28.RegistryFileSchema.parse(parsed);
|
|
7635
7851
|
}
|
|
7636
7852
|
async function writeRegistry(reg) {
|
|
7637
|
-
const validated =
|
|
7853
|
+
const validated = import_types28.RegistryFileSchema.parse(reg);
|
|
7638
7854
|
await writeAtomically(registryPath(), JSON.stringify(validated, null, 2) + "\n");
|
|
7639
7855
|
}
|
|
7640
7856
|
async function getProject(name) {
|
|
@@ -7678,7 +7894,7 @@ function pruneTtlMs() {
|
|
|
7678
7894
|
}
|
|
7679
7895
|
async function statPathStatus(p) {
|
|
7680
7896
|
try {
|
|
7681
|
-
const stat = await
|
|
7897
|
+
const stat = await import_node_fs24.promises.stat(p);
|
|
7682
7898
|
return stat.isDirectory() ? "present" : "unknown";
|
|
7683
7899
|
} catch (err) {
|
|
7684
7900
|
return err.code === "ENOENT" ? "gone" : "unknown";
|
|
@@ -7925,11 +8141,11 @@ function registerRoutes(scope, ctx) {
|
|
|
7925
8141
|
const candidates = req2.query.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
7926
8142
|
const parsed = [];
|
|
7927
8143
|
for (const c of candidates) {
|
|
7928
|
-
const r =
|
|
8144
|
+
const r = import_types29.DivergenceTypeSchema.safeParse(c);
|
|
7929
8145
|
if (!r.success) {
|
|
7930
8146
|
return reply.code(400).send({
|
|
7931
8147
|
error: `unknown divergence type "${c}"`,
|
|
7932
|
-
allowed:
|
|
8148
|
+
allowed: import_types29.DivergenceTypeSchema.options
|
|
7933
8149
|
});
|
|
7934
8150
|
}
|
|
7935
8151
|
parsed.push(r.data);
|
|
@@ -8148,7 +8364,7 @@ function registerRoutes(scope, ctx) {
|
|
|
8148
8364
|
const log = new PolicyViolationsLog(proj.paths.policyViolationsPath);
|
|
8149
8365
|
let violations = await log.readAll();
|
|
8150
8366
|
if (req2.query.severity) {
|
|
8151
|
-
const sev =
|
|
8367
|
+
const sev = import_types29.PolicySeveritySchema.safeParse(req2.query.severity);
|
|
8152
8368
|
if (!sev.success) {
|
|
8153
8369
|
return reply.code(400).send({
|
|
8154
8370
|
error: "invalid severity",
|
|
@@ -8187,7 +8403,7 @@ function registerRoutes(scope, ctx) {
|
|
|
8187
8403
|
scope.post("/policies/check", async (req2, reply) => {
|
|
8188
8404
|
const proj = resolveProject(registry, req2, reply, ctx.bootstrap, ctx.singleProject);
|
|
8189
8405
|
if (!proj) return;
|
|
8190
|
-
const parsed =
|
|
8406
|
+
const parsed = import_types29.PoliciesCheckBodySchema.safeParse(req2.body ?? {});
|
|
8191
8407
|
if (!parsed.success) {
|
|
8192
8408
|
return reply.code(400).send({
|
|
8193
8409
|
error: "invalid /policies/check body",
|
|
@@ -8445,12 +8661,91 @@ async function buildApi(opts) {
|
|
|
8445
8661
|
|
|
8446
8662
|
// src/daemon.ts
|
|
8447
8663
|
init_otel();
|
|
8664
|
+
|
|
8665
|
+
// src/connectors/index.ts
|
|
8666
|
+
init_cjs_shims();
|
|
8667
|
+
var NO_ENV = "unknown";
|
|
8668
|
+
async function runConnectorPoll(connector, ctx, graph, resolveTarget) {
|
|
8669
|
+
const signals = await connector.poll(ctx);
|
|
8670
|
+
let edgesCreated = 0;
|
|
8671
|
+
let edgesUpdated = 0;
|
|
8672
|
+
let unresolved = 0;
|
|
8673
|
+
for (const signal of signals) {
|
|
8674
|
+
const resolved = resolveTarget(signal, ctx);
|
|
8675
|
+
if (!resolved) {
|
|
8676
|
+
unresolved++;
|
|
8677
|
+
continue;
|
|
8678
|
+
}
|
|
8679
|
+
const serviceNodeId = ensureServiceNode(graph, resolved.serviceName, NO_ENV);
|
|
8680
|
+
const callSite = signal.callSite ? { relPath: signal.callSite.file, line: signal.callSite.line } : void 0;
|
|
8681
|
+
const sourceId = callSite ? ensureObservedFileNode(graph, resolved.serviceName, serviceNodeId, callSite) : serviceNodeId;
|
|
8682
|
+
const evidence = callSite ? {
|
|
8683
|
+
file: reconcileObservedRelPath(graph, resolved.serviceName, callSite.relPath),
|
|
8684
|
+
line: callSite.line
|
|
8685
|
+
} : void 0;
|
|
8686
|
+
const calls = Math.trunc(signal.callCount);
|
|
8687
|
+
if (calls < 1) continue;
|
|
8688
|
+
const errors = Math.min(Math.max(Math.trunc(signal.errorCount), 0), calls);
|
|
8689
|
+
let created = false;
|
|
8690
|
+
let ok = true;
|
|
8691
|
+
for (let i = 0; i < calls; i++) {
|
|
8692
|
+
const result = upsertObservedEdge(
|
|
8693
|
+
graph,
|
|
8694
|
+
resolved.edgeType,
|
|
8695
|
+
sourceId,
|
|
8696
|
+
resolved.targetNodeId,
|
|
8697
|
+
signal.lastObservedIso,
|
|
8698
|
+
i < errors,
|
|
8699
|
+
evidence
|
|
8700
|
+
);
|
|
8701
|
+
if (!result) {
|
|
8702
|
+
ok = false;
|
|
8703
|
+
break;
|
|
8704
|
+
}
|
|
8705
|
+
if (i === 0) created = result.created;
|
|
8706
|
+
}
|
|
8707
|
+
if (!ok) {
|
|
8708
|
+
unresolved++;
|
|
8709
|
+
continue;
|
|
8710
|
+
}
|
|
8711
|
+
if (created) edgesCreated++;
|
|
8712
|
+
else edgesUpdated++;
|
|
8713
|
+
}
|
|
8714
|
+
return { signalCount: signals.length, edgesCreated, edgesUpdated, unresolved };
|
|
8715
|
+
}
|
|
8716
|
+
var DEFAULT_POLL_INTERVAL_MS = 6e4;
|
|
8717
|
+
function startConnectorPollLoop(connector, ctx, graph, resolveTarget, options = {}) {
|
|
8718
|
+
let stopped = false;
|
|
8719
|
+
let since = ctx.since;
|
|
8720
|
+
const intervalMs = options.intervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
8721
|
+
const onError = options.onError ?? ((err) => console.error(`[neatd] connector poll failed (${connector.provider})`, err));
|
|
8722
|
+
const tick = () => {
|
|
8723
|
+
if (stopped) return;
|
|
8724
|
+
void (async () => {
|
|
8725
|
+
const tickStartedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
8726
|
+
try {
|
|
8727
|
+
await runConnectorPoll(connector, { ...ctx, since }, graph, resolveTarget);
|
|
8728
|
+
since = tickStartedAt;
|
|
8729
|
+
} catch (err) {
|
|
8730
|
+
onError(err);
|
|
8731
|
+
}
|
|
8732
|
+
})();
|
|
8733
|
+
};
|
|
8734
|
+
const interval = setInterval(tick, intervalMs);
|
|
8735
|
+
if (typeof interval.unref === "function") interval.unref();
|
|
8736
|
+
return () => {
|
|
8737
|
+
stopped = true;
|
|
8738
|
+
clearInterval(interval);
|
|
8739
|
+
};
|
|
8740
|
+
}
|
|
8741
|
+
|
|
8742
|
+
// src/daemon.ts
|
|
8448
8743
|
init_auth();
|
|
8449
8744
|
|
|
8450
8745
|
// src/unrouted.ts
|
|
8451
8746
|
init_cjs_shims();
|
|
8452
|
-
var
|
|
8453
|
-
var
|
|
8747
|
+
var import_node_fs25 = require("fs");
|
|
8748
|
+
var import_node_path44 = __toESM(require("path"), 1);
|
|
8454
8749
|
function buildUnroutedSpanRecord(serviceName, traceId, now = /* @__PURE__ */ new Date()) {
|
|
8455
8750
|
return {
|
|
8456
8751
|
timestamp: now.toISOString(),
|
|
@@ -8460,34 +8755,34 @@ function buildUnroutedSpanRecord(serviceName, traceId, now = /* @__PURE__ */ new
|
|
|
8460
8755
|
};
|
|
8461
8756
|
}
|
|
8462
8757
|
async function appendUnroutedSpan(neatHome3, record) {
|
|
8463
|
-
const target =
|
|
8464
|
-
await
|
|
8465
|
-
await
|
|
8758
|
+
const target = import_node_path44.default.join(neatHome3, "errors.ndjson");
|
|
8759
|
+
await import_node_fs25.promises.mkdir(neatHome3, { recursive: true });
|
|
8760
|
+
await import_node_fs25.promises.appendFile(target, JSON.stringify(record) + "\n", "utf8");
|
|
8466
8761
|
}
|
|
8467
8762
|
function unroutedErrorsPath(neatHome3) {
|
|
8468
|
-
return
|
|
8763
|
+
return import_node_path44.default.join(neatHome3, "errors.ndjson");
|
|
8469
8764
|
}
|
|
8470
8765
|
|
|
8471
8766
|
// src/daemon.ts
|
|
8472
|
-
var
|
|
8767
|
+
var import_types30 = require("@neat.is/types");
|
|
8473
8768
|
function daemonJsonPath(scanPath) {
|
|
8474
|
-
return
|
|
8769
|
+
return import_node_path45.default.join(scanPath, "neat-out", "daemon.json");
|
|
8475
8770
|
}
|
|
8476
8771
|
function daemonsDiscoveryDir(home) {
|
|
8477
8772
|
const base = home && home.length > 0 ? home : neatHomeFromEnv();
|
|
8478
|
-
return
|
|
8773
|
+
return import_node_path45.default.join(base, "daemons");
|
|
8479
8774
|
}
|
|
8480
8775
|
function daemonDiscoveryPath(project, home) {
|
|
8481
|
-
return
|
|
8776
|
+
return import_node_path45.default.join(daemonsDiscoveryDir(home), `${sanitizeDiscoveryName(project)}.json`);
|
|
8482
8777
|
}
|
|
8483
8778
|
function sanitizeDiscoveryName(project) {
|
|
8484
8779
|
return project.replace(/[^A-Za-z0-9._-]/g, "_");
|
|
8485
8780
|
}
|
|
8486
8781
|
function neatHomeFromEnv() {
|
|
8487
8782
|
const env = process.env.NEAT_HOME;
|
|
8488
|
-
if (env && env.length > 0) return
|
|
8783
|
+
if (env && env.length > 0) return import_node_path45.default.resolve(env);
|
|
8489
8784
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
8490
|
-
return
|
|
8785
|
+
return import_node_path45.default.join(home, ".neat");
|
|
8491
8786
|
}
|
|
8492
8787
|
function resolveNeatVersion() {
|
|
8493
8788
|
if (process.env.NEAT_LOCAL_VERSION && process.env.NEAT_LOCAL_VERSION.length > 0) {
|
|
@@ -8519,7 +8814,7 @@ async function clearDaemonRecord(record, home) {
|
|
|
8519
8814
|
} catch {
|
|
8520
8815
|
}
|
|
8521
8816
|
try {
|
|
8522
|
-
await
|
|
8817
|
+
await import_node_fs26.promises.unlink(daemonDiscoveryPath(record.project, home));
|
|
8523
8818
|
} catch {
|
|
8524
8819
|
}
|
|
8525
8820
|
}
|
|
@@ -8528,12 +8823,12 @@ function reconcileDaemonRecordSync(record, home) {
|
|
|
8528
8823
|
const stopped = { ...record, status: "stopped" };
|
|
8529
8824
|
const target = daemonJsonPath(record.projectPath);
|
|
8530
8825
|
const tmp = `${target}.${process.pid}.tmp`;
|
|
8531
|
-
(0,
|
|
8532
|
-
(0,
|
|
8826
|
+
(0, import_node_fs26.writeFileSync)(tmp, JSON.stringify(stopped, null, 2) + "\n");
|
|
8827
|
+
(0, import_node_fs26.renameSync)(tmp, target);
|
|
8533
8828
|
} catch {
|
|
8534
8829
|
}
|
|
8535
8830
|
try {
|
|
8536
|
-
(0,
|
|
8831
|
+
(0, import_node_fs26.unlinkSync)(daemonDiscoveryPath(record.project, home));
|
|
8537
8832
|
} catch {
|
|
8538
8833
|
}
|
|
8539
8834
|
}
|
|
@@ -8546,17 +8841,21 @@ function teardownSlot(slot) {
|
|
|
8546
8841
|
slot.stopStaleness();
|
|
8547
8842
|
} catch {
|
|
8548
8843
|
}
|
|
8844
|
+
try {
|
|
8845
|
+
slot.stopConnectors();
|
|
8846
|
+
} catch {
|
|
8847
|
+
}
|
|
8549
8848
|
try {
|
|
8550
8849
|
slot.detachEvents();
|
|
8551
8850
|
} catch {
|
|
8552
8851
|
}
|
|
8553
8852
|
}
|
|
8554
8853
|
function neatHomeFor(opts) {
|
|
8555
|
-
if (opts.neatHome && opts.neatHome.length > 0) return
|
|
8854
|
+
if (opts.neatHome && opts.neatHome.length > 0) return import_node_path45.default.resolve(opts.neatHome);
|
|
8556
8855
|
const env = process.env.NEAT_HOME;
|
|
8557
|
-
if (env && env.length > 0) return
|
|
8856
|
+
if (env && env.length > 0) return import_node_path45.default.resolve(env);
|
|
8558
8857
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
8559
|
-
return
|
|
8858
|
+
return import_node_path45.default.join(home, ".neat");
|
|
8560
8859
|
}
|
|
8561
8860
|
function routeSpanToProject(serviceName, projects) {
|
|
8562
8861
|
if (!serviceName) return DEFAULT_PROJECT;
|
|
@@ -8600,13 +8899,13 @@ function spanBelongsToSingleProject(graph, project, serviceName) {
|
|
|
8600
8899
|
if (!serviceName) return true;
|
|
8601
8900
|
if (serviceNameMatchesProject(serviceName, project)) return true;
|
|
8602
8901
|
return graph.someNode(
|
|
8603
|
-
(_id, attrs) => attrs.type ===
|
|
8902
|
+
(_id, attrs) => attrs.type === import_types30.NodeType.ServiceNode && attrs.name === serviceName
|
|
8604
8903
|
);
|
|
8605
8904
|
}
|
|
8606
|
-
async function bootstrapProject(entry2) {
|
|
8607
|
-
const paths = pathsForProject(entry2.name,
|
|
8905
|
+
async function bootstrapProject(entry2, connectors = []) {
|
|
8906
|
+
const paths = pathsForProject(entry2.name, import_node_path45.default.join(entry2.path, "neat-out"));
|
|
8608
8907
|
try {
|
|
8609
|
-
const stat = await
|
|
8908
|
+
const stat = await import_node_fs26.promises.stat(entry2.path);
|
|
8610
8909
|
if (!stat.isDirectory()) {
|
|
8611
8910
|
throw new Error(`registered path ${entry2.path} is not a directory`);
|
|
8612
8911
|
}
|
|
@@ -8624,6 +8923,8 @@ async function bootstrapProject(entry2) {
|
|
|
8624
8923
|
},
|
|
8625
8924
|
stopStaleness: () => {
|
|
8626
8925
|
},
|
|
8926
|
+
stopConnectors: () => {
|
|
8927
|
+
},
|
|
8627
8928
|
detachEvents: () => {
|
|
8628
8929
|
},
|
|
8629
8930
|
status: "broken",
|
|
@@ -8642,6 +8943,18 @@ async function bootstrapProject(entry2) {
|
|
|
8642
8943
|
staleEventsPath: paths.staleEventsPath,
|
|
8643
8944
|
project: entry2.name
|
|
8644
8945
|
});
|
|
8946
|
+
const stopFns = connectors.map(
|
|
8947
|
+
(registration) => startConnectorPollLoop(
|
|
8948
|
+
registration.connector,
|
|
8949
|
+
{ projectDir: entry2.path, credentials: registration.credentials },
|
|
8950
|
+
graph,
|
|
8951
|
+
registration.resolveTarget,
|
|
8952
|
+
{ intervalMs: registration.intervalMs }
|
|
8953
|
+
)
|
|
8954
|
+
);
|
|
8955
|
+
const stopConnectors = () => {
|
|
8956
|
+
for (const stop of stopFns) stop();
|
|
8957
|
+
};
|
|
8645
8958
|
await touchLastSeen(entry2.name).catch(() => {
|
|
8646
8959
|
});
|
|
8647
8960
|
return {
|
|
@@ -8651,6 +8964,7 @@ async function bootstrapProject(entry2) {
|
|
|
8651
8964
|
paths,
|
|
8652
8965
|
stopPersist,
|
|
8653
8966
|
stopStaleness,
|
|
8967
|
+
stopConnectors,
|
|
8654
8968
|
detachEvents,
|
|
8655
8969
|
status: "active"
|
|
8656
8970
|
};
|
|
@@ -8707,7 +9021,7 @@ async function startDaemon(opts = {}) {
|
|
|
8707
9021
|
const projectArg = typeof opts.project === "string" && opts.project.length > 0 ? opts.project : process.env.NEAT_PROJECT && process.env.NEAT_PROJECT.length > 0 ? process.env.NEAT_PROJECT : null;
|
|
8708
9022
|
const projectPathArg = opts.projectPath && opts.projectPath.length > 0 ? opts.projectPath : process.env.NEAT_PROJECT_PATH && process.env.NEAT_PROJECT_PATH.length > 0 ? process.env.NEAT_PROJECT_PATH : null;
|
|
8709
9023
|
const singleProject = projectArg;
|
|
8710
|
-
const singleProjectPath = singleProject && projectPathArg ?
|
|
9024
|
+
const singleProjectPath = singleProject && projectPathArg ? import_node_path45.default.resolve(projectPathArg) : null;
|
|
8711
9025
|
if (singleProject && !singleProjectPath) {
|
|
8712
9026
|
throw new Error(
|
|
8713
9027
|
`neatd: project "${singleProject}" given without a projectPath; pass NEAT_PROJECT_PATH alongside NEAT_PROJECT.`
|
|
@@ -8715,14 +9029,14 @@ async function startDaemon(opts = {}) {
|
|
|
8715
9029
|
}
|
|
8716
9030
|
if (!singleProject) {
|
|
8717
9031
|
try {
|
|
8718
|
-
await
|
|
9032
|
+
await import_node_fs26.promises.access(regPath);
|
|
8719
9033
|
} catch {
|
|
8720
9034
|
throw new Error(
|
|
8721
9035
|
`neatd: registry not found at ${regPath}. Run \`neat init <path>\` to register a project before starting the daemon.`
|
|
8722
9036
|
);
|
|
8723
9037
|
}
|
|
8724
9038
|
}
|
|
8725
|
-
const pidPath =
|
|
9039
|
+
const pidPath = import_node_path45.default.join(home, "neatd.pid");
|
|
8726
9040
|
await writeAtomically(pidPath, `${process.pid}
|
|
8727
9041
|
`);
|
|
8728
9042
|
const slots = /* @__PURE__ */ new Map();
|
|
@@ -8766,7 +9080,7 @@ async function startDaemon(opts = {}) {
|
|
|
8766
9080
|
}
|
|
8767
9081
|
async function tryRecoverSlot(entry2) {
|
|
8768
9082
|
try {
|
|
8769
|
-
const fresh = await bootstrapProject(entry2);
|
|
9083
|
+
const fresh = await bootstrapProject(entry2, opts.connectors ?? []);
|
|
8770
9084
|
const prior = slots.get(entry2.name);
|
|
8771
9085
|
if (prior) teardownSlot(prior);
|
|
8772
9086
|
slots.set(entry2.name, fresh);
|
|
@@ -8790,7 +9104,7 @@ async function startDaemon(opts = {}) {
|
|
|
8790
9104
|
bootstrapStatus.set(entry2.name, "bootstrapping");
|
|
8791
9105
|
bootstrapStartedAt.set(entry2.name, Date.now());
|
|
8792
9106
|
try {
|
|
8793
|
-
const slot = await bootstrapProject(entry2);
|
|
9107
|
+
const slot = await bootstrapProject(entry2, opts.connectors ?? []);
|
|
8794
9108
|
const prior = slots.get(entry2.name);
|
|
8795
9109
|
if (prior) teardownSlot(prior);
|
|
8796
9110
|
slots.set(entry2.name, slot);
|
|
@@ -8916,7 +9230,7 @@ async function startDaemon(opts = {}) {
|
|
|
8916
9230
|
}
|
|
8917
9231
|
if (restApp) await restApp.close().catch(() => {
|
|
8918
9232
|
});
|
|
8919
|
-
await
|
|
9233
|
+
await import_node_fs26.promises.unlink(pidPath).catch(() => {
|
|
8920
9234
|
});
|
|
8921
9235
|
throw new Error(
|
|
8922
9236
|
`neatd: failed to bind REST on port ${restPort} \u2014 ${err.message}`
|
|
@@ -9042,7 +9356,7 @@ async function startDaemon(opts = {}) {
|
|
|
9042
9356
|
});
|
|
9043
9357
|
if (otlpApp) await otlpApp.close().catch(() => {
|
|
9044
9358
|
});
|
|
9045
|
-
await
|
|
9359
|
+
await import_node_fs26.promises.unlink(pidPath).catch(() => {
|
|
9046
9360
|
});
|
|
9047
9361
|
throw new Error(
|
|
9048
9362
|
`neatd: failed to bind OTLP on port ${otlpPort} \u2014 ${err.message}`
|
|
@@ -9077,7 +9391,7 @@ async function startDaemon(opts = {}) {
|
|
|
9077
9391
|
});
|
|
9078
9392
|
if (otlpApp) await otlpApp.close().catch(() => {
|
|
9079
9393
|
});
|
|
9080
|
-
await
|
|
9394
|
+
await import_node_fs26.promises.unlink(pidPath).catch(() => {
|
|
9081
9395
|
});
|
|
9082
9396
|
throw new Error(
|
|
9083
9397
|
`neatd: failed to write daemon.json for "${singleProject}" \u2014 ${err.message}`
|
|
@@ -9124,9 +9438,9 @@ async function startDaemon(opts = {}) {
|
|
|
9124
9438
|
let registryWatcher = null;
|
|
9125
9439
|
let reloadTimer = null;
|
|
9126
9440
|
if (!singleProject) try {
|
|
9127
|
-
const regDir =
|
|
9128
|
-
const regBase =
|
|
9129
|
-
registryWatcher = (0,
|
|
9441
|
+
const regDir = import_node_path45.default.dirname(regPath);
|
|
9442
|
+
const regBase = import_node_path45.default.basename(regPath);
|
|
9443
|
+
registryWatcher = (0, import_node_fs26.watch)(regDir, (_eventType, filename) => {
|
|
9130
9444
|
if (filename !== null && filename !== regBase) return;
|
|
9131
9445
|
if (reloadTimer) clearTimeout(reloadTimer);
|
|
9132
9446
|
reloadTimer = setTimeout(() => {
|
|
@@ -9175,7 +9489,7 @@ async function startDaemon(opts = {}) {
|
|
|
9175
9489
|
if (daemonRecord) {
|
|
9176
9490
|
await clearDaemonRecord(daemonRecord, home);
|
|
9177
9491
|
}
|
|
9178
|
-
await
|
|
9492
|
+
await import_node_fs26.promises.unlink(pidPath).catch(() => {
|
|
9179
9493
|
});
|
|
9180
9494
|
};
|
|
9181
9495
|
return {
|
|
@@ -9198,9 +9512,9 @@ init_auth();
|
|
|
9198
9512
|
// src/web-spawn.ts
|
|
9199
9513
|
init_cjs_shims();
|
|
9200
9514
|
var import_node_child_process2 = require("child_process");
|
|
9201
|
-
var
|
|
9515
|
+
var import_node_fs27 = require("fs");
|
|
9202
9516
|
var import_node_net = __toESM(require("net"), 1);
|
|
9203
|
-
var
|
|
9517
|
+
var import_node_path46 = __toESM(require("path"), 1);
|
|
9204
9518
|
var DEFAULT_WEB_PORT = 6328;
|
|
9205
9519
|
var DEFAULT_REST_PORT = 8080;
|
|
9206
9520
|
function asValidPort(value) {
|
|
@@ -9209,11 +9523,11 @@ function asValidPort(value) {
|
|
|
9209
9523
|
}
|
|
9210
9524
|
function projectRoot() {
|
|
9211
9525
|
const fromEnv = process.env.NEAT_SCAN_PATH;
|
|
9212
|
-
return
|
|
9526
|
+
return import_node_path46.default.resolve(fromEnv && fromEnv.length > 0 ? fromEnv : process.cwd());
|
|
9213
9527
|
}
|
|
9214
9528
|
async function readDaemonPorts(root) {
|
|
9215
9529
|
try {
|
|
9216
|
-
const raw = await
|
|
9530
|
+
const raw = await import_node_fs27.promises.readFile(import_node_path46.default.join(root, "neat-out", "daemon.json"), "utf8");
|
|
9217
9531
|
const parsed = JSON.parse(raw);
|
|
9218
9532
|
const ports = parsed?.ports ?? {};
|
|
9219
9533
|
return { web: asValidPort(ports.web), rest: asValidPort(ports.rest) };
|
|
@@ -9258,10 +9572,10 @@ function resolveWebPackageDir() {
|
|
|
9258
9572
|
eval("require")
|
|
9259
9573
|
);
|
|
9260
9574
|
const pkgJsonPath = req.resolve("@neat.is/web/package.json");
|
|
9261
|
-
return
|
|
9575
|
+
return import_node_path46.default.dirname(pkgJsonPath);
|
|
9262
9576
|
}
|
|
9263
9577
|
function resolveStandaloneServerEntry(webDir) {
|
|
9264
|
-
return
|
|
9578
|
+
return import_node_path46.default.join(webDir, ".next/standalone/packages/web/server.js");
|
|
9265
9579
|
}
|
|
9266
9580
|
async function pickInternalPort() {
|
|
9267
9581
|
return new Promise((resolve, reject) => {
|
|
@@ -9314,7 +9628,7 @@ async function spawnWebUI(restPort, opts = {}) {
|
|
|
9314
9628
|
NEAT_API_URL: apiUrl
|
|
9315
9629
|
};
|
|
9316
9630
|
child = (0, import_node_child_process2.spawn)(process.execPath, [serverEntry], {
|
|
9317
|
-
cwd:
|
|
9631
|
+
cwd: import_node_path46.default.dirname(serverEntry),
|
|
9318
9632
|
env,
|
|
9319
9633
|
stdio: ["ignore", "inherit", "inherit"],
|
|
9320
9634
|
detached: false
|
|
@@ -9490,14 +9804,14 @@ function localVersion() {
|
|
|
9490
9804
|
}
|
|
9491
9805
|
function neatHome2() {
|
|
9492
9806
|
if (process.env.NEAT_HOME && process.env.NEAT_HOME.length > 0) {
|
|
9493
|
-
return
|
|
9807
|
+
return import_node_path47.default.resolve(process.env.NEAT_HOME);
|
|
9494
9808
|
}
|
|
9495
9809
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
9496
|
-
return
|
|
9810
|
+
return import_node_path47.default.join(home, ".neat");
|
|
9497
9811
|
}
|
|
9498
9812
|
async function readPid() {
|
|
9499
9813
|
try {
|
|
9500
|
-
const raw = await
|
|
9814
|
+
const raw = await import_node_fs28.promises.readFile(import_node_path47.default.join(neatHome2(), "neatd.pid"), "utf8");
|
|
9501
9815
|
const n = Number.parseInt(raw.trim(), 10);
|
|
9502
9816
|
return Number.isFinite(n) ? n : null;
|
|
9503
9817
|
} catch {
|