@neat.is/core 0.4.26-dev.20260702 → 0.4.26
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-ZX7PCMGZ.js → chunk-A3322JYS.js} +57 -3
- package/dist/chunk-A3322JYS.js.map +1 -0
- package/dist/{chunk-XOOCA5T7.js → chunk-BIY46Q6U.js} +25 -1
- package/dist/chunk-BIY46Q6U.js.map +1 -0
- package/dist/{chunk-O25KZNZK.js → chunk-QM6BMPVJ.js} +1200 -280
- package/dist/chunk-QM6BMPVJ.js.map +1 -0
- package/dist/{chunk-GAFTW2OX.js → chunk-UV5WSM7M.js} +2 -2
- package/dist/{chunk-IABNGQT2.js → chunk-XV4D7A3Z.js} +117 -6
- package/dist/chunk-XV4D7A3Z.js.map +1 -0
- package/dist/cli.cjs +1565 -546
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +49 -6
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +1382 -326
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +102 -1
- package/dist/index.d.ts +102 -1
- package/dist/index.js +4 -4
- package/dist/neatd.cjs +1543 -395
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +3 -3
- package/dist/{otel-grpc-QYHUJ4OY.js → otel-grpc-ET5Z6KI6.js} +3 -3
- package/dist/server.cjs +1206 -235
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +4 -4
- package/package.json +2 -2
- package/dist/chunk-IABNGQT2.js.map +0 -1
- package/dist/chunk-O25KZNZK.js.map +0 -1
- package/dist/chunk-XOOCA5T7.js.map +0 -1
- package/dist/chunk-ZX7PCMGZ.js.map +0 -1
- /package/dist/{chunk-GAFTW2OX.js.map → chunk-UV5WSM7M.js.map} +0 -0
- /package/dist/{otel-grpc-QYHUJ4OY.js.map → otel-grpc-ET5Z6KI6.js.map} +0 -0
package/dist/index.cjs
CHANGED
|
@@ -57,9 +57,9 @@ function mountBearerAuth(app, opts) {
|
|
|
57
57
|
const suffixes = [...DEFAULT_UNAUTH_SUFFIXES, ...opts.extraUnauthenticatedSuffixes ?? []];
|
|
58
58
|
const publicRead = opts.publicRead === true;
|
|
59
59
|
app.addHook("preHandler", (req, reply, done) => {
|
|
60
|
-
const
|
|
60
|
+
const path46 = (req.url.split("?")[0] ?? "").replace(/\/+$/, "");
|
|
61
61
|
for (const suffix of suffixes) {
|
|
62
|
-
if (
|
|
62
|
+
if (path46 === suffix || path46.endsWith(suffix)) {
|
|
63
63
|
done();
|
|
64
64
|
return;
|
|
65
65
|
}
|
|
@@ -70,11 +70,13 @@ function mountBearerAuth(app, opts) {
|
|
|
70
70
|
}
|
|
71
71
|
const header = req.headers.authorization;
|
|
72
72
|
if (typeof header !== "string" || !header.startsWith("Bearer ")) {
|
|
73
|
+
opts.onReject?.();
|
|
73
74
|
void reply.code(401).send({ error: "unauthorized" });
|
|
74
75
|
return;
|
|
75
76
|
}
|
|
76
77
|
const provided = Buffer.from(header.slice("Bearer ".length).trim(), "utf8");
|
|
77
78
|
if (provided.length !== expected.length || !(0, import_node_crypto.timingSafeEqual)(provided, expected)) {
|
|
79
|
+
opts.onReject?.();
|
|
78
80
|
void reply.code(401).send({ error: "unauthorized" });
|
|
79
81
|
return;
|
|
80
82
|
}
|
|
@@ -187,8 +189,8 @@ function reshapeGrpcRequest(req) {
|
|
|
187
189
|
};
|
|
188
190
|
}
|
|
189
191
|
function resolveProtoRoot() {
|
|
190
|
-
const here =
|
|
191
|
-
return
|
|
192
|
+
const here = import_node_path42.default.dirname((0, import_node_url.fileURLToPath)(importMetaUrl));
|
|
193
|
+
return import_node_path42.default.resolve(here, "..", "proto");
|
|
192
194
|
}
|
|
193
195
|
function loadTraceService() {
|
|
194
196
|
const protoRoot = resolveProtoRoot();
|
|
@@ -256,13 +258,13 @@ async function startOtelGrpcReceiver(opts) {
|
|
|
256
258
|
})
|
|
257
259
|
};
|
|
258
260
|
}
|
|
259
|
-
var import_node_url,
|
|
261
|
+
var import_node_url, import_node_path42, import_node_crypto2, grpc, protoLoader;
|
|
260
262
|
var init_otel_grpc = __esm({
|
|
261
263
|
"src/otel-grpc.ts"() {
|
|
262
264
|
"use strict";
|
|
263
265
|
init_cjs_shims();
|
|
264
266
|
import_node_url = require("url");
|
|
265
|
-
|
|
267
|
+
import_node_path42 = __toESM(require("path"), 1);
|
|
266
268
|
import_node_crypto2 = require("crypto");
|
|
267
269
|
grpc = __toESM(require("@grpc/grpc-js"), 1);
|
|
268
270
|
protoLoader = __toESM(require("@grpc/proto-loader"), 1);
|
|
@@ -335,6 +337,36 @@ function pickEnv(spanAttrs, resourceAttrs) {
|
|
|
335
337
|
}
|
|
336
338
|
return ENV_FALLBACK;
|
|
337
339
|
}
|
|
340
|
+
function messagingDestinationOf(attrs) {
|
|
341
|
+
for (const key of ["messaging.destination.name", "messaging.destination"]) {
|
|
342
|
+
const v = attrs[key];
|
|
343
|
+
if (typeof v === "string" && v.length > 0) return v;
|
|
344
|
+
}
|
|
345
|
+
return void 0;
|
|
346
|
+
}
|
|
347
|
+
function hasWebsocketUpgradeHeader(attrs) {
|
|
348
|
+
const v = attrs["http.request.header.upgrade"];
|
|
349
|
+
const matches = (s) => typeof s === "string" && s.trim().toLowerCase() === "websocket";
|
|
350
|
+
if (Array.isArray(v)) return v.some(matches);
|
|
351
|
+
return matches(v);
|
|
352
|
+
}
|
|
353
|
+
function websocketChannelPathOf(attrs) {
|
|
354
|
+
const route = attrs["http.route"];
|
|
355
|
+
if (typeof route === "string" && route.length > 0) return route;
|
|
356
|
+
for (const key of ["url.path", "http.target"]) {
|
|
357
|
+
const v = attrs[key];
|
|
358
|
+
if (typeof v === "string" && v.length > 0) {
|
|
359
|
+
const q = v.indexOf("?");
|
|
360
|
+
const path46 = q === -1 ? v : v.slice(0, q);
|
|
361
|
+
if (path46.length > 0) return path46;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
return void 0;
|
|
365
|
+
}
|
|
366
|
+
function websocketChannelOf(attrs) {
|
|
367
|
+
if (!hasWebsocketUpgradeHeader(attrs)) return void 0;
|
|
368
|
+
return websocketChannelPathOf(attrs);
|
|
369
|
+
}
|
|
338
370
|
function parseOtlpRequest(body) {
|
|
339
371
|
const out = [];
|
|
340
372
|
for (const rs of body.resourceSpans ?? []) {
|
|
@@ -361,6 +393,14 @@ function parseOtlpRequest(body) {
|
|
|
361
393
|
attributes: attrs,
|
|
362
394
|
dbSystem: typeof attrs["db.system"] === "string" ? attrs["db.system"] : void 0,
|
|
363
395
|
dbName: typeof attrs["db.name"] === "string" ? attrs["db.name"] : void 0,
|
|
396
|
+
messagingSystem: typeof attrs["messaging.system"] === "string" ? attrs["messaging.system"] : void 0,
|
|
397
|
+
messagingDestination: messagingDestinationOf(attrs),
|
|
398
|
+
graphqlOperationName: typeof attrs["graphql.operation.name"] === "string" && attrs["graphql.operation.name"].length > 0 ? attrs["graphql.operation.name"] : void 0,
|
|
399
|
+
graphqlOperationType: typeof attrs["graphql.operation.type"] === "string" && attrs["graphql.operation.type"].length > 0 ? attrs["graphql.operation.type"] : void 0,
|
|
400
|
+
rpcSystem: typeof attrs["rpc.system"] === "string" && attrs["rpc.system"].length > 0 ? attrs["rpc.system"] : void 0,
|
|
401
|
+
rpcService: typeof attrs["rpc.service"] === "string" && attrs["rpc.service"].length > 0 ? attrs["rpc.service"] : void 0,
|
|
402
|
+
rpcMethod: typeof attrs["rpc.method"] === "string" && attrs["rpc.method"].length > 0 ? attrs["rpc.method"] : void 0,
|
|
403
|
+
websocketChannel: websocketChannelOf(attrs),
|
|
364
404
|
statusCode: span.status?.code,
|
|
365
405
|
errorMessage: span.status?.message,
|
|
366
406
|
exception: extractExceptionFromEvents(span.events)
|
|
@@ -372,10 +412,10 @@ function parseOtlpRequest(body) {
|
|
|
372
412
|
return out;
|
|
373
413
|
}
|
|
374
414
|
function loadProtoRoot() {
|
|
375
|
-
const here =
|
|
376
|
-
const protoRoot =
|
|
415
|
+
const here = import_node_path43.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
|
|
416
|
+
const protoRoot = import_node_path43.default.resolve(here, "..", "proto");
|
|
377
417
|
const root = new import_protobufjs.default.Root();
|
|
378
|
-
root.resolvePath = (_origin, target) =>
|
|
418
|
+
root.resolvePath = (_origin, target) => import_node_path43.default.resolve(protoRoot, target);
|
|
379
419
|
root.loadSync(
|
|
380
420
|
"opentelemetry/proto/collector/trace/v1/trace_service.proto",
|
|
381
421
|
{ keepCase: true }
|
|
@@ -420,7 +460,21 @@ async function buildOtelReceiver(opts) {
|
|
|
420
460
|
logger: false,
|
|
421
461
|
bodyLimit: opts.bodyLimit ?? 16 * 1024 * 1024
|
|
422
462
|
});
|
|
423
|
-
|
|
463
|
+
const REJECT_WARN_INTERVAL_MS = 6e4;
|
|
464
|
+
let lastRejectWarnAt = 0;
|
|
465
|
+
const warnRejectedOtlp = () => {
|
|
466
|
+
const now = Date.now();
|
|
467
|
+
if (now - lastRejectWarnAt < REJECT_WARN_INTERVAL_MS) return;
|
|
468
|
+
lastRejectWarnAt = now;
|
|
469
|
+
console.warn(
|
|
470
|
+
"[neatd] rejecting OTLP spans on /v1/traces \u2014 missing or invalid bearer token (set NEAT_OTEL_TOKEN on the instrumented app)"
|
|
471
|
+
);
|
|
472
|
+
};
|
|
473
|
+
mountBearerAuth(app, {
|
|
474
|
+
token: opts.authToken,
|
|
475
|
+
trustProxy: opts.trustProxy,
|
|
476
|
+
onReject: warnRejectedOtlp
|
|
477
|
+
});
|
|
424
478
|
const queue = [];
|
|
425
479
|
let draining = false;
|
|
426
480
|
let drainPromise = Promise.resolve();
|
|
@@ -597,12 +651,12 @@ function logSpanHandler(span) {
|
|
|
597
651
|
`otel: ${span.service} ${span.name} parent=${parent} status=${status2}${db}`
|
|
598
652
|
);
|
|
599
653
|
}
|
|
600
|
-
var
|
|
654
|
+
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;
|
|
601
655
|
var init_otel = __esm({
|
|
602
656
|
"src/otel.ts"() {
|
|
603
657
|
"use strict";
|
|
604
658
|
init_cjs_shims();
|
|
605
|
-
|
|
659
|
+
import_node_path43 = __toESM(require("path"), 1);
|
|
606
660
|
import_node_url2 = require("url");
|
|
607
661
|
import_fastify2 = __toESM(require("fastify"), 1);
|
|
608
662
|
import_protobufjs = __toESM(require("protobufjs"), 1);
|
|
@@ -1192,19 +1246,19 @@ function confidenceFromMix(edges, now = Date.now()) {
|
|
|
1192
1246
|
function longestIncomingWalk(graph, start, maxDepth) {
|
|
1193
1247
|
let best = { path: [start], edges: [] };
|
|
1194
1248
|
const visited = /* @__PURE__ */ new Set([start]);
|
|
1195
|
-
function step(node,
|
|
1196
|
-
if (
|
|
1197
|
-
best = { path: [...
|
|
1249
|
+
function step(node, path46, edges) {
|
|
1250
|
+
if (path46.length > best.path.length) {
|
|
1251
|
+
best = { path: [...path46], edges: [...edges] };
|
|
1198
1252
|
}
|
|
1199
|
-
if (
|
|
1253
|
+
if (path46.length - 1 >= maxDepth) return;
|
|
1200
1254
|
const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
|
|
1201
1255
|
for (const [srcId, edge] of incoming) {
|
|
1202
1256
|
if (visited.has(srcId)) continue;
|
|
1203
1257
|
visited.add(srcId);
|
|
1204
|
-
|
|
1258
|
+
path46.push(srcId);
|
|
1205
1259
|
edges.push(edge);
|
|
1206
|
-
step(srcId,
|
|
1207
|
-
|
|
1260
|
+
step(srcId, path46, edges);
|
|
1261
|
+
path46.pop();
|
|
1208
1262
|
edges.pop();
|
|
1209
1263
|
visited.delete(srcId);
|
|
1210
1264
|
}
|
|
@@ -1212,14 +1266,14 @@ function longestIncomingWalk(graph, start, maxDepth) {
|
|
|
1212
1266
|
step(start, [start], []);
|
|
1213
1267
|
return best;
|
|
1214
1268
|
}
|
|
1215
|
-
function databaseRootCauseShape(graph, origin,
|
|
1269
|
+
function databaseRootCauseShape(graph, origin, walk3) {
|
|
1216
1270
|
const targetDb = origin;
|
|
1217
1271
|
const candidatePairs = compatPairs().filter((p) => p.engine === targetDb.engine);
|
|
1218
1272
|
if (candidatePairs.length === 0) return null;
|
|
1219
|
-
for (const id of
|
|
1273
|
+
for (const id of walk3.path) {
|
|
1220
1274
|
const owner = resolveOwningService(graph, id);
|
|
1221
1275
|
if (!owner) continue;
|
|
1222
|
-
const { id:
|
|
1276
|
+
const { id: serviceId4, svc } = owner;
|
|
1223
1277
|
const deps = svc.dependencies ?? {};
|
|
1224
1278
|
for (const pair of candidatePairs) {
|
|
1225
1279
|
const declared = deps[pair.driver];
|
|
@@ -1232,7 +1286,7 @@ function databaseRootCauseShape(graph, origin, walk) {
|
|
|
1232
1286
|
);
|
|
1233
1287
|
if (!result.compatible) {
|
|
1234
1288
|
return {
|
|
1235
|
-
rootCauseNode:
|
|
1289
|
+
rootCauseNode: serviceId4,
|
|
1236
1290
|
rootCauseReason: result.reason ?? "incompatible driver",
|
|
1237
1291
|
...result.minDriverVersion ? {
|
|
1238
1292
|
fixRecommendation: `Upgrade ${svc.name} ${pair.driver} driver to >= ${result.minDriverVersion}`
|
|
@@ -1243,11 +1297,11 @@ function databaseRootCauseShape(graph, origin, walk) {
|
|
|
1243
1297
|
}
|
|
1244
1298
|
return null;
|
|
1245
1299
|
}
|
|
1246
|
-
function serviceRootCauseShape(graph, _origin,
|
|
1247
|
-
for (const id of
|
|
1300
|
+
function serviceRootCauseShape(graph, _origin, walk3) {
|
|
1301
|
+
for (const id of walk3.path) {
|
|
1248
1302
|
const owner = resolveOwningService(graph, id);
|
|
1249
1303
|
if (!owner) continue;
|
|
1250
|
-
const { id:
|
|
1304
|
+
const { id: serviceId4, svc } = owner;
|
|
1251
1305
|
const deps = svc.dependencies ?? {};
|
|
1252
1306
|
const serviceNodeEngine = svc.nodeEngine;
|
|
1253
1307
|
for (const constraint of nodeEngineConstraints()) {
|
|
@@ -1256,7 +1310,7 @@ function serviceRootCauseShape(graph, _origin, walk) {
|
|
|
1256
1310
|
const result = checkNodeEngineConstraint(constraint, declared, serviceNodeEngine);
|
|
1257
1311
|
if (!result.compatible && result.reason) {
|
|
1258
1312
|
return {
|
|
1259
|
-
rootCauseNode:
|
|
1313
|
+
rootCauseNode: serviceId4,
|
|
1260
1314
|
rootCauseReason: result.reason,
|
|
1261
1315
|
...result.requiredNodeVersion ? {
|
|
1262
1316
|
fixRecommendation: `Bump ${svc.name}'s engines.node to >= ${result.requiredNodeVersion}`
|
|
@@ -1271,7 +1325,7 @@ function serviceRootCauseShape(graph, _origin, walk) {
|
|
|
1271
1325
|
const result = checkPackageConflict(conflict, declared, requiredDeclared);
|
|
1272
1326
|
if (!result.compatible && result.reason) {
|
|
1273
1327
|
return {
|
|
1274
|
-
rootCauseNode:
|
|
1328
|
+
rootCauseNode: serviceId4,
|
|
1275
1329
|
rootCauseReason: result.reason,
|
|
1276
1330
|
fixRecommendation: `Upgrade ${svc.name}'s ${conflict.requires.name} to >= ${conflict.requires.minVersion}`
|
|
1277
1331
|
};
|
|
@@ -1280,10 +1334,10 @@ function serviceRootCauseShape(graph, _origin, walk) {
|
|
|
1280
1334
|
}
|
|
1281
1335
|
return null;
|
|
1282
1336
|
}
|
|
1283
|
-
function fileRootCauseShape(graph, origin,
|
|
1337
|
+
function fileRootCauseShape(graph, origin, walk3) {
|
|
1284
1338
|
const owner = resolveOwningService(graph, origin.id);
|
|
1285
1339
|
if (!owner) return null;
|
|
1286
|
-
return serviceRootCauseShape(graph, owner.svc,
|
|
1340
|
+
return serviceRootCauseShape(graph, owner.svc, walk3);
|
|
1287
1341
|
}
|
|
1288
1342
|
var rootCauseShapes = {
|
|
1289
1343
|
[import_types.NodeType.DatabaseNode]: databaseRootCauseShape,
|
|
@@ -1295,16 +1349,16 @@ function getRootCause(graph, errorNodeId, errorEvent, incidents) {
|
|
|
1295
1349
|
const origin = graph.getNodeAttributes(errorNodeId);
|
|
1296
1350
|
const shape = rootCauseShapes[origin.type];
|
|
1297
1351
|
if (shape) {
|
|
1298
|
-
const
|
|
1299
|
-
const match = shape(graph, origin,
|
|
1352
|
+
const walk3 = longestIncomingWalk(graph, errorNodeId, ROOT_CAUSE_MAX_DEPTH);
|
|
1353
|
+
const match = shape(graph, origin, walk3);
|
|
1300
1354
|
if (match) {
|
|
1301
1355
|
const reason = errorEvent ? `${match.rootCauseReason} (observed error: ${errorEvent.errorMessage})` : match.rootCauseReason;
|
|
1302
1356
|
return import_types.RootCauseResultSchema.parse({
|
|
1303
1357
|
rootCauseNode: match.rootCauseNode,
|
|
1304
1358
|
rootCauseReason: reason,
|
|
1305
|
-
traversalPath:
|
|
1306
|
-
edgeProvenances:
|
|
1307
|
-
confidence: confidenceFromMix(
|
|
1359
|
+
traversalPath: walk3.path,
|
|
1360
|
+
edgeProvenances: walk3.edges.map((e) => e.provenance),
|
|
1361
|
+
confidence: confidenceFromMix(walk3.edges),
|
|
1308
1362
|
fixRecommendation: match.fixRecommendation
|
|
1309
1363
|
});
|
|
1310
1364
|
}
|
|
@@ -1362,9 +1416,9 @@ function rootCauseFromIncidents(nodeId, incidents, errorEvent) {
|
|
|
1362
1416
|
function isFailingCallEdge(e) {
|
|
1363
1417
|
return e.type === import_types.EdgeType.CALLS && (e.signal?.errorCount ?? 0) > 0;
|
|
1364
1418
|
}
|
|
1365
|
-
function callSourcesForService(graph,
|
|
1366
|
-
const ids = [
|
|
1367
|
-
for (const edgeId of graph.outboundEdges(
|
|
1419
|
+
function callSourcesForService(graph, serviceId4) {
|
|
1420
|
+
const ids = [serviceId4];
|
|
1421
|
+
for (const edgeId of graph.outboundEdges(serviceId4)) {
|
|
1368
1422
|
const e = graph.getEdgeAttributes(edgeId);
|
|
1369
1423
|
if (e.type !== import_types.EdgeType.CONTAINS) continue;
|
|
1370
1424
|
const tgt = graph.getNodeAttributes(e.target);
|
|
@@ -1381,9 +1435,9 @@ function failingCallDominates(e, id, curEdge, curId) {
|
|
|
1381
1435
|
}
|
|
1382
1436
|
return id < curId;
|
|
1383
1437
|
}
|
|
1384
|
-
function dominantFailingCall(graph,
|
|
1438
|
+
function dominantFailingCall(graph, serviceId4, visited) {
|
|
1385
1439
|
let best = null;
|
|
1386
|
-
for (const src of callSourcesForService(graph,
|
|
1440
|
+
for (const src of callSourcesForService(graph, serviceId4)) {
|
|
1387
1441
|
for (const edgeId of graph.outboundEdges(src)) {
|
|
1388
1442
|
const e = graph.getEdgeAttributes(edgeId);
|
|
1389
1443
|
if (!isFailingCallEdge(e)) continue;
|
|
@@ -1398,26 +1452,26 @@ function dominantFailingCall(graph, serviceId3, visited) {
|
|
|
1398
1452
|
return best;
|
|
1399
1453
|
}
|
|
1400
1454
|
function followFailingCallChain(graph, originServiceId, maxDepth) {
|
|
1401
|
-
const
|
|
1455
|
+
const path46 = [originServiceId];
|
|
1402
1456
|
const edges = [];
|
|
1403
1457
|
const visited = /* @__PURE__ */ new Set([originServiceId]);
|
|
1404
1458
|
let current = originServiceId;
|
|
1405
1459
|
for (let depth = 0; depth < maxDepth; depth++) {
|
|
1406
1460
|
const hop = dominantFailingCall(graph, current, visited);
|
|
1407
1461
|
if (!hop) break;
|
|
1408
|
-
|
|
1462
|
+
path46.push(hop.nextService);
|
|
1409
1463
|
edges.push(hop.edge);
|
|
1410
1464
|
visited.add(hop.nextService);
|
|
1411
1465
|
current = hop.nextService;
|
|
1412
1466
|
}
|
|
1413
1467
|
if (edges.length === 0) return null;
|
|
1414
|
-
return { path:
|
|
1468
|
+
return { path: path46, edges, culprit: current };
|
|
1415
1469
|
}
|
|
1416
1470
|
function crossServiceRootCause(graph, originId, incidents, errorEvent) {
|
|
1417
1471
|
const chain = followFailingCallChain(graph, originId, ROOT_CAUSE_MAX_DEPTH);
|
|
1418
1472
|
if (!chain) return null;
|
|
1419
1473
|
const culprit = chain.culprit;
|
|
1420
|
-
const
|
|
1474
|
+
const path46 = [...chain.path];
|
|
1421
1475
|
const edgeProvenances = chain.edges.map((e) => e.provenance);
|
|
1422
1476
|
const baseConfidence = confidenceFromMix(chain.edges);
|
|
1423
1477
|
const confidence = Math.max(0, Math.min(1, baseConfidence * INCIDENT_ROOT_CAUSE_CONFIDENCE));
|
|
@@ -1425,14 +1479,14 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
|
|
|
1425
1479
|
if (loc) {
|
|
1426
1480
|
let rootCauseNode = culprit;
|
|
1427
1481
|
if (loc.fileNode) {
|
|
1428
|
-
|
|
1482
|
+
path46.push(loc.fileNode);
|
|
1429
1483
|
edgeProvenances.push(import_types.Provenance.OBSERVED);
|
|
1430
1484
|
rootCauseNode = loc.fileNode;
|
|
1431
1485
|
}
|
|
1432
1486
|
return import_types.RootCauseResultSchema.parse({
|
|
1433
1487
|
rootCauseNode,
|
|
1434
1488
|
rootCauseReason: loc.rootCauseReason,
|
|
1435
|
-
traversalPath:
|
|
1489
|
+
traversalPath: path46,
|
|
1436
1490
|
edgeProvenances,
|
|
1437
1491
|
confidence,
|
|
1438
1492
|
...loc.fixRecommendation ? { fixRecommendation: loc.fixRecommendation } : {}
|
|
@@ -1444,7 +1498,7 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
|
|
|
1444
1498
|
return import_types.RootCauseResultSchema.parse({
|
|
1445
1499
|
rootCauseNode: culprit,
|
|
1446
1500
|
rootCauseReason: `${culpritName} is failing downstream calls (${errs} observed error${errs === 1 ? "" : "s"})`,
|
|
1447
|
-
traversalPath:
|
|
1501
|
+
traversalPath: path46,
|
|
1448
1502
|
edgeProvenances,
|
|
1449
1503
|
confidence,
|
|
1450
1504
|
fixRecommendation: `Inspect ${culpritName}'s failing handler`
|
|
@@ -2368,10 +2422,82 @@ var STITCH_EDGE_TYPES = /* @__PURE__ */ new Set([
|
|
|
2368
2422
|
]);
|
|
2369
2423
|
var WIRE_SPAN_KIND_CLIENT = 3;
|
|
2370
2424
|
var WIRE_SPAN_KIND_PRODUCER = 4;
|
|
2425
|
+
var WIRE_SPAN_KIND_CONSUMER = 5;
|
|
2371
2426
|
function spanMintsObservedEdge(kind) {
|
|
2372
2427
|
if (kind === void 0 || kind === 0) return true;
|
|
2373
2428
|
return kind === WIRE_SPAN_KIND_CLIENT || kind === WIRE_SPAN_KIND_PRODUCER;
|
|
2374
2429
|
}
|
|
2430
|
+
function spanMintsMessagingEdge(kind) {
|
|
2431
|
+
return kind === WIRE_SPAN_KIND_PRODUCER || kind === WIRE_SPAN_KIND_CONSUMER;
|
|
2432
|
+
}
|
|
2433
|
+
function spanServesGraphqlOperation(kind) {
|
|
2434
|
+
return kind !== WIRE_SPAN_KIND_CLIENT && kind !== WIRE_SPAN_KIND_PRODUCER && kind !== WIRE_SPAN_KIND_CONSUMER;
|
|
2435
|
+
}
|
|
2436
|
+
function ensureGraphqlOperationNode(graph, serviceName, operationType, operationName) {
|
|
2437
|
+
const id = (0, import_types3.graphqlOperationId)(serviceName, operationType, operationName);
|
|
2438
|
+
if (graph.hasNode(id)) return id;
|
|
2439
|
+
const node = {
|
|
2440
|
+
id,
|
|
2441
|
+
type: import_types3.NodeType.GraphQLOperationNode,
|
|
2442
|
+
name: operationName,
|
|
2443
|
+
service: serviceName,
|
|
2444
|
+
operationType: operationType.toLowerCase(),
|
|
2445
|
+
operationName,
|
|
2446
|
+
discoveredVia: "otel"
|
|
2447
|
+
};
|
|
2448
|
+
graph.addNode(id, node);
|
|
2449
|
+
return id;
|
|
2450
|
+
}
|
|
2451
|
+
function spanServesGrpcMethod(kind) {
|
|
2452
|
+
return kind !== WIRE_SPAN_KIND_CLIENT && kind !== WIRE_SPAN_KIND_PRODUCER && kind !== WIRE_SPAN_KIND_CONSUMER;
|
|
2453
|
+
}
|
|
2454
|
+
function ensureGrpcMethodNode(graph, rpcService, rpcMethod) {
|
|
2455
|
+
const id = (0, import_types3.grpcMethodId)(rpcService, rpcMethod);
|
|
2456
|
+
if (graph.hasNode(id)) return id;
|
|
2457
|
+
const node = {
|
|
2458
|
+
id,
|
|
2459
|
+
type: import_types3.NodeType.GrpcMethodNode,
|
|
2460
|
+
name: `${rpcService}/${rpcMethod}`,
|
|
2461
|
+
rpcService,
|
|
2462
|
+
rpcMethod,
|
|
2463
|
+
discoveredVia: "otel"
|
|
2464
|
+
};
|
|
2465
|
+
graph.addNode(id, node);
|
|
2466
|
+
return id;
|
|
2467
|
+
}
|
|
2468
|
+
function spanServesWebsocketChannel(kind) {
|
|
2469
|
+
return kind !== WIRE_SPAN_KIND_CLIENT && kind !== WIRE_SPAN_KIND_PRODUCER && kind !== WIRE_SPAN_KIND_CONSUMER;
|
|
2470
|
+
}
|
|
2471
|
+
function ensureWebsocketChannelNode(graph, serviceName, channel) {
|
|
2472
|
+
const id = (0, import_types3.websocketChannelId)(serviceName, channel);
|
|
2473
|
+
if (graph.hasNode(id)) return id;
|
|
2474
|
+
const node = {
|
|
2475
|
+
id,
|
|
2476
|
+
type: import_types3.NodeType.WebSocketChannelNode,
|
|
2477
|
+
name: channel,
|
|
2478
|
+
service: serviceName,
|
|
2479
|
+
channel,
|
|
2480
|
+
discoveredVia: "otel"
|
|
2481
|
+
};
|
|
2482
|
+
graph.addNode(id, node);
|
|
2483
|
+
return id;
|
|
2484
|
+
}
|
|
2485
|
+
function messagingDestinationKind(system) {
|
|
2486
|
+
return `${system}-topic`;
|
|
2487
|
+
}
|
|
2488
|
+
function ensureMessagingDestinationNode(graph, system, destination) {
|
|
2489
|
+
const id = (0, import_types3.infraId)(messagingDestinationKind(system), destination);
|
|
2490
|
+
if (graph.hasNode(id)) return id;
|
|
2491
|
+
const node = {
|
|
2492
|
+
id,
|
|
2493
|
+
type: import_types3.NodeType.InfraNode,
|
|
2494
|
+
name: destination,
|
|
2495
|
+
provider: "self",
|
|
2496
|
+
kind: messagingDestinationKind(system)
|
|
2497
|
+
};
|
|
2498
|
+
graph.addNode(id, node);
|
|
2499
|
+
return id;
|
|
2500
|
+
}
|
|
2375
2501
|
var PARENT_SPAN_CACHE_SIZE = 1e4;
|
|
2376
2502
|
var PARENT_SPAN_CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
2377
2503
|
var parentSpanCache = /* @__PURE__ */ new Map();
|
|
@@ -2465,6 +2591,21 @@ function ensureDatabaseNode(graph, host, engine) {
|
|
|
2465
2591
|
graph.addNode(id, node);
|
|
2466
2592
|
return id;
|
|
2467
2593
|
}
|
|
2594
|
+
function ensureLocalDatabaseNode(graph, serviceName, name, engine) {
|
|
2595
|
+
const id = (0, import_types3.localDatabaseId)(serviceName, name);
|
|
2596
|
+
if (graph.hasNode(id)) return id;
|
|
2597
|
+
const node = {
|
|
2598
|
+
id,
|
|
2599
|
+
type: import_types3.NodeType.DatabaseNode,
|
|
2600
|
+
name,
|
|
2601
|
+
engine,
|
|
2602
|
+
engineVersion: "unknown",
|
|
2603
|
+
compatibleDrivers: [],
|
|
2604
|
+
discoveredVia: "otel"
|
|
2605
|
+
};
|
|
2606
|
+
graph.addNode(id, node);
|
|
2607
|
+
return id;
|
|
2608
|
+
}
|
|
2468
2609
|
function ensureFrontierNode(graph, host, ts) {
|
|
2469
2610
|
const id = frontierIdFor(host);
|
|
2470
2611
|
if (graph.hasNode(id)) {
|
|
@@ -2716,10 +2857,21 @@ async function handleSpan(ctx, span) {
|
|
|
2716
2857
|
let affectedNode = sourceId;
|
|
2717
2858
|
const mintsFromCallerSide = spanMintsObservedEdge(span.kind);
|
|
2718
2859
|
if (span.dbSystem) {
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2860
|
+
if (mintsFromCallerSide) {
|
|
2861
|
+
const host = pickAddress(span);
|
|
2862
|
+
let targetId;
|
|
2863
|
+
if (host) {
|
|
2864
|
+
ensureDatabaseNode(ctx.graph, host, span.dbSystem);
|
|
2865
|
+
targetId = (0, import_types3.databaseId)(host);
|
|
2866
|
+
} else {
|
|
2867
|
+
const localName = span.dbName ?? span.dbSystem;
|
|
2868
|
+
targetId = ensureLocalDatabaseNode(
|
|
2869
|
+
ctx.graph,
|
|
2870
|
+
span.service,
|
|
2871
|
+
localName,
|
|
2872
|
+
span.dbSystem
|
|
2873
|
+
);
|
|
2874
|
+
}
|
|
2723
2875
|
const result = upsertObservedEdge(
|
|
2724
2876
|
ctx.graph,
|
|
2725
2877
|
import_types3.EdgeType.CONNECTS_TO,
|
|
@@ -2731,6 +2883,68 @@ async function handleSpan(ctx, span) {
|
|
|
2731
2883
|
);
|
|
2732
2884
|
if (result) affectedNode = targetId;
|
|
2733
2885
|
}
|
|
2886
|
+
} else if (span.messagingSystem && span.messagingDestination && spanMintsMessagingEdge(span.kind)) {
|
|
2887
|
+
const targetId = ensureMessagingDestinationNode(
|
|
2888
|
+
ctx.graph,
|
|
2889
|
+
span.messagingSystem,
|
|
2890
|
+
span.messagingDestination
|
|
2891
|
+
);
|
|
2892
|
+
const edgeType = span.kind === WIRE_SPAN_KIND_CONSUMER ? import_types3.EdgeType.CONSUMES_FROM : import_types3.EdgeType.PUBLISHES_TO;
|
|
2893
|
+
const result = upsertObservedEdge(
|
|
2894
|
+
ctx.graph,
|
|
2895
|
+
edgeType,
|
|
2896
|
+
observedSource(),
|
|
2897
|
+
targetId,
|
|
2898
|
+
ts,
|
|
2899
|
+
isError,
|
|
2900
|
+
callSiteEvidence
|
|
2901
|
+
);
|
|
2902
|
+
if (result) affectedNode = targetId;
|
|
2903
|
+
} else if (span.graphqlOperationName && span.graphqlOperationType && spanServesGraphqlOperation(span.kind)) {
|
|
2904
|
+
const targetId = ensureGraphqlOperationNode(
|
|
2905
|
+
ctx.graph,
|
|
2906
|
+
span.service,
|
|
2907
|
+
span.graphqlOperationType,
|
|
2908
|
+
span.graphqlOperationName
|
|
2909
|
+
);
|
|
2910
|
+
const result = upsertObservedEdge(
|
|
2911
|
+
ctx.graph,
|
|
2912
|
+
import_types3.EdgeType.CONTAINS,
|
|
2913
|
+
observedSource(),
|
|
2914
|
+
targetId,
|
|
2915
|
+
ts,
|
|
2916
|
+
isError,
|
|
2917
|
+
callSiteEvidence
|
|
2918
|
+
);
|
|
2919
|
+
if (result) affectedNode = targetId;
|
|
2920
|
+
} else if (span.rpcSystem === "grpc" && span.rpcService && span.rpcMethod && spanServesGrpcMethod(span.kind)) {
|
|
2921
|
+
const targetId = ensureGrpcMethodNode(ctx.graph, span.rpcService, span.rpcMethod);
|
|
2922
|
+
const result = upsertObservedEdge(
|
|
2923
|
+
ctx.graph,
|
|
2924
|
+
import_types3.EdgeType.CONTAINS,
|
|
2925
|
+
observedSource(),
|
|
2926
|
+
targetId,
|
|
2927
|
+
ts,
|
|
2928
|
+
isError,
|
|
2929
|
+
callSiteEvidence
|
|
2930
|
+
);
|
|
2931
|
+
if (result) affectedNode = targetId;
|
|
2932
|
+
} else if (span.websocketChannel && spanServesWebsocketChannel(span.kind)) {
|
|
2933
|
+
const targetId = ensureWebsocketChannelNode(
|
|
2934
|
+
ctx.graph,
|
|
2935
|
+
span.service,
|
|
2936
|
+
span.websocketChannel
|
|
2937
|
+
);
|
|
2938
|
+
const result = upsertObservedEdge(
|
|
2939
|
+
ctx.graph,
|
|
2940
|
+
import_types3.EdgeType.CONNECTS_TO,
|
|
2941
|
+
observedSource(),
|
|
2942
|
+
targetId,
|
|
2943
|
+
ts,
|
|
2944
|
+
isError,
|
|
2945
|
+
callSiteEvidence
|
|
2946
|
+
);
|
|
2947
|
+
if (result) affectedNode = targetId;
|
|
2734
2948
|
} else {
|
|
2735
2949
|
const host = pickAddress(span);
|
|
2736
2950
|
let resolvedViaAddress = false;
|
|
@@ -2840,29 +3054,29 @@ function promoteFrontierNodes(graph, opts = {}) {
|
|
|
2840
3054
|
toPromote.push({ frontierId: id, serviceId: target });
|
|
2841
3055
|
});
|
|
2842
3056
|
let promoted = 0;
|
|
2843
|
-
for (const { frontierId: frontierId2, serviceId:
|
|
3057
|
+
for (const { frontierId: frontierId2, serviceId: serviceId4 } of toPromote) {
|
|
2844
3058
|
if (opts.policies && opts.policies.length > 0 && opts.policyCtx) {
|
|
2845
3059
|
const gate = canPromoteFrontier(graph, frontierId2, opts.policies, opts.policyCtx);
|
|
2846
3060
|
if (!gate.allowed) {
|
|
2847
3061
|
continue;
|
|
2848
3062
|
}
|
|
2849
3063
|
}
|
|
2850
|
-
rewireFrontierEdges(graph, frontierId2,
|
|
3064
|
+
rewireFrontierEdges(graph, frontierId2, serviceId4);
|
|
2851
3065
|
graph.dropNode(frontierId2);
|
|
2852
3066
|
promoted++;
|
|
2853
3067
|
}
|
|
2854
3068
|
return promoted;
|
|
2855
3069
|
}
|
|
2856
|
-
function rewireFrontierEdges(graph, frontierId2,
|
|
3070
|
+
function rewireFrontierEdges(graph, frontierId2, serviceId4) {
|
|
2857
3071
|
const inbound = [...graph.inboundEdges(frontierId2)];
|
|
2858
3072
|
const outbound = [...graph.outboundEdges(frontierId2)];
|
|
2859
3073
|
for (const edgeId of inbound) {
|
|
2860
3074
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
2861
|
-
rebuildEdge(graph, edge, edge.source,
|
|
3075
|
+
rebuildEdge(graph, edge, edge.source, serviceId4, edgeId);
|
|
2862
3076
|
}
|
|
2863
3077
|
for (const edgeId of outbound) {
|
|
2864
3078
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
2865
|
-
rebuildEdge(graph, edge,
|
|
3079
|
+
rebuildEdge(graph, edge, serviceId4, edge.target, edgeId);
|
|
2866
3080
|
}
|
|
2867
3081
|
}
|
|
2868
3082
|
function rebuildEdge(graph, edge, newSource, newTarget, oldEdgeId) {
|
|
@@ -3624,9 +3838,9 @@ var K8S_KINDS_WITH_HOSTNAMES = /* @__PURE__ */ new Set([
|
|
|
3624
3838
|
"StatefulSet",
|
|
3625
3839
|
"DaemonSet"
|
|
3626
3840
|
]);
|
|
3627
|
-
function addAliases(graph,
|
|
3628
|
-
if (!graph.hasNode(
|
|
3629
|
-
const node = graph.getNodeAttributes(
|
|
3841
|
+
function addAliases(graph, serviceId4, candidates) {
|
|
3842
|
+
if (!graph.hasNode(serviceId4)) return;
|
|
3843
|
+
const node = graph.getNodeAttributes(serviceId4);
|
|
3630
3844
|
if (node.type !== import_types6.NodeType.ServiceNode) return;
|
|
3631
3845
|
const set = new Set(node.aliases ?? []);
|
|
3632
3846
|
for (const c of candidates) {
|
|
@@ -3636,7 +3850,7 @@ function addAliases(graph, serviceId3, candidates) {
|
|
|
3636
3850
|
}
|
|
3637
3851
|
if (set.size === 0) return;
|
|
3638
3852
|
const updated = { ...node, aliases: [...set].sort() };
|
|
3639
|
-
graph.replaceNodeAttributes(
|
|
3853
|
+
graph.replaceNodeAttributes(serviceId4, updated);
|
|
3640
3854
|
}
|
|
3641
3855
|
function indexServicesByName(services) {
|
|
3642
3856
|
const map = /* @__PURE__ */ new Map();
|
|
@@ -3669,12 +3883,12 @@ async function collectComposeAliases(graph, scanPath, serviceIndex) {
|
|
|
3669
3883
|
}
|
|
3670
3884
|
if (!compose?.services) return;
|
|
3671
3885
|
for (const [composeName, svc] of Object.entries(compose.services)) {
|
|
3672
|
-
const
|
|
3673
|
-
if (!
|
|
3886
|
+
const serviceId4 = serviceIndex.get(composeName);
|
|
3887
|
+
if (!serviceId4) continue;
|
|
3674
3888
|
const aliases = /* @__PURE__ */ new Set([composeName]);
|
|
3675
3889
|
if (svc.container_name) aliases.add(svc.container_name);
|
|
3676
3890
|
if (svc.hostname) aliases.add(svc.hostname);
|
|
3677
|
-
addAliases(graph,
|
|
3891
|
+
addAliases(graph, serviceId4, aliases);
|
|
3678
3892
|
}
|
|
3679
3893
|
}
|
|
3680
3894
|
var LABEL_KEYS = /* @__PURE__ */ new Set([
|
|
@@ -3787,16 +4001,28 @@ init_cjs_shims();
|
|
|
3787
4001
|
var import_node_fs10 = require("fs");
|
|
3788
4002
|
var import_node_path10 = __toESM(require("path"), 1);
|
|
3789
4003
|
var import_types7 = require("@neat.is/types");
|
|
4004
|
+
function buildServiceHostIndex(services) {
|
|
4005
|
+
const knownHosts = /* @__PURE__ */ new Set();
|
|
4006
|
+
const hostToNodeId = /* @__PURE__ */ new Map();
|
|
4007
|
+
for (const service of services) {
|
|
4008
|
+
const base = import_node_path10.default.basename(service.dir);
|
|
4009
|
+
knownHosts.add(base);
|
|
4010
|
+
knownHosts.add(service.pkg.name);
|
|
4011
|
+
hostToNodeId.set(base, service.node.id);
|
|
4012
|
+
hostToNodeId.set(service.pkg.name, service.node.id);
|
|
4013
|
+
}
|
|
4014
|
+
return { knownHosts, hostToNodeId };
|
|
4015
|
+
}
|
|
3790
4016
|
async function walkSourceFiles(dir) {
|
|
3791
4017
|
const out = [];
|
|
3792
|
-
async function
|
|
4018
|
+
async function walk3(current) {
|
|
3793
4019
|
const entries = await import_node_fs10.promises.readdir(current, { withFileTypes: true }).catch(() => []);
|
|
3794
4020
|
for (const entry of entries) {
|
|
3795
4021
|
const full = import_node_path10.default.join(current, entry.name);
|
|
3796
4022
|
if (entry.isDirectory()) {
|
|
3797
4023
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
3798
4024
|
if (await isPythonVenvDir(full)) continue;
|
|
3799
|
-
await
|
|
4025
|
+
await walk3(full);
|
|
3800
4026
|
} else if (entry.isFile() && SERVICE_FILE_EXTENSIONS.has(import_node_path10.default.extname(entry.name)) && // Skip NEAT's own generated `otel-init.*` bootstrap — extracting it
|
|
3801
4027
|
// would attribute our instrumentation imports to the user's service.
|
|
3802
4028
|
!isNeatAuthoredSourceFile(entry.name)) {
|
|
@@ -3804,7 +4030,7 @@ async function walkSourceFiles(dir) {
|
|
|
3804
4030
|
}
|
|
3805
4031
|
}
|
|
3806
4032
|
}
|
|
3807
|
-
await
|
|
4033
|
+
await walk3(dir);
|
|
3808
4034
|
return out;
|
|
3809
4035
|
}
|
|
3810
4036
|
async function loadSourceFiles(dir) {
|
|
@@ -4898,20 +5124,20 @@ var import_node_path21 = __toESM(require("path"), 1);
|
|
|
4898
5124
|
var import_types10 = require("@neat.is/types");
|
|
4899
5125
|
async function walkConfigFiles(dir) {
|
|
4900
5126
|
const out = [];
|
|
4901
|
-
async function
|
|
5127
|
+
async function walk3(current) {
|
|
4902
5128
|
const entries = await import_node_fs14.promises.readdir(current, { withFileTypes: true });
|
|
4903
5129
|
for (const entry of entries) {
|
|
4904
5130
|
const full = import_node_path21.default.join(current, entry.name);
|
|
4905
5131
|
if (entry.isDirectory()) {
|
|
4906
5132
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
4907
5133
|
if (await isPythonVenvDir(full)) continue;
|
|
4908
|
-
await
|
|
5134
|
+
await walk3(full);
|
|
4909
5135
|
} else if (entry.isFile() && isConfigFile(entry.name).match) {
|
|
4910
5136
|
out.push(full);
|
|
4911
5137
|
}
|
|
4912
5138
|
}
|
|
4913
5139
|
}
|
|
4914
|
-
await
|
|
5140
|
+
await walk3(dir);
|
|
4915
5141
|
return out;
|
|
4916
5142
|
}
|
|
4917
5143
|
async function addConfigNodes(graph, services, scanPath) {
|
|
@@ -4959,17 +5185,468 @@ async function addConfigNodes(graph, services, scanPath) {
|
|
|
4959
5185
|
return { nodesAdded, edgesAdded };
|
|
4960
5186
|
}
|
|
4961
5187
|
|
|
5188
|
+
// src/extract/routes.ts
|
|
5189
|
+
init_cjs_shims();
|
|
5190
|
+
var import_node_path22 = __toESM(require("path"), 1);
|
|
5191
|
+
var import_tree_sitter2 = __toESM(require("tree-sitter"), 1);
|
|
5192
|
+
var import_tree_sitter_javascript2 = __toESM(require("tree-sitter-javascript"), 1);
|
|
5193
|
+
var import_types11 = require("@neat.is/types");
|
|
5194
|
+
var PARSE_CHUNK2 = 16384;
|
|
5195
|
+
function parseSource2(parser, source) {
|
|
5196
|
+
return parser.parse(
|
|
5197
|
+
(index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK2)
|
|
5198
|
+
);
|
|
5199
|
+
}
|
|
5200
|
+
function makeJsParser2() {
|
|
5201
|
+
const p = new import_tree_sitter2.default();
|
|
5202
|
+
p.setLanguage(import_tree_sitter_javascript2.default);
|
|
5203
|
+
return p;
|
|
5204
|
+
}
|
|
5205
|
+
var ROUTER_METHODS = /* @__PURE__ */ new Set([
|
|
5206
|
+
"get",
|
|
5207
|
+
"post",
|
|
5208
|
+
"put",
|
|
5209
|
+
"patch",
|
|
5210
|
+
"delete",
|
|
5211
|
+
"options",
|
|
5212
|
+
"head",
|
|
5213
|
+
"all"
|
|
5214
|
+
]);
|
|
5215
|
+
var NEXT_APP_METHODS = /* @__PURE__ */ new Set(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"]);
|
|
5216
|
+
var JS_ROUTE_EXTENSIONS = /* @__PURE__ */ new Set([".js", ".jsx", ".mjs", ".cjs", ".ts", ".tsx"]);
|
|
5217
|
+
function canonicalizeTemplate(raw) {
|
|
5218
|
+
let p = raw.split("?")[0].split("#")[0];
|
|
5219
|
+
if (!p.startsWith("/")) p = "/" + p;
|
|
5220
|
+
if (p.length > 1 && p.endsWith("/")) p = p.slice(0, -1);
|
|
5221
|
+
return p;
|
|
5222
|
+
}
|
|
5223
|
+
function isDynamicSegment(seg) {
|
|
5224
|
+
if (seg.length === 0) return false;
|
|
5225
|
+
if (seg.includes(":")) return true;
|
|
5226
|
+
if (seg.startsWith("{") || seg.startsWith("[")) return true;
|
|
5227
|
+
if (/^\d+$/.test(seg)) return true;
|
|
5228
|
+
if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(seg)) return true;
|
|
5229
|
+
if (/^[0-9a-f]{24,}$/i.test(seg)) return true;
|
|
5230
|
+
return false;
|
|
5231
|
+
}
|
|
5232
|
+
function normalizePathTemplate(raw) {
|
|
5233
|
+
const canonical = canonicalizeTemplate(raw);
|
|
5234
|
+
const segments = canonical.split("/").filter((s) => s.length > 0);
|
|
5235
|
+
const normalised = segments.map((seg) => isDynamicSegment(seg) ? ":param" : seg.toLowerCase());
|
|
5236
|
+
return "/" + normalised.join("/");
|
|
5237
|
+
}
|
|
5238
|
+
function walk(node, visit) {
|
|
5239
|
+
visit(node);
|
|
5240
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
5241
|
+
const child = node.namedChild(i);
|
|
5242
|
+
if (child) walk(child, visit);
|
|
5243
|
+
}
|
|
5244
|
+
}
|
|
5245
|
+
function staticStringText(node) {
|
|
5246
|
+
if (node.type === "string") {
|
|
5247
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
5248
|
+
const child = node.namedChild(i);
|
|
5249
|
+
if (child?.type === "string_fragment") return child.text;
|
|
5250
|
+
}
|
|
5251
|
+
return "";
|
|
5252
|
+
}
|
|
5253
|
+
if (node.type === "template_string") {
|
|
5254
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
5255
|
+
if (node.namedChild(i)?.type === "template_substitution") return null;
|
|
5256
|
+
}
|
|
5257
|
+
const raw = node.text;
|
|
5258
|
+
return raw.length >= 2 ? raw.slice(1, -1) : "";
|
|
5259
|
+
}
|
|
5260
|
+
return null;
|
|
5261
|
+
}
|
|
5262
|
+
function objectStringProp(objNode, key) {
|
|
5263
|
+
for (let i = 0; i < objNode.namedChildCount; i++) {
|
|
5264
|
+
const pair = objNode.namedChild(i);
|
|
5265
|
+
if (!pair || pair.type !== "pair") continue;
|
|
5266
|
+
const k = pair.childForFieldName("key");
|
|
5267
|
+
if (!k) continue;
|
|
5268
|
+
const kText = k.type === "string" ? staticStringText(k) : k.text;
|
|
5269
|
+
if (kText !== key) continue;
|
|
5270
|
+
const v = pair.childForFieldName("value");
|
|
5271
|
+
if (v) return staticStringText(v);
|
|
5272
|
+
}
|
|
5273
|
+
return null;
|
|
5274
|
+
}
|
|
5275
|
+
function fastifyRouteMethods(objNode) {
|
|
5276
|
+
for (let i = 0; i < objNode.namedChildCount; i++) {
|
|
5277
|
+
const pair = objNode.namedChild(i);
|
|
5278
|
+
if (!pair || pair.type !== "pair") continue;
|
|
5279
|
+
const k = pair.childForFieldName("key");
|
|
5280
|
+
const kText = k ? k.type === "string" ? staticStringText(k) : k.text : null;
|
|
5281
|
+
if (kText !== "method") continue;
|
|
5282
|
+
const v = pair.childForFieldName("value");
|
|
5283
|
+
if (!v) return [];
|
|
5284
|
+
if (v.type === "string" || v.type === "template_string") {
|
|
5285
|
+
const s = staticStringText(v);
|
|
5286
|
+
return s ? [s.toUpperCase()] : [];
|
|
5287
|
+
}
|
|
5288
|
+
if (v.type === "array") {
|
|
5289
|
+
const out = [];
|
|
5290
|
+
for (let j = 0; j < v.namedChildCount; j++) {
|
|
5291
|
+
const el = v.namedChild(j);
|
|
5292
|
+
if (el && (el.type === "string" || el.type === "template_string")) {
|
|
5293
|
+
const s = staticStringText(el);
|
|
5294
|
+
if (s) out.push(s.toUpperCase());
|
|
5295
|
+
}
|
|
5296
|
+
}
|
|
5297
|
+
return out;
|
|
5298
|
+
}
|
|
5299
|
+
}
|
|
5300
|
+
return [];
|
|
5301
|
+
}
|
|
5302
|
+
function serverRoutesFromSource(source, parser, hasExpress, hasFastify) {
|
|
5303
|
+
const tree = parseSource2(parser, source);
|
|
5304
|
+
const out = [];
|
|
5305
|
+
const framework = hasExpress ? "express" : "fastify";
|
|
5306
|
+
walk(tree.rootNode, (node) => {
|
|
5307
|
+
if (node.type !== "call_expression") return;
|
|
5308
|
+
const fn = node.childForFieldName("function");
|
|
5309
|
+
if (!fn || fn.type !== "member_expression") return;
|
|
5310
|
+
const prop = fn.childForFieldName("property");
|
|
5311
|
+
if (!prop) return;
|
|
5312
|
+
const method = prop.text.toLowerCase();
|
|
5313
|
+
const args = node.childForFieldName("arguments");
|
|
5314
|
+
const first = args?.namedChild(0);
|
|
5315
|
+
if (!first) return;
|
|
5316
|
+
const line = node.startPosition.row + 1;
|
|
5317
|
+
if (ROUTER_METHODS.has(method)) {
|
|
5318
|
+
const p = staticStringText(first);
|
|
5319
|
+
if (p && p.startsWith("/")) {
|
|
5320
|
+
out.push({
|
|
5321
|
+
method: method === "all" ? "ALL" : method.toUpperCase(),
|
|
5322
|
+
pathTemplate: canonicalizeTemplate(p),
|
|
5323
|
+
line,
|
|
5324
|
+
framework
|
|
5325
|
+
});
|
|
5326
|
+
}
|
|
5327
|
+
return;
|
|
5328
|
+
}
|
|
5329
|
+
if (method === "route" && hasFastify && first.type === "object") {
|
|
5330
|
+
const url = objectStringProp(first, "url");
|
|
5331
|
+
if (!url || !url.startsWith("/")) return;
|
|
5332
|
+
const methods = fastifyRouteMethods(first);
|
|
5333
|
+
const list = methods.length > 0 ? methods : ["ALL"];
|
|
5334
|
+
for (const m of list) {
|
|
5335
|
+
out.push({
|
|
5336
|
+
method: m === "ALL" ? "ALL" : m.toUpperCase(),
|
|
5337
|
+
pathTemplate: canonicalizeTemplate(url),
|
|
5338
|
+
line,
|
|
5339
|
+
framework: "fastify"
|
|
5340
|
+
});
|
|
5341
|
+
}
|
|
5342
|
+
}
|
|
5343
|
+
});
|
|
5344
|
+
return out;
|
|
5345
|
+
}
|
|
5346
|
+
function segmentsOf(relFile) {
|
|
5347
|
+
return toPosix2(relFile).split("/").filter((s) => s.length > 0);
|
|
5348
|
+
}
|
|
5349
|
+
function isNextAppRouteFile(relFile) {
|
|
5350
|
+
const segs = segmentsOf(relFile);
|
|
5351
|
+
if (!segs.includes("app")) return false;
|
|
5352
|
+
const base = segs[segs.length - 1] ?? "";
|
|
5353
|
+
return /^route\.(?:js|jsx|mjs|cjs|ts|tsx)$/.test(base);
|
|
5354
|
+
}
|
|
5355
|
+
function isNextPagesApiFile(relFile) {
|
|
5356
|
+
const segs = segmentsOf(relFile);
|
|
5357
|
+
const pagesIdx = segs.indexOf("pages");
|
|
5358
|
+
if (pagesIdx === -1 || segs[pagesIdx + 1] !== "api") return false;
|
|
5359
|
+
const base = segs[segs.length - 1] ?? "";
|
|
5360
|
+
if (/^_(app|document|middleware)\./.test(base)) return false;
|
|
5361
|
+
return JS_ROUTE_EXTENSIONS.has(import_node_path22.default.extname(base));
|
|
5362
|
+
}
|
|
5363
|
+
function nextSegment(seg) {
|
|
5364
|
+
if (seg.startsWith("(") && seg.endsWith(")")) return null;
|
|
5365
|
+
const catchAll = seg.match(/^\[\[?\.\.\.(.+?)\]?\]$/);
|
|
5366
|
+
if (catchAll) return ":" + catchAll[1];
|
|
5367
|
+
const dynamic = seg.match(/^\[(.+?)\]$/);
|
|
5368
|
+
if (dynamic) return ":" + dynamic[1];
|
|
5369
|
+
return seg;
|
|
5370
|
+
}
|
|
5371
|
+
function nextAppPathTemplate(relFile) {
|
|
5372
|
+
const segs = segmentsOf(relFile);
|
|
5373
|
+
const appIdx = segs.lastIndexOf("app");
|
|
5374
|
+
const between = segs.slice(appIdx + 1, segs.length - 1);
|
|
5375
|
+
const parts = [];
|
|
5376
|
+
for (const seg of between) {
|
|
5377
|
+
const mapped = nextSegment(seg);
|
|
5378
|
+
if (mapped !== null) parts.push(mapped);
|
|
5379
|
+
}
|
|
5380
|
+
return "/" + parts.join("/");
|
|
5381
|
+
}
|
|
5382
|
+
function nextPagesApiPathTemplate(relFile) {
|
|
5383
|
+
const segs = segmentsOf(relFile);
|
|
5384
|
+
const pagesIdx = segs.indexOf("pages");
|
|
5385
|
+
const rest = segs.slice(pagesIdx + 1);
|
|
5386
|
+
const parts = [];
|
|
5387
|
+
for (let i = 0; i < rest.length; i++) {
|
|
5388
|
+
let seg = rest[i];
|
|
5389
|
+
if (i === rest.length - 1) {
|
|
5390
|
+
seg = seg.replace(/\.(?:js|jsx|mjs|cjs|ts|tsx)$/, "");
|
|
5391
|
+
if (seg === "index") continue;
|
|
5392
|
+
}
|
|
5393
|
+
const mapped = nextSegment(seg);
|
|
5394
|
+
if (mapped !== null) parts.push(mapped);
|
|
5395
|
+
}
|
|
5396
|
+
return "/" + parts.join("/");
|
|
5397
|
+
}
|
|
5398
|
+
function nextAppMethods(root) {
|
|
5399
|
+
const out = [];
|
|
5400
|
+
walk(root, (node) => {
|
|
5401
|
+
if (node.type !== "export_statement") return;
|
|
5402
|
+
const decl = node.childForFieldName("declaration");
|
|
5403
|
+
if (!decl) return;
|
|
5404
|
+
const line = node.startPosition.row + 1;
|
|
5405
|
+
if (decl.type === "function_declaration") {
|
|
5406
|
+
const name = decl.childForFieldName("name")?.text;
|
|
5407
|
+
if (name && NEXT_APP_METHODS.has(name)) out.push({ method: name, line });
|
|
5408
|
+
return;
|
|
5409
|
+
}
|
|
5410
|
+
if (decl.type === "lexical_declaration" || decl.type === "variable_declaration") {
|
|
5411
|
+
for (let i = 0; i < decl.namedChildCount; i++) {
|
|
5412
|
+
const d = decl.namedChild(i);
|
|
5413
|
+
if (d?.type !== "variable_declarator") continue;
|
|
5414
|
+
const name = d.childForFieldName("name")?.text;
|
|
5415
|
+
if (name && NEXT_APP_METHODS.has(name)) out.push({ method: name, line });
|
|
5416
|
+
}
|
|
5417
|
+
}
|
|
5418
|
+
});
|
|
5419
|
+
return out;
|
|
5420
|
+
}
|
|
5421
|
+
function nextRoutesFromFile(source, relFile, parser) {
|
|
5422
|
+
if (isNextAppRouteFile(relFile)) {
|
|
5423
|
+
const tree = parseSource2(parser, source);
|
|
5424
|
+
const template = nextAppPathTemplate(relFile);
|
|
5425
|
+
return nextAppMethods(tree.rootNode).map(({ method, line }) => ({
|
|
5426
|
+
method,
|
|
5427
|
+
pathTemplate: canonicalizeTemplate(template),
|
|
5428
|
+
line,
|
|
5429
|
+
framework: "next"
|
|
5430
|
+
}));
|
|
5431
|
+
}
|
|
5432
|
+
if (isNextPagesApiFile(relFile)) {
|
|
5433
|
+
return [
|
|
5434
|
+
{
|
|
5435
|
+
method: "ALL",
|
|
5436
|
+
pathTemplate: canonicalizeTemplate(nextPagesApiPathTemplate(relFile)),
|
|
5437
|
+
line: 1,
|
|
5438
|
+
framework: "next"
|
|
5439
|
+
}
|
|
5440
|
+
];
|
|
5441
|
+
}
|
|
5442
|
+
return [];
|
|
5443
|
+
}
|
|
5444
|
+
async function addRoutes(graph, services) {
|
|
5445
|
+
const jsParser = makeJsParser2();
|
|
5446
|
+
let nodesAdded = 0;
|
|
5447
|
+
let edgesAdded = 0;
|
|
5448
|
+
for (const service of services) {
|
|
5449
|
+
const deps = {
|
|
5450
|
+
...service.pkg.dependencies ?? {},
|
|
5451
|
+
...service.pkg.devDependencies ?? {}
|
|
5452
|
+
};
|
|
5453
|
+
const hasExpress = deps["express"] !== void 0;
|
|
5454
|
+
const hasFastify = deps["fastify"] !== void 0;
|
|
5455
|
+
const hasNext = deps["next"] !== void 0;
|
|
5456
|
+
if (!hasExpress && !hasFastify && !hasNext) continue;
|
|
5457
|
+
const files = await loadSourceFiles(service.dir);
|
|
5458
|
+
for (const file of files) {
|
|
5459
|
+
if (isTestPath(file.path)) continue;
|
|
5460
|
+
if (!JS_ROUTE_EXTENSIONS.has(import_node_path22.default.extname(file.path))) continue;
|
|
5461
|
+
const relFile = toPosix2(import_node_path22.default.relative(service.dir, file.path));
|
|
5462
|
+
let routes;
|
|
5463
|
+
try {
|
|
5464
|
+
if (hasNext && (isNextAppRouteFile(relFile) || isNextPagesApiFile(relFile))) {
|
|
5465
|
+
routes = nextRoutesFromFile(file.content, relFile, jsParser);
|
|
5466
|
+
} else if (hasExpress || hasFastify) {
|
|
5467
|
+
routes = serverRoutesFromSource(file.content, jsParser, hasExpress, hasFastify);
|
|
5468
|
+
} else {
|
|
5469
|
+
routes = [];
|
|
5470
|
+
}
|
|
5471
|
+
} catch (err) {
|
|
5472
|
+
recordExtractionError("route extraction", file.path, err);
|
|
5473
|
+
continue;
|
|
5474
|
+
}
|
|
5475
|
+
if (routes.length === 0) continue;
|
|
5476
|
+
for (const route of routes) {
|
|
5477
|
+
const rid = (0, import_types11.routeId)(service.pkg.name, route.method, route.pathTemplate);
|
|
5478
|
+
if (!graph.hasNode(rid)) {
|
|
5479
|
+
const node = {
|
|
5480
|
+
id: rid,
|
|
5481
|
+
type: import_types11.NodeType.RouteNode,
|
|
5482
|
+
name: `${route.method} ${route.pathTemplate}`,
|
|
5483
|
+
service: service.pkg.name,
|
|
5484
|
+
method: route.method,
|
|
5485
|
+
pathTemplate: route.pathTemplate,
|
|
5486
|
+
path: relFile,
|
|
5487
|
+
line: route.line,
|
|
5488
|
+
framework: route.framework,
|
|
5489
|
+
discoveredVia: "static"
|
|
5490
|
+
};
|
|
5491
|
+
graph.addNode(rid, node);
|
|
5492
|
+
nodesAdded++;
|
|
5493
|
+
}
|
|
5494
|
+
const containsId = (0, import_types11.extractedEdgeId)(service.node.id, rid, import_types11.EdgeType.CONTAINS);
|
|
5495
|
+
if (!graph.hasEdge(containsId)) {
|
|
5496
|
+
const edge = {
|
|
5497
|
+
id: containsId,
|
|
5498
|
+
source: service.node.id,
|
|
5499
|
+
target: rid,
|
|
5500
|
+
type: import_types11.EdgeType.CONTAINS,
|
|
5501
|
+
provenance: import_types11.Provenance.EXTRACTED,
|
|
5502
|
+
confidence: (0, import_types11.confidenceForExtracted)("structural"),
|
|
5503
|
+
evidence: {
|
|
5504
|
+
file: relFile,
|
|
5505
|
+
line: route.line,
|
|
5506
|
+
snippet: snippet(file.content, route.line)
|
|
5507
|
+
}
|
|
5508
|
+
};
|
|
5509
|
+
graph.addEdgeWithKey(containsId, service.node.id, rid, edge);
|
|
5510
|
+
edgesAdded++;
|
|
5511
|
+
}
|
|
5512
|
+
}
|
|
5513
|
+
}
|
|
5514
|
+
}
|
|
5515
|
+
return { nodesAdded, edgesAdded };
|
|
5516
|
+
}
|
|
5517
|
+
|
|
5518
|
+
// src/extract/proto.ts
|
|
5519
|
+
init_cjs_shims();
|
|
5520
|
+
var import_node_fs15 = require("fs");
|
|
5521
|
+
var import_node_path23 = __toESM(require("path"), 1);
|
|
5522
|
+
var import_types12 = require("@neat.is/types");
|
|
5523
|
+
var PROTO_EXTENSION = ".proto";
|
|
5524
|
+
function packageOf(content) {
|
|
5525
|
+
const m = content.match(/^\s*package\s+([A-Za-z_][A-Za-z0-9_.]*)\s*;/m);
|
|
5526
|
+
return m ? m[1] : null;
|
|
5527
|
+
}
|
|
5528
|
+
function lineAt(content, offset) {
|
|
5529
|
+
return content.slice(0, offset).split("\n").length;
|
|
5530
|
+
}
|
|
5531
|
+
function grpcMethodsFromProto(content, fqPackage) {
|
|
5532
|
+
const out = [];
|
|
5533
|
+
const serviceRe = /\bservice\s+([A-Za-z_][A-Za-z0-9_]*)\s*\{/g;
|
|
5534
|
+
let sm;
|
|
5535
|
+
while ((sm = serviceRe.exec(content)) !== null) {
|
|
5536
|
+
const serviceName = sm[1];
|
|
5537
|
+
const rpcService = fqPackage ? `${fqPackage}.${serviceName}` : serviceName;
|
|
5538
|
+
const bodyStart = serviceRe.lastIndex;
|
|
5539
|
+
let depth = 1;
|
|
5540
|
+
let i = bodyStart;
|
|
5541
|
+
for (; i < content.length && depth > 0; i++) {
|
|
5542
|
+
const ch = content[i];
|
|
5543
|
+
if (ch === "{") depth++;
|
|
5544
|
+
else if (ch === "}") depth--;
|
|
5545
|
+
}
|
|
5546
|
+
const body = content.slice(bodyStart, i - 1);
|
|
5547
|
+
const rpcRe = /\brpc\s+([A-Za-z_][A-Za-z0-9_]*)\s*\(/g;
|
|
5548
|
+
let rm;
|
|
5549
|
+
while ((rm = rpcRe.exec(body)) !== null) {
|
|
5550
|
+
const rpcMethod = rm[1];
|
|
5551
|
+
const line = lineAt(content, bodyStart + rm.index);
|
|
5552
|
+
out.push({ rpcService, rpcMethod, line });
|
|
5553
|
+
}
|
|
5554
|
+
serviceRe.lastIndex = i;
|
|
5555
|
+
}
|
|
5556
|
+
return out;
|
|
5557
|
+
}
|
|
5558
|
+
async function walkProtoFiles(dir) {
|
|
5559
|
+
const out = [];
|
|
5560
|
+
async function walk3(current) {
|
|
5561
|
+
const entries = await import_node_fs15.promises.readdir(current, { withFileTypes: true }).catch(() => []);
|
|
5562
|
+
for (const entry of entries) {
|
|
5563
|
+
const full = import_node_path23.default.join(current, entry.name);
|
|
5564
|
+
if (entry.isDirectory()) {
|
|
5565
|
+
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
5566
|
+
if (await isPythonVenvDir(full)) continue;
|
|
5567
|
+
await walk3(full);
|
|
5568
|
+
} else if (entry.isFile() && import_node_path23.default.extname(entry.name) === PROTO_EXTENSION) {
|
|
5569
|
+
out.push(full);
|
|
5570
|
+
}
|
|
5571
|
+
}
|
|
5572
|
+
}
|
|
5573
|
+
await walk3(dir);
|
|
5574
|
+
return out;
|
|
5575
|
+
}
|
|
5576
|
+
async function addGrpcMethods(graph, services) {
|
|
5577
|
+
let nodesAdded = 0;
|
|
5578
|
+
let edgesAdded = 0;
|
|
5579
|
+
for (const service of services) {
|
|
5580
|
+
const protoPaths = await walkProtoFiles(service.dir);
|
|
5581
|
+
for (const protoPath of protoPaths) {
|
|
5582
|
+
if (isTestPath(protoPath)) continue;
|
|
5583
|
+
const relFile = toPosix2(import_node_path23.default.relative(service.dir, protoPath));
|
|
5584
|
+
let content;
|
|
5585
|
+
try {
|
|
5586
|
+
content = await import_node_fs15.promises.readFile(protoPath, "utf8");
|
|
5587
|
+
} catch (err) {
|
|
5588
|
+
recordExtractionError("proto extraction", protoPath, err);
|
|
5589
|
+
continue;
|
|
5590
|
+
}
|
|
5591
|
+
let methods;
|
|
5592
|
+
try {
|
|
5593
|
+
methods = grpcMethodsFromProto(content, packageOf(content));
|
|
5594
|
+
} catch (err) {
|
|
5595
|
+
recordExtractionError("proto extraction", protoPath, err);
|
|
5596
|
+
continue;
|
|
5597
|
+
}
|
|
5598
|
+
if (methods.length === 0) continue;
|
|
5599
|
+
for (const method of methods) {
|
|
5600
|
+
const mid = (0, import_types12.grpcMethodId)(method.rpcService, method.rpcMethod);
|
|
5601
|
+
if (!graph.hasNode(mid)) {
|
|
5602
|
+
const node = {
|
|
5603
|
+
id: mid,
|
|
5604
|
+
type: import_types12.NodeType.GrpcMethodNode,
|
|
5605
|
+
name: `${method.rpcService}/${method.rpcMethod}`,
|
|
5606
|
+
rpcService: method.rpcService,
|
|
5607
|
+
rpcMethod: method.rpcMethod,
|
|
5608
|
+
path: relFile,
|
|
5609
|
+
line: method.line,
|
|
5610
|
+
discoveredVia: "static"
|
|
5611
|
+
};
|
|
5612
|
+
graph.addNode(mid, node);
|
|
5613
|
+
nodesAdded++;
|
|
5614
|
+
}
|
|
5615
|
+
const containsId = (0, import_types12.extractedEdgeId)(service.node.id, mid, import_types12.EdgeType.CONTAINS);
|
|
5616
|
+
if (!graph.hasEdge(containsId)) {
|
|
5617
|
+
const edge = {
|
|
5618
|
+
id: containsId,
|
|
5619
|
+
source: service.node.id,
|
|
5620
|
+
target: mid,
|
|
5621
|
+
type: import_types12.EdgeType.CONTAINS,
|
|
5622
|
+
provenance: import_types12.Provenance.EXTRACTED,
|
|
5623
|
+
confidence: (0, import_types12.confidenceForExtracted)("structural"),
|
|
5624
|
+
evidence: {
|
|
5625
|
+
file: relFile,
|
|
5626
|
+
line: method.line,
|
|
5627
|
+
snippet: snippet(content, method.line)
|
|
5628
|
+
}
|
|
5629
|
+
};
|
|
5630
|
+
graph.addEdgeWithKey(containsId, service.node.id, mid, edge);
|
|
5631
|
+
edgesAdded++;
|
|
5632
|
+
}
|
|
5633
|
+
}
|
|
5634
|
+
}
|
|
5635
|
+
}
|
|
5636
|
+
return { nodesAdded, edgesAdded };
|
|
5637
|
+
}
|
|
5638
|
+
|
|
4962
5639
|
// src/extract/calls/index.ts
|
|
4963
5640
|
init_cjs_shims();
|
|
4964
|
-
var
|
|
5641
|
+
var import_types20 = require("@neat.is/types");
|
|
4965
5642
|
|
|
4966
5643
|
// src/extract/calls/http.ts
|
|
4967
5644
|
init_cjs_shims();
|
|
4968
|
-
var
|
|
4969
|
-
var
|
|
4970
|
-
var
|
|
5645
|
+
var import_node_path24 = __toESM(require("path"), 1);
|
|
5646
|
+
var import_tree_sitter3 = __toESM(require("tree-sitter"), 1);
|
|
5647
|
+
var import_tree_sitter_javascript3 = __toESM(require("tree-sitter-javascript"), 1);
|
|
4971
5648
|
var import_tree_sitter_python2 = __toESM(require("tree-sitter-python"), 1);
|
|
4972
|
-
var
|
|
5649
|
+
var import_types13 = require("@neat.is/types");
|
|
4973
5650
|
var STRING_LITERAL_NODE_TYPES = /* @__PURE__ */ new Set(["string_fragment", "string_content"]);
|
|
4974
5651
|
var JSX_EXTERNAL_LINK_TAGS = /* @__PURE__ */ new Set(["a", "Link", "NavLink", "ExternalLink", "Anchor"]);
|
|
4975
5652
|
function isInsideJsxExternalLink(node) {
|
|
@@ -4997,14 +5674,14 @@ function collectStringLiterals(node, out) {
|
|
|
4997
5674
|
if (child) collectStringLiterals(child, out);
|
|
4998
5675
|
}
|
|
4999
5676
|
}
|
|
5000
|
-
var
|
|
5001
|
-
function
|
|
5677
|
+
var PARSE_CHUNK3 = 16384;
|
|
5678
|
+
function parseSource3(parser, source) {
|
|
5002
5679
|
return parser.parse(
|
|
5003
|
-
(index) => index >= source.length ? "" : source.slice(index, index +
|
|
5680
|
+
(index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK3)
|
|
5004
5681
|
);
|
|
5005
5682
|
}
|
|
5006
5683
|
function callsFromSource(source, parser, knownHosts) {
|
|
5007
|
-
const tree =
|
|
5684
|
+
const tree = parseSource3(parser, source);
|
|
5008
5685
|
const literals = [];
|
|
5009
5686
|
collectStringLiterals(tree.rootNode, literals);
|
|
5010
5687
|
const out = [];
|
|
@@ -5018,27 +5695,20 @@ function callsFromSource(source, parser, knownHosts) {
|
|
|
5018
5695
|
}
|
|
5019
5696
|
return out;
|
|
5020
5697
|
}
|
|
5021
|
-
function
|
|
5022
|
-
const p = new
|
|
5023
|
-
p.setLanguage(
|
|
5698
|
+
function makeJsParser3() {
|
|
5699
|
+
const p = new import_tree_sitter3.default();
|
|
5700
|
+
p.setLanguage(import_tree_sitter_javascript3.default);
|
|
5024
5701
|
return p;
|
|
5025
5702
|
}
|
|
5026
5703
|
function makePyParser2() {
|
|
5027
|
-
const p = new
|
|
5704
|
+
const p = new import_tree_sitter3.default();
|
|
5028
5705
|
p.setLanguage(import_tree_sitter_python2.default);
|
|
5029
5706
|
return p;
|
|
5030
5707
|
}
|
|
5031
5708
|
async function addHttpCallEdges(graph, services) {
|
|
5032
|
-
const jsParser =
|
|
5709
|
+
const jsParser = makeJsParser3();
|
|
5033
5710
|
const pyParser = makePyParser2();
|
|
5034
|
-
const knownHosts
|
|
5035
|
-
const hostToNodeId = /* @__PURE__ */ new Map();
|
|
5036
|
-
for (const service of services) {
|
|
5037
|
-
knownHosts.add(import_node_path22.default.basename(service.dir));
|
|
5038
|
-
knownHosts.add(service.pkg.name);
|
|
5039
|
-
hostToNodeId.set(import_node_path22.default.basename(service.dir), service.node.id);
|
|
5040
|
-
hostToNodeId.set(service.pkg.name, service.node.id);
|
|
5041
|
-
}
|
|
5711
|
+
const { knownHosts, hostToNodeId } = buildServiceHostIndex(services);
|
|
5042
5712
|
let nodesAdded = 0;
|
|
5043
5713
|
let edgesAdded = 0;
|
|
5044
5714
|
for (const service of services) {
|
|
@@ -5046,7 +5716,7 @@ async function addHttpCallEdges(graph, services) {
|
|
|
5046
5716
|
const seen = /* @__PURE__ */ new Set();
|
|
5047
5717
|
for (const file of files) {
|
|
5048
5718
|
if (isTestPath(file.path)) continue;
|
|
5049
|
-
const parser =
|
|
5719
|
+
const parser = import_node_path24.default.extname(file.path) === ".py" ? pyParser : jsParser;
|
|
5050
5720
|
let sites;
|
|
5051
5721
|
try {
|
|
5052
5722
|
sites = callsFromSource(file.content, parser, knownHosts);
|
|
@@ -5055,14 +5725,14 @@ async function addHttpCallEdges(graph, services) {
|
|
|
5055
5725
|
continue;
|
|
5056
5726
|
}
|
|
5057
5727
|
if (sites.length === 0) continue;
|
|
5058
|
-
const relFile = toPosix2(
|
|
5728
|
+
const relFile = toPosix2(import_node_path24.default.relative(service.dir, file.path));
|
|
5059
5729
|
for (const site of sites) {
|
|
5060
5730
|
const targetId = hostToNodeId.get(site.host);
|
|
5061
5731
|
if (!targetId || targetId === service.node.id) continue;
|
|
5062
5732
|
const dedupKey = `${relFile}|${targetId}`;
|
|
5063
5733
|
if (seen.has(dedupKey)) continue;
|
|
5064
5734
|
seen.add(dedupKey);
|
|
5065
|
-
const confidence = (0,
|
|
5735
|
+
const confidence = (0, import_types13.confidenceForExtracted)("url-literal-service-target");
|
|
5066
5736
|
const ev = {
|
|
5067
5737
|
file: relFile,
|
|
5068
5738
|
line: site.line,
|
|
@@ -5076,25 +5746,25 @@ async function addHttpCallEdges(graph, services) {
|
|
|
5076
5746
|
);
|
|
5077
5747
|
nodesAdded += n;
|
|
5078
5748
|
edgesAdded += e;
|
|
5079
|
-
if (!(0,
|
|
5749
|
+
if (!(0, import_types13.passesExtractedFloor)(confidence)) {
|
|
5080
5750
|
noteExtractedDropped({
|
|
5081
5751
|
source: fileNodeId,
|
|
5082
5752
|
target: targetId,
|
|
5083
|
-
type:
|
|
5753
|
+
type: import_types13.EdgeType.CALLS,
|
|
5084
5754
|
confidence,
|
|
5085
5755
|
confidenceKind: "url-literal-service-target",
|
|
5086
5756
|
evidence: ev
|
|
5087
5757
|
});
|
|
5088
5758
|
continue;
|
|
5089
5759
|
}
|
|
5090
|
-
const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, targetId,
|
|
5760
|
+
const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, targetId, import_types13.EdgeType.CALLS);
|
|
5091
5761
|
if (!graph.hasEdge(edgeId)) {
|
|
5092
5762
|
const edge = {
|
|
5093
5763
|
id: edgeId,
|
|
5094
5764
|
source: fileNodeId,
|
|
5095
5765
|
target: targetId,
|
|
5096
|
-
type:
|
|
5097
|
-
provenance:
|
|
5766
|
+
type: import_types13.EdgeType.CALLS,
|
|
5767
|
+
provenance: import_types13.Provenance.EXTRACTED,
|
|
5098
5768
|
confidence,
|
|
5099
5769
|
evidence: ev
|
|
5100
5770
|
};
|
|
@@ -5107,10 +5777,279 @@ async function addHttpCallEdges(graph, services) {
|
|
|
5107
5777
|
return { nodesAdded, edgesAdded };
|
|
5108
5778
|
}
|
|
5109
5779
|
|
|
5780
|
+
// src/extract/calls/route-match.ts
|
|
5781
|
+
init_cjs_shims();
|
|
5782
|
+
var import_node_path25 = __toESM(require("path"), 1);
|
|
5783
|
+
var import_tree_sitter4 = __toESM(require("tree-sitter"), 1);
|
|
5784
|
+
var import_tree_sitter_javascript4 = __toESM(require("tree-sitter-javascript"), 1);
|
|
5785
|
+
var import_types14 = require("@neat.is/types");
|
|
5786
|
+
var PARSE_CHUNK4 = 16384;
|
|
5787
|
+
function parseSource4(parser, source) {
|
|
5788
|
+
return parser.parse(
|
|
5789
|
+
(index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK4)
|
|
5790
|
+
);
|
|
5791
|
+
}
|
|
5792
|
+
function makeJsParser4() {
|
|
5793
|
+
const p = new import_tree_sitter4.default();
|
|
5794
|
+
p.setLanguage(import_tree_sitter_javascript4.default);
|
|
5795
|
+
return p;
|
|
5796
|
+
}
|
|
5797
|
+
var JS_CLIENT_EXTENSIONS = /* @__PURE__ */ new Set([".js", ".jsx", ".mjs", ".cjs", ".ts", ".tsx"]);
|
|
5798
|
+
var AXIOS_METHODS = /* @__PURE__ */ new Set(["get", "post", "put", "patch", "delete", "head", "options", "request"]);
|
|
5799
|
+
function walk2(node, visit) {
|
|
5800
|
+
visit(node);
|
|
5801
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
5802
|
+
const child = node.namedChild(i);
|
|
5803
|
+
if (child) walk2(child, visit);
|
|
5804
|
+
}
|
|
5805
|
+
}
|
|
5806
|
+
function reconstructUrl(node) {
|
|
5807
|
+
if (node.type === "string") {
|
|
5808
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
5809
|
+
const child = node.namedChild(i);
|
|
5810
|
+
if (child?.type === "string_fragment") return child.text;
|
|
5811
|
+
}
|
|
5812
|
+
return "";
|
|
5813
|
+
}
|
|
5814
|
+
if (node.type === "template_string") {
|
|
5815
|
+
let out = "";
|
|
5816
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
5817
|
+
const child = node.namedChild(i);
|
|
5818
|
+
if (!child) continue;
|
|
5819
|
+
if (child.type === "string_fragment") out += child.text;
|
|
5820
|
+
else if (child.type === "template_substitution") out += ":param";
|
|
5821
|
+
}
|
|
5822
|
+
if (out.length === 0) {
|
|
5823
|
+
const raw = node.text;
|
|
5824
|
+
return raw.length >= 2 ? raw.slice(1, -1) : "";
|
|
5825
|
+
}
|
|
5826
|
+
return out;
|
|
5827
|
+
}
|
|
5828
|
+
return null;
|
|
5829
|
+
}
|
|
5830
|
+
function methodFromOptions(objNode) {
|
|
5831
|
+
for (let i = 0; i < objNode.namedChildCount; i++) {
|
|
5832
|
+
const pair = objNode.namedChild(i);
|
|
5833
|
+
if (!pair || pair.type !== "pair") continue;
|
|
5834
|
+
const k = pair.childForFieldName("key");
|
|
5835
|
+
const kText = k ? k.type === "string" ? stringText(k) : k.text : null;
|
|
5836
|
+
if (kText !== "method") continue;
|
|
5837
|
+
const v = pair.childForFieldName("value");
|
|
5838
|
+
if (v && (v.type === "string" || v.type === "template_string")) {
|
|
5839
|
+
const s = stringText(v);
|
|
5840
|
+
return s ? s.toUpperCase() : void 0;
|
|
5841
|
+
}
|
|
5842
|
+
}
|
|
5843
|
+
return void 0;
|
|
5844
|
+
}
|
|
5845
|
+
function stringText(node) {
|
|
5846
|
+
if (node.type === "string") {
|
|
5847
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
5848
|
+
const child = node.namedChild(i);
|
|
5849
|
+
if (child?.type === "string_fragment") return child.text;
|
|
5850
|
+
}
|
|
5851
|
+
return "";
|
|
5852
|
+
}
|
|
5853
|
+
return null;
|
|
5854
|
+
}
|
|
5855
|
+
function urlNodeFromConfig(objNode) {
|
|
5856
|
+
for (let i = 0; i < objNode.namedChildCount; i++) {
|
|
5857
|
+
const pair = objNode.namedChild(i);
|
|
5858
|
+
if (!pair || pair.type !== "pair") continue;
|
|
5859
|
+
const k = pair.childForFieldName("key");
|
|
5860
|
+
const kText = k ? k.type === "string" ? stringText(k) : k.text : null;
|
|
5861
|
+
if (kText === "url") return pair.childForFieldName("value");
|
|
5862
|
+
}
|
|
5863
|
+
return null;
|
|
5864
|
+
}
|
|
5865
|
+
function pathOf(urlStr) {
|
|
5866
|
+
try {
|
|
5867
|
+
const candidate = urlStr.startsWith("//") ? `http:${urlStr}` : urlStr;
|
|
5868
|
+
const parsed = new URL(candidate);
|
|
5869
|
+
return parsed.pathname || "/";
|
|
5870
|
+
} catch {
|
|
5871
|
+
return null;
|
|
5872
|
+
}
|
|
5873
|
+
}
|
|
5874
|
+
function matchHost(urlStr, knownHosts) {
|
|
5875
|
+
for (const host of knownHosts) {
|
|
5876
|
+
if (urlMatchesHost(urlStr, host)) return host;
|
|
5877
|
+
}
|
|
5878
|
+
return null;
|
|
5879
|
+
}
|
|
5880
|
+
function clientCallSitesFromSource(source, parser, knownHosts) {
|
|
5881
|
+
const tree = parseSource4(parser, source);
|
|
5882
|
+
const out = [];
|
|
5883
|
+
const push = (urlNode, method, callNode) => {
|
|
5884
|
+
const urlStr = reconstructUrl(urlNode);
|
|
5885
|
+
if (!urlStr) return;
|
|
5886
|
+
const host = matchHost(urlStr, knownHosts);
|
|
5887
|
+
if (!host) return;
|
|
5888
|
+
const p = pathOf(urlStr);
|
|
5889
|
+
if (p === null) return;
|
|
5890
|
+
const line = callNode.startPosition.row + 1;
|
|
5891
|
+
out.push({
|
|
5892
|
+
host,
|
|
5893
|
+
method,
|
|
5894
|
+
pathTemplate: p,
|
|
5895
|
+
line,
|
|
5896
|
+
snippet: snippet(source, line)
|
|
5897
|
+
});
|
|
5898
|
+
};
|
|
5899
|
+
walk2(tree.rootNode, (node) => {
|
|
5900
|
+
if (node.type !== "call_expression") return;
|
|
5901
|
+
const fn = node.childForFieldName("function");
|
|
5902
|
+
if (!fn) return;
|
|
5903
|
+
const args = node.childForFieldName("arguments");
|
|
5904
|
+
const first = args?.namedChild(0);
|
|
5905
|
+
if (!first) return;
|
|
5906
|
+
const fnName = fn.type === "identifier" ? fn.text : fn.type === "member_expression" ? fn.childForFieldName("property")?.text ?? "" : "";
|
|
5907
|
+
if (fn.type === "identifier" && fnName === "fetch") {
|
|
5908
|
+
const opts = args?.namedChild(1);
|
|
5909
|
+
const method = opts && opts.type === "object" ? methodFromOptions(opts) ?? "GET" : "GET";
|
|
5910
|
+
push(first, method, node);
|
|
5911
|
+
return;
|
|
5912
|
+
}
|
|
5913
|
+
if (fn.type === "identifier" && fnName === "axios") {
|
|
5914
|
+
if (first.type === "object") {
|
|
5915
|
+
const urlNode = urlNodeFromConfig(first);
|
|
5916
|
+
if (urlNode) push(urlNode, methodFromOptions(first) ?? "GET", node);
|
|
5917
|
+
} else {
|
|
5918
|
+
const opts = args?.namedChild(1);
|
|
5919
|
+
const method = opts && opts.type === "object" ? methodFromOptions(opts) ?? "GET" : "GET";
|
|
5920
|
+
push(first, method, node);
|
|
5921
|
+
}
|
|
5922
|
+
return;
|
|
5923
|
+
}
|
|
5924
|
+
if (fn.type === "member_expression") {
|
|
5925
|
+
const obj = fn.childForFieldName("object");
|
|
5926
|
+
const objName = obj?.text ?? "";
|
|
5927
|
+
if (objName === "axios" && AXIOS_METHODS.has(fnName)) {
|
|
5928
|
+
if (fnName === "request" && first.type === "object") {
|
|
5929
|
+
const urlNode = urlNodeFromConfig(first);
|
|
5930
|
+
if (urlNode) push(urlNode, methodFromOptions(first) ?? "GET", node);
|
|
5931
|
+
} else {
|
|
5932
|
+
push(first, fnName.toUpperCase(), node);
|
|
5933
|
+
}
|
|
5934
|
+
return;
|
|
5935
|
+
}
|
|
5936
|
+
if ((objName === "http" || objName === "https") && (fnName === "request" || fnName === "get")) {
|
|
5937
|
+
const opts = args?.namedChild(1);
|
|
5938
|
+
const method = opts && opts.type === "object" ? methodFromOptions(opts) ?? (fnName === "get" ? "GET" : "GET") : "GET";
|
|
5939
|
+
push(first, method, node);
|
|
5940
|
+
return;
|
|
5941
|
+
}
|
|
5942
|
+
}
|
|
5943
|
+
});
|
|
5944
|
+
return out;
|
|
5945
|
+
}
|
|
5946
|
+
function buildRouteIndex(graph) {
|
|
5947
|
+
const index = /* @__PURE__ */ new Map();
|
|
5948
|
+
graph.forEachNode((_id, attrs) => {
|
|
5949
|
+
const node = attrs;
|
|
5950
|
+
if (node.type !== import_types14.NodeType.RouteNode) return;
|
|
5951
|
+
const route = attrs;
|
|
5952
|
+
const owner = (0, import_types14.serviceId)(route.service);
|
|
5953
|
+
const entry = {
|
|
5954
|
+
method: route.method.toUpperCase(),
|
|
5955
|
+
normalizedPath: normalizePathTemplate(route.pathTemplate),
|
|
5956
|
+
routeNodeId: route.id
|
|
5957
|
+
};
|
|
5958
|
+
const list = index.get(owner);
|
|
5959
|
+
if (list) list.push(entry);
|
|
5960
|
+
else index.set(owner, [entry]);
|
|
5961
|
+
});
|
|
5962
|
+
return index;
|
|
5963
|
+
}
|
|
5964
|
+
function findRoute(entries, method, normalizedPath) {
|
|
5965
|
+
return entries.find(
|
|
5966
|
+
(e) => e.normalizedPath === normalizedPath && (e.method === "ALL" || method === void 0 || e.method === method)
|
|
5967
|
+
);
|
|
5968
|
+
}
|
|
5969
|
+
async function addRouteCallEdges(graph, services) {
|
|
5970
|
+
const jsParser = makeJsParser4();
|
|
5971
|
+
const { knownHosts, hostToNodeId } = buildServiceHostIndex(services);
|
|
5972
|
+
const routeIndex = buildRouteIndex(graph);
|
|
5973
|
+
if (routeIndex.size === 0) return { nodesAdded: 0, edgesAdded: 0 };
|
|
5974
|
+
let nodesAdded = 0;
|
|
5975
|
+
let edgesAdded = 0;
|
|
5976
|
+
for (const service of services) {
|
|
5977
|
+
const files = await loadSourceFiles(service.dir);
|
|
5978
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5979
|
+
for (const file of files) {
|
|
5980
|
+
if (isTestPath(file.path)) continue;
|
|
5981
|
+
if (!JS_CLIENT_EXTENSIONS.has(import_node_path25.default.extname(file.path))) continue;
|
|
5982
|
+
let sites;
|
|
5983
|
+
try {
|
|
5984
|
+
sites = clientCallSitesFromSource(file.content, jsParser, knownHosts);
|
|
5985
|
+
} catch (err) {
|
|
5986
|
+
recordExtractionError("route-match call extraction", file.path, err);
|
|
5987
|
+
continue;
|
|
5988
|
+
}
|
|
5989
|
+
if (sites.length === 0) continue;
|
|
5990
|
+
const relFile = toPosix2(import_node_path25.default.relative(service.dir, file.path));
|
|
5991
|
+
for (const site of sites) {
|
|
5992
|
+
const serverServiceId = hostToNodeId.get(site.host);
|
|
5993
|
+
if (!serverServiceId || serverServiceId === service.node.id) continue;
|
|
5994
|
+
const entries = routeIndex.get(serverServiceId);
|
|
5995
|
+
if (!entries) continue;
|
|
5996
|
+
const normalizedPath = normalizePathTemplate(site.pathTemplate);
|
|
5997
|
+
const match = findRoute(entries, site.method, normalizedPath);
|
|
5998
|
+
if (!match) continue;
|
|
5999
|
+
const dedupKey = `${relFile}|${match.routeNodeId}`;
|
|
6000
|
+
if (seen.has(dedupKey)) continue;
|
|
6001
|
+
seen.add(dedupKey);
|
|
6002
|
+
const { fileNodeId, nodesAdded: n, edgesAdded: e } = ensureFileNode(
|
|
6003
|
+
graph,
|
|
6004
|
+
service.pkg.name,
|
|
6005
|
+
service.node.id,
|
|
6006
|
+
relFile
|
|
6007
|
+
);
|
|
6008
|
+
nodesAdded += n;
|
|
6009
|
+
edgesAdded += e;
|
|
6010
|
+
const confidence = (0, import_types14.confidenceForExtracted)("verified-call-site");
|
|
6011
|
+
const ev = {
|
|
6012
|
+
file: relFile,
|
|
6013
|
+
line: site.line,
|
|
6014
|
+
snippet: site.snippet,
|
|
6015
|
+
method: site.method ?? match.method,
|
|
6016
|
+
pathTemplate: site.pathTemplate
|
|
6017
|
+
};
|
|
6018
|
+
if (!(0, import_types14.passesExtractedFloor)(confidence)) {
|
|
6019
|
+
noteExtractedDropped({
|
|
6020
|
+
source: fileNodeId,
|
|
6021
|
+
target: match.routeNodeId,
|
|
6022
|
+
type: import_types14.EdgeType.CALLS,
|
|
6023
|
+
confidence,
|
|
6024
|
+
confidenceKind: "verified-call-site",
|
|
6025
|
+
evidence: ev
|
|
6026
|
+
});
|
|
6027
|
+
continue;
|
|
6028
|
+
}
|
|
6029
|
+
const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, match.routeNodeId, import_types14.EdgeType.CALLS);
|
|
6030
|
+
if (!graph.hasEdge(edgeId)) {
|
|
6031
|
+
const edge = {
|
|
6032
|
+
id: edgeId,
|
|
6033
|
+
source: fileNodeId,
|
|
6034
|
+
target: match.routeNodeId,
|
|
6035
|
+
type: import_types14.EdgeType.CALLS,
|
|
6036
|
+
provenance: import_types14.Provenance.EXTRACTED,
|
|
6037
|
+
confidence,
|
|
6038
|
+
evidence: ev
|
|
6039
|
+
};
|
|
6040
|
+
graph.addEdgeWithKey(edgeId, fileNodeId, match.routeNodeId, edge);
|
|
6041
|
+
edgesAdded++;
|
|
6042
|
+
}
|
|
6043
|
+
}
|
|
6044
|
+
}
|
|
6045
|
+
}
|
|
6046
|
+
return { nodesAdded, edgesAdded };
|
|
6047
|
+
}
|
|
6048
|
+
|
|
5110
6049
|
// src/extract/calls/kafka.ts
|
|
5111
6050
|
init_cjs_shims();
|
|
5112
|
-
var
|
|
5113
|
-
var
|
|
6051
|
+
var import_node_path26 = __toESM(require("path"), 1);
|
|
6052
|
+
var import_types15 = require("@neat.is/types");
|
|
5114
6053
|
var PRODUCER_TOPIC_RE = /(?:producer|kafkaProducer)[\s\S]{0,40}?\.send\s*\(\s*\{[\s\S]{0,200}?topic\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
5115
6054
|
var CONSUMER_TOPIC_RE = /(?:consumer|kafkaConsumer)[\s\S]{0,40}?\.(?:subscribe|run)\s*\(\s*\{[\s\S]{0,200}?topic[s]?\s*:\s*(?:\[\s*)?['"`]([^'"`]+)['"`]/g;
|
|
5116
6055
|
function findAll(re, text) {
|
|
@@ -5131,7 +6070,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
|
|
|
5131
6070
|
seen.add(key);
|
|
5132
6071
|
const line = lineOf(file.content, topic);
|
|
5133
6072
|
out.push({
|
|
5134
|
-
infraId: (0,
|
|
6073
|
+
infraId: (0, import_types15.infraId)("kafka-topic", topic),
|
|
5135
6074
|
name: topic,
|
|
5136
6075
|
kind: "kafka-topic",
|
|
5137
6076
|
edgeType,
|
|
@@ -5140,7 +6079,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
|
|
|
5140
6079
|
// tier (ADR-066).
|
|
5141
6080
|
confidenceKind: "verified-call-site",
|
|
5142
6081
|
evidence: {
|
|
5143
|
-
file:
|
|
6082
|
+
file: import_node_path26.default.relative(serviceDir, file.path),
|
|
5144
6083
|
line,
|
|
5145
6084
|
snippet: snippet(file.content, line)
|
|
5146
6085
|
}
|
|
@@ -5153,8 +6092,8 @@ function kafkaEndpointsFromFile(file, serviceDir) {
|
|
|
5153
6092
|
|
|
5154
6093
|
// src/extract/calls/redis.ts
|
|
5155
6094
|
init_cjs_shims();
|
|
5156
|
-
var
|
|
5157
|
-
var
|
|
6095
|
+
var import_node_path27 = __toESM(require("path"), 1);
|
|
6096
|
+
var import_types16 = require("@neat.is/types");
|
|
5158
6097
|
var REDIS_URL_RE = /redis(?:s)?:\/\/(?:[^@'"`\s]+@)?([^:/'"`\s]+)(?::(\d+))?/g;
|
|
5159
6098
|
function redisEndpointsFromFile(file, serviceDir) {
|
|
5160
6099
|
const out = [];
|
|
@@ -5167,7 +6106,7 @@ function redisEndpointsFromFile(file, serviceDir) {
|
|
|
5167
6106
|
seen.add(host);
|
|
5168
6107
|
const line = lineOf(file.content, host);
|
|
5169
6108
|
out.push({
|
|
5170
|
-
infraId: (0,
|
|
6109
|
+
infraId: (0, import_types16.infraId)("redis", host),
|
|
5171
6110
|
name: host,
|
|
5172
6111
|
kind: "redis",
|
|
5173
6112
|
edgeType: "CALLS",
|
|
@@ -5176,7 +6115,7 @@ function redisEndpointsFromFile(file, serviceDir) {
|
|
|
5176
6115
|
// support tier (ADR-066).
|
|
5177
6116
|
confidenceKind: "url-with-structural-support",
|
|
5178
6117
|
evidence: {
|
|
5179
|
-
file:
|
|
6118
|
+
file: import_node_path27.default.relative(serviceDir, file.path),
|
|
5180
6119
|
line,
|
|
5181
6120
|
snippet: snippet(file.content, line)
|
|
5182
6121
|
}
|
|
@@ -5187,8 +6126,8 @@ function redisEndpointsFromFile(file, serviceDir) {
|
|
|
5187
6126
|
|
|
5188
6127
|
// src/extract/calls/aws.ts
|
|
5189
6128
|
init_cjs_shims();
|
|
5190
|
-
var
|
|
5191
|
-
var
|
|
6129
|
+
var import_node_path28 = __toESM(require("path"), 1);
|
|
6130
|
+
var import_types17 = require("@neat.is/types");
|
|
5192
6131
|
var S3_BUCKET_RE = /Bucket\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
5193
6132
|
var DYNAMO_TABLE_RE = /TableName\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
5194
6133
|
function hasMarker(text, markers) {
|
|
@@ -5212,7 +6151,7 @@ function awsEndpointsFromFile(file, serviceDir) {
|
|
|
5212
6151
|
seen.add(key);
|
|
5213
6152
|
const line = lineOf(file.content, name);
|
|
5214
6153
|
out.push({
|
|
5215
|
-
infraId: (0,
|
|
6154
|
+
infraId: (0, import_types17.infraId)(kind, name),
|
|
5216
6155
|
name,
|
|
5217
6156
|
kind,
|
|
5218
6157
|
edgeType: "CALLS",
|
|
@@ -5221,7 +6160,7 @@ function awsEndpointsFromFile(file, serviceDir) {
|
|
|
5221
6160
|
// (ADR-066).
|
|
5222
6161
|
confidenceKind: "verified-call-site",
|
|
5223
6162
|
evidence: {
|
|
5224
|
-
file:
|
|
6163
|
+
file: import_node_path28.default.relative(serviceDir, file.path),
|
|
5225
6164
|
line,
|
|
5226
6165
|
snippet: snippet(file.content, line)
|
|
5227
6166
|
}
|
|
@@ -5246,8 +6185,8 @@ function awsEndpointsFromFile(file, serviceDir) {
|
|
|
5246
6185
|
|
|
5247
6186
|
// src/extract/calls/grpc.ts
|
|
5248
6187
|
init_cjs_shims();
|
|
5249
|
-
var
|
|
5250
|
-
var
|
|
6188
|
+
var import_node_path29 = __toESM(require("path"), 1);
|
|
6189
|
+
var import_types18 = require("@neat.is/types");
|
|
5251
6190
|
var GRPC_CLIENT_RE = /new\s+([A-Z][A-Za-z0-9_]*)Client\s*\(\s*['"`]?([^,'"`)]+)?/g;
|
|
5252
6191
|
var AWS_SDK_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@aws-sdk\/client-([a-z0-9-]+)['"`]/g;
|
|
5253
6192
|
var GRPC_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@grpc\/grpc-js['"`]|_grpc_pb['"`]/;
|
|
@@ -5296,7 +6235,7 @@ function grpcEndpointsFromFile(file, serviceDir) {
|
|
|
5296
6235
|
const { kind } = classified;
|
|
5297
6236
|
const line = lineOf(file.content, m[0]);
|
|
5298
6237
|
out.push({
|
|
5299
|
-
infraId: (0,
|
|
6238
|
+
infraId: (0, import_types18.infraId)(kind, name),
|
|
5300
6239
|
name,
|
|
5301
6240
|
kind,
|
|
5302
6241
|
edgeType: "CALLS",
|
|
@@ -5305,7 +6244,7 @@ function grpcEndpointsFromFile(file, serviceDir) {
|
|
|
5305
6244
|
// tier (ADR-066).
|
|
5306
6245
|
confidenceKind: "verified-call-site",
|
|
5307
6246
|
evidence: {
|
|
5308
|
-
file:
|
|
6247
|
+
file: import_node_path29.default.relative(serviceDir, file.path),
|
|
5309
6248
|
line,
|
|
5310
6249
|
snippet: snippet(file.content, line)
|
|
5311
6250
|
}
|
|
@@ -5316,8 +6255,8 @@ function grpcEndpointsFromFile(file, serviceDir) {
|
|
|
5316
6255
|
|
|
5317
6256
|
// src/extract/calls/supabase.ts
|
|
5318
6257
|
init_cjs_shims();
|
|
5319
|
-
var
|
|
5320
|
-
var
|
|
6258
|
+
var import_node_path30 = __toESM(require("path"), 1);
|
|
6259
|
+
var import_types19 = require("@neat.is/types");
|
|
5321
6260
|
var SUPABASE_JS_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@supabase\/supabase-js['"`]/;
|
|
5322
6261
|
var SUPABASE_SSR_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@supabase\/ssr['"`]/;
|
|
5323
6262
|
var SUPABASE_CLIENT_RE = /\b(createClient|createServerClient|createBrowserClient)\s*\(\s*(?:['"`]([^'"`]*)['"`])?/g;
|
|
@@ -5355,7 +6294,7 @@ function supabaseEndpointsFromFile(file, serviceDir) {
|
|
|
5355
6294
|
seen.add(name);
|
|
5356
6295
|
const line = lineOf(file.content, m[0]);
|
|
5357
6296
|
out.push({
|
|
5358
|
-
infraId: (0,
|
|
6297
|
+
infraId: (0, import_types19.infraId)("supabase", name),
|
|
5359
6298
|
name,
|
|
5360
6299
|
kind: "supabase",
|
|
5361
6300
|
edgeType: "CALLS",
|
|
@@ -5365,7 +6304,7 @@ function supabaseEndpointsFromFile(file, serviceDir) {
|
|
|
5365
6304
|
// tier (ADR-066), the same grade aws.ts / grpc.ts emit at.
|
|
5366
6305
|
confidenceKind: "verified-call-site",
|
|
5367
6306
|
evidence: {
|
|
5368
|
-
file:
|
|
6307
|
+
file: import_node_path30.default.relative(serviceDir, file.path),
|
|
5369
6308
|
line,
|
|
5370
6309
|
snippet: snippet(file.content, line)
|
|
5371
6310
|
}
|
|
@@ -5378,11 +6317,11 @@ function supabaseEndpointsFromFile(file, serviceDir) {
|
|
|
5378
6317
|
function edgeTypeFromEndpoint(ep) {
|
|
5379
6318
|
switch (ep.edgeType) {
|
|
5380
6319
|
case "PUBLISHES_TO":
|
|
5381
|
-
return
|
|
6320
|
+
return import_types20.EdgeType.PUBLISHES_TO;
|
|
5382
6321
|
case "CONSUMES_FROM":
|
|
5383
|
-
return
|
|
6322
|
+
return import_types20.EdgeType.CONSUMES_FROM;
|
|
5384
6323
|
default:
|
|
5385
|
-
return
|
|
6324
|
+
return import_types20.EdgeType.CALLS;
|
|
5386
6325
|
}
|
|
5387
6326
|
}
|
|
5388
6327
|
function isAwsKind(kind) {
|
|
@@ -5410,7 +6349,7 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
5410
6349
|
if (!graph.hasNode(ep.infraId)) {
|
|
5411
6350
|
const node = {
|
|
5412
6351
|
id: ep.infraId,
|
|
5413
|
-
type:
|
|
6352
|
+
type: import_types20.NodeType.InfraNode,
|
|
5414
6353
|
name: ep.name,
|
|
5415
6354
|
// #238 — `aws-*` covers AWS-SDK client kinds (aws-s3, aws-dynamodb,
|
|
5416
6355
|
// aws-cognito-identity-provider, …); `s3-` / `dynamodb-` cover the
|
|
@@ -5422,7 +6361,7 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
5422
6361
|
nodesAdded++;
|
|
5423
6362
|
}
|
|
5424
6363
|
const edgeType = edgeTypeFromEndpoint(ep);
|
|
5425
|
-
const confidence = (0,
|
|
6364
|
+
const confidence = (0, import_types20.confidenceForExtracted)(ep.confidenceKind);
|
|
5426
6365
|
const relFile = toPosix2(ep.evidence.file);
|
|
5427
6366
|
const { fileNodeId, nodesAdded: n, edgesAdded: e } = ensureFileNode(
|
|
5428
6367
|
graph,
|
|
@@ -5432,7 +6371,7 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
5432
6371
|
);
|
|
5433
6372
|
nodesAdded += n;
|
|
5434
6373
|
edgesAdded += e;
|
|
5435
|
-
if (!(0,
|
|
6374
|
+
if (!(0, import_types20.passesExtractedFloor)(confidence)) {
|
|
5436
6375
|
noteExtractedDropped({
|
|
5437
6376
|
source: fileNodeId,
|
|
5438
6377
|
target: ep.infraId,
|
|
@@ -5452,7 +6391,7 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
5452
6391
|
source: fileNodeId,
|
|
5453
6392
|
target: ep.infraId,
|
|
5454
6393
|
type: edgeType,
|
|
5455
|
-
provenance:
|
|
6394
|
+
provenance: import_types20.Provenance.EXTRACTED,
|
|
5456
6395
|
confidence,
|
|
5457
6396
|
evidence: ep.evidence
|
|
5458
6397
|
};
|
|
@@ -5466,9 +6405,10 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
5466
6405
|
async function addCallEdges(graph, services) {
|
|
5467
6406
|
const http = await addHttpCallEdges(graph, services);
|
|
5468
6407
|
const ext = await addExternalEndpointEdges(graph, services);
|
|
6408
|
+
const routes = await addRouteCallEdges(graph, services);
|
|
5469
6409
|
return {
|
|
5470
|
-
nodesAdded: http.nodesAdded + ext.nodesAdded,
|
|
5471
|
-
edgesAdded: http.edgesAdded + ext.edgesAdded
|
|
6410
|
+
nodesAdded: http.nodesAdded + ext.nodesAdded + routes.nodesAdded,
|
|
6411
|
+
edgesAdded: http.edgesAdded + ext.edgesAdded + routes.edgesAdded
|
|
5472
6412
|
};
|
|
5473
6413
|
}
|
|
5474
6414
|
|
|
@@ -5477,16 +6417,16 @@ init_cjs_shims();
|
|
|
5477
6417
|
|
|
5478
6418
|
// src/extract/infra/docker-compose.ts
|
|
5479
6419
|
init_cjs_shims();
|
|
5480
|
-
var
|
|
5481
|
-
var
|
|
6420
|
+
var import_node_path31 = __toESM(require("path"), 1);
|
|
6421
|
+
var import_types22 = require("@neat.is/types");
|
|
5482
6422
|
|
|
5483
6423
|
// src/extract/infra/shared.ts
|
|
5484
6424
|
init_cjs_shims();
|
|
5485
|
-
var
|
|
6425
|
+
var import_types21 = require("@neat.is/types");
|
|
5486
6426
|
function makeInfraNode(kind, name, provider = "self", extras) {
|
|
5487
6427
|
return {
|
|
5488
|
-
id: (0,
|
|
5489
|
-
type:
|
|
6428
|
+
id: (0, import_types21.infraId)(kind, name),
|
|
6429
|
+
type: import_types21.NodeType.InfraNode,
|
|
5490
6430
|
name,
|
|
5491
6431
|
provider,
|
|
5492
6432
|
kind,
|
|
@@ -5515,7 +6455,7 @@ function dependsOnList(value) {
|
|
|
5515
6455
|
}
|
|
5516
6456
|
function serviceNameToServiceNode(name, services) {
|
|
5517
6457
|
for (const s of services) {
|
|
5518
|
-
if (s.node.name === name ||
|
|
6458
|
+
if (s.node.name === name || import_node_path31.default.basename(s.dir) === name) return s.node.id;
|
|
5519
6459
|
}
|
|
5520
6460
|
return null;
|
|
5521
6461
|
}
|
|
@@ -5524,7 +6464,7 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
5524
6464
|
let edgesAdded = 0;
|
|
5525
6465
|
let composePath = null;
|
|
5526
6466
|
for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
|
|
5527
|
-
const abs =
|
|
6467
|
+
const abs = import_node_path31.default.join(scanPath, name);
|
|
5528
6468
|
if (await exists(abs)) {
|
|
5529
6469
|
composePath = abs;
|
|
5530
6470
|
break;
|
|
@@ -5537,13 +6477,13 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
5537
6477
|
} catch (err) {
|
|
5538
6478
|
recordExtractionError(
|
|
5539
6479
|
"infra docker-compose",
|
|
5540
|
-
|
|
6480
|
+
import_node_path31.default.relative(scanPath, composePath),
|
|
5541
6481
|
err
|
|
5542
6482
|
);
|
|
5543
6483
|
return { nodesAdded, edgesAdded };
|
|
5544
6484
|
}
|
|
5545
6485
|
if (!compose?.services) return { nodesAdded, edgesAdded };
|
|
5546
|
-
const evidenceFile =
|
|
6486
|
+
const evidenceFile = import_node_path31.default.relative(scanPath, composePath).split(import_node_path31.default.sep).join("/");
|
|
5547
6487
|
const composeNameToNodeId = /* @__PURE__ */ new Map();
|
|
5548
6488
|
for (const [composeName, svc] of Object.entries(compose.services)) {
|
|
5549
6489
|
const matchedServiceId = serviceNameToServiceNode(composeName, services);
|
|
@@ -5565,15 +6505,15 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
5565
6505
|
for (const dep of dependsOnList(svc.depends_on)) {
|
|
5566
6506
|
const targetId = composeNameToNodeId.get(dep);
|
|
5567
6507
|
if (!targetId) continue;
|
|
5568
|
-
const edgeId = (0, import_types4.extractedEdgeId)(sourceId, targetId,
|
|
6508
|
+
const edgeId = (0, import_types4.extractedEdgeId)(sourceId, targetId, import_types22.EdgeType.DEPENDS_ON);
|
|
5569
6509
|
if (graph.hasEdge(edgeId)) continue;
|
|
5570
6510
|
const edge = {
|
|
5571
6511
|
id: edgeId,
|
|
5572
6512
|
source: sourceId,
|
|
5573
6513
|
target: targetId,
|
|
5574
|
-
type:
|
|
5575
|
-
provenance:
|
|
5576
|
-
confidence: (0,
|
|
6514
|
+
type: import_types22.EdgeType.DEPENDS_ON,
|
|
6515
|
+
provenance: import_types22.Provenance.EXTRACTED,
|
|
6516
|
+
confidence: (0, import_types22.confidenceForExtracted)("structural"),
|
|
5577
6517
|
evidence: { file: evidenceFile }
|
|
5578
6518
|
};
|
|
5579
6519
|
graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
|
|
@@ -5585,9 +6525,9 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
5585
6525
|
|
|
5586
6526
|
// src/extract/infra/dockerfile.ts
|
|
5587
6527
|
init_cjs_shims();
|
|
5588
|
-
var
|
|
5589
|
-
var
|
|
5590
|
-
var
|
|
6528
|
+
var import_node_path32 = __toESM(require("path"), 1);
|
|
6529
|
+
var import_node_fs16 = require("fs");
|
|
6530
|
+
var import_types23 = require("@neat.is/types");
|
|
5591
6531
|
function readDockerfile(content) {
|
|
5592
6532
|
let image = null;
|
|
5593
6533
|
const ports = [];
|
|
@@ -5616,15 +6556,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
5616
6556
|
let nodesAdded = 0;
|
|
5617
6557
|
let edgesAdded = 0;
|
|
5618
6558
|
for (const service of services) {
|
|
5619
|
-
const dockerfilePath =
|
|
6559
|
+
const dockerfilePath = import_node_path32.default.join(service.dir, "Dockerfile");
|
|
5620
6560
|
if (!await exists(dockerfilePath)) continue;
|
|
5621
6561
|
let content;
|
|
5622
6562
|
try {
|
|
5623
|
-
content = await
|
|
6563
|
+
content = await import_node_fs16.promises.readFile(dockerfilePath, "utf8");
|
|
5624
6564
|
} catch (err) {
|
|
5625
6565
|
recordExtractionError(
|
|
5626
6566
|
"infra dockerfile",
|
|
5627
|
-
|
|
6567
|
+
import_node_path32.default.relative(scanPath, dockerfilePath),
|
|
5628
6568
|
err
|
|
5629
6569
|
);
|
|
5630
6570
|
continue;
|
|
@@ -5636,8 +6576,8 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
5636
6576
|
graph.addNode(node.id, node);
|
|
5637
6577
|
nodesAdded++;
|
|
5638
6578
|
}
|
|
5639
|
-
const relDockerfile = toPosix2(
|
|
5640
|
-
const evidenceFile = toPosix2(
|
|
6579
|
+
const relDockerfile = toPosix2(import_node_path32.default.relative(service.dir, dockerfilePath));
|
|
6580
|
+
const evidenceFile = toPosix2(import_node_path32.default.relative(scanPath, dockerfilePath));
|
|
5641
6581
|
const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
|
|
5642
6582
|
graph,
|
|
5643
6583
|
service.pkg.name,
|
|
@@ -5646,15 +6586,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
5646
6586
|
);
|
|
5647
6587
|
nodesAdded += fn;
|
|
5648
6588
|
edgesAdded += fe;
|
|
5649
|
-
const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, node.id,
|
|
6589
|
+
const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, node.id, import_types23.EdgeType.RUNS_ON);
|
|
5650
6590
|
if (!graph.hasEdge(edgeId)) {
|
|
5651
6591
|
const edge = {
|
|
5652
6592
|
id: edgeId,
|
|
5653
6593
|
source: fileNodeId,
|
|
5654
6594
|
target: node.id,
|
|
5655
|
-
type:
|
|
5656
|
-
provenance:
|
|
5657
|
-
confidence: (0,
|
|
6595
|
+
type: import_types23.EdgeType.RUNS_ON,
|
|
6596
|
+
provenance: import_types23.Provenance.EXTRACTED,
|
|
6597
|
+
confidence: (0, import_types23.confidenceForExtracted)("structural"),
|
|
5658
6598
|
evidence: {
|
|
5659
6599
|
file: evidenceFile,
|
|
5660
6600
|
...facts.entrypoint ? { snippet: facts.entrypoint.slice(0, 120) } : {}
|
|
@@ -5669,15 +6609,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
5669
6609
|
graph.addNode(portNode.id, portNode);
|
|
5670
6610
|
nodesAdded++;
|
|
5671
6611
|
}
|
|
5672
|
-
const portEdgeId = (0, import_types4.extractedEdgeId)(fileNodeId, portNode.id,
|
|
6612
|
+
const portEdgeId = (0, import_types4.extractedEdgeId)(fileNodeId, portNode.id, import_types23.EdgeType.CONNECTS_TO);
|
|
5673
6613
|
if (graph.hasEdge(portEdgeId)) continue;
|
|
5674
6614
|
const portEdge = {
|
|
5675
6615
|
id: portEdgeId,
|
|
5676
6616
|
source: fileNodeId,
|
|
5677
6617
|
target: portNode.id,
|
|
5678
|
-
type:
|
|
5679
|
-
provenance:
|
|
5680
|
-
confidence: (0,
|
|
6618
|
+
type: import_types23.EdgeType.CONNECTS_TO,
|
|
6619
|
+
provenance: import_types23.Provenance.EXTRACTED,
|
|
6620
|
+
confidence: (0, import_types23.confidenceForExtracted)("structural"),
|
|
5681
6621
|
evidence: { file: evidenceFile, snippet: `EXPOSE ${port}` }
|
|
5682
6622
|
};
|
|
5683
6623
|
graph.addEdgeWithKey(portEdgeId, portEdge.source, portEdge.target, portEdge);
|
|
@@ -5689,23 +6629,23 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
5689
6629
|
|
|
5690
6630
|
// src/extract/infra/terraform.ts
|
|
5691
6631
|
init_cjs_shims();
|
|
5692
|
-
var
|
|
5693
|
-
var
|
|
5694
|
-
var
|
|
6632
|
+
var import_node_fs17 = require("fs");
|
|
6633
|
+
var import_node_path33 = __toESM(require("path"), 1);
|
|
6634
|
+
var import_types24 = require("@neat.is/types");
|
|
5695
6635
|
var RESOURCE_RE = /resource\s+"(aws_[A-Za-z0-9_]+)"\s+"([A-Za-z0-9_-]+)"/g;
|
|
5696
6636
|
var REFERENCE_RE = /(?<![\w.])(aws_[A-Za-z0-9_]+)\.([A-Za-z0-9_-]+)/g;
|
|
5697
6637
|
async function walkTfFiles(start, depth = 0, max = 5) {
|
|
5698
6638
|
if (depth > max) return [];
|
|
5699
6639
|
const out = [];
|
|
5700
|
-
const entries = await
|
|
6640
|
+
const entries = await import_node_fs17.promises.readdir(start, { withFileTypes: true }).catch(() => []);
|
|
5701
6641
|
for (const entry of entries) {
|
|
5702
6642
|
if (entry.isDirectory()) {
|
|
5703
6643
|
if (IGNORED_DIRS.has(entry.name) || entry.name === ".terraform") continue;
|
|
5704
|
-
const child =
|
|
6644
|
+
const child = import_node_path33.default.join(start, entry.name);
|
|
5705
6645
|
if (await isPythonVenvDir(child)) continue;
|
|
5706
6646
|
out.push(...await walkTfFiles(child, depth + 1, max));
|
|
5707
6647
|
} else if (entry.isFile() && entry.name.endsWith(".tf")) {
|
|
5708
|
-
out.push(
|
|
6648
|
+
out.push(import_node_path33.default.join(start, entry.name));
|
|
5709
6649
|
}
|
|
5710
6650
|
}
|
|
5711
6651
|
return out;
|
|
@@ -5724,7 +6664,7 @@ function blockBody(content, from) {
|
|
|
5724
6664
|
}
|
|
5725
6665
|
return null;
|
|
5726
6666
|
}
|
|
5727
|
-
function
|
|
6667
|
+
function lineAt2(content, index) {
|
|
5728
6668
|
let line = 1;
|
|
5729
6669
|
for (let i = 0; i < index && i < content.length; i++) {
|
|
5730
6670
|
if (content[i] === "\n") line++;
|
|
@@ -5736,8 +6676,8 @@ async function addTerraformResources(graph, scanPath) {
|
|
|
5736
6676
|
let edgesAdded = 0;
|
|
5737
6677
|
const files = await walkTfFiles(scanPath);
|
|
5738
6678
|
for (const file of files) {
|
|
5739
|
-
const content = await
|
|
5740
|
-
const evidenceFile = toPosix2(
|
|
6679
|
+
const content = await import_node_fs17.promises.readFile(file, "utf8");
|
|
6680
|
+
const evidenceFile = toPosix2(import_node_path33.default.relative(scanPath, file));
|
|
5741
6681
|
const resources = [];
|
|
5742
6682
|
const byKey = /* @__PURE__ */ new Map();
|
|
5743
6683
|
RESOURCE_RE.lastIndex = 0;
|
|
@@ -5772,16 +6712,16 @@ async function addTerraformResources(graph, scanPath) {
|
|
|
5772
6712
|
if (!target) continue;
|
|
5773
6713
|
if (seen.has(target.nodeId)) continue;
|
|
5774
6714
|
seen.add(target.nodeId);
|
|
5775
|
-
const edgeId = (0, import_types4.extractedEdgeId)(resource.nodeId, target.nodeId,
|
|
6715
|
+
const edgeId = (0, import_types4.extractedEdgeId)(resource.nodeId, target.nodeId, import_types24.EdgeType.DEPENDS_ON);
|
|
5776
6716
|
if (graph.hasEdge(edgeId)) continue;
|
|
5777
|
-
const line =
|
|
6717
|
+
const line = lineAt2(content, resource.bodyOffset + ref.index);
|
|
5778
6718
|
const edge = {
|
|
5779
6719
|
id: edgeId,
|
|
5780
6720
|
source: resource.nodeId,
|
|
5781
6721
|
target: target.nodeId,
|
|
5782
|
-
type:
|
|
5783
|
-
provenance:
|
|
5784
|
-
confidence: (0,
|
|
6722
|
+
type: import_types24.EdgeType.DEPENDS_ON,
|
|
6723
|
+
provenance: import_types24.Provenance.EXTRACTED,
|
|
6724
|
+
confidence: (0, import_types24.confidenceForExtracted)("structural"),
|
|
5785
6725
|
evidence: { file: evidenceFile, line, snippet: key }
|
|
5786
6726
|
};
|
|
5787
6727
|
graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
|
|
@@ -5794,8 +6734,8 @@ async function addTerraformResources(graph, scanPath) {
|
|
|
5794
6734
|
|
|
5795
6735
|
// src/extract/infra/k8s.ts
|
|
5796
6736
|
init_cjs_shims();
|
|
5797
|
-
var
|
|
5798
|
-
var
|
|
6737
|
+
var import_node_fs18 = require("fs");
|
|
6738
|
+
var import_node_path34 = __toESM(require("path"), 1);
|
|
5799
6739
|
var import_yaml3 = require("yaml");
|
|
5800
6740
|
var K8S_KIND_TO_INFRA_KIND = {
|
|
5801
6741
|
Service: "k8s-service",
|
|
@@ -5809,15 +6749,15 @@ var K8S_KIND_TO_INFRA_KIND = {
|
|
|
5809
6749
|
async function walkYamlFiles2(start, depth = 0, max = 5) {
|
|
5810
6750
|
if (depth > max) return [];
|
|
5811
6751
|
const out = [];
|
|
5812
|
-
const entries = await
|
|
6752
|
+
const entries = await import_node_fs18.promises.readdir(start, { withFileTypes: true }).catch(() => []);
|
|
5813
6753
|
for (const entry of entries) {
|
|
5814
6754
|
if (entry.isDirectory()) {
|
|
5815
6755
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
5816
|
-
const child =
|
|
6756
|
+
const child = import_node_path34.default.join(start, entry.name);
|
|
5817
6757
|
if (await isPythonVenvDir(child)) continue;
|
|
5818
6758
|
out.push(...await walkYamlFiles2(child, depth + 1, max));
|
|
5819
|
-
} else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(
|
|
5820
|
-
out.push(
|
|
6759
|
+
} else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path34.default.extname(entry.name))) {
|
|
6760
|
+
out.push(import_node_path34.default.join(start, entry.name));
|
|
5821
6761
|
}
|
|
5822
6762
|
}
|
|
5823
6763
|
return out;
|
|
@@ -5826,7 +6766,7 @@ async function addK8sResources(graph, scanPath) {
|
|
|
5826
6766
|
let nodesAdded = 0;
|
|
5827
6767
|
const files = await walkYamlFiles2(scanPath);
|
|
5828
6768
|
for (const file of files) {
|
|
5829
|
-
const content = await
|
|
6769
|
+
const content = await import_node_fs18.promises.readFile(file, "utf8");
|
|
5830
6770
|
let docs;
|
|
5831
6771
|
try {
|
|
5832
6772
|
docs = (0, import_yaml3.parseAllDocuments)(content).map((d) => d.toJSON());
|
|
@@ -5861,17 +6801,17 @@ async function addInfra(graph, scanPath, services) {
|
|
|
5861
6801
|
}
|
|
5862
6802
|
|
|
5863
6803
|
// src/extract/index.ts
|
|
5864
|
-
var
|
|
6804
|
+
var import_node_path36 = __toESM(require("path"), 1);
|
|
5865
6805
|
|
|
5866
6806
|
// src/extract/retire.ts
|
|
5867
6807
|
init_cjs_shims();
|
|
5868
|
-
var
|
|
5869
|
-
var
|
|
5870
|
-
var
|
|
6808
|
+
var import_node_fs19 = require("fs");
|
|
6809
|
+
var import_node_path35 = __toESM(require("path"), 1);
|
|
6810
|
+
var import_types25 = require("@neat.is/types");
|
|
5871
6811
|
function dropOrphanedFileNodes(graph) {
|
|
5872
6812
|
const orphans = [];
|
|
5873
6813
|
graph.forEachNode((id, attrs) => {
|
|
5874
|
-
if (attrs.type !==
|
|
6814
|
+
if (attrs.type !== import_types25.NodeType.FileNode) return;
|
|
5875
6815
|
if (graph.inboundEdges(id).length === 0 && graph.outboundEdges(id).length === 0) {
|
|
5876
6816
|
orphans.push(id);
|
|
5877
6817
|
}
|
|
@@ -5884,14 +6824,14 @@ function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = []) {
|
|
|
5884
6824
|
const bases = [scanPath, ...serviceDirs];
|
|
5885
6825
|
graph.forEachEdge((id, attrs) => {
|
|
5886
6826
|
const edge = attrs;
|
|
5887
|
-
if (edge.provenance !==
|
|
6827
|
+
if (edge.provenance !== import_types25.Provenance.EXTRACTED) return;
|
|
5888
6828
|
const evidenceFile = edge.evidence?.file;
|
|
5889
6829
|
if (!evidenceFile) return;
|
|
5890
|
-
if (
|
|
5891
|
-
if (!(0,
|
|
6830
|
+
if (import_node_path35.default.isAbsolute(evidenceFile)) {
|
|
6831
|
+
if (!(0, import_node_fs19.existsSync)(evidenceFile)) toDrop.push(id);
|
|
5892
6832
|
return;
|
|
5893
6833
|
}
|
|
5894
|
-
const found = bases.some((base) => (0,
|
|
6834
|
+
const found = bases.some((base) => (0, import_node_fs19.existsSync)(import_node_path35.default.join(base, evidenceFile)));
|
|
5895
6835
|
if (!found) toDrop.push(id);
|
|
5896
6836
|
});
|
|
5897
6837
|
for (const id of toDrop) graph.dropEdge(id);
|
|
@@ -5910,6 +6850,8 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
5910
6850
|
const importGraph = await addImports(graph, services);
|
|
5911
6851
|
const phase2 = await addDatabasesAndCompat(graph, services, scanPath);
|
|
5912
6852
|
const phase3 = await addConfigNodes(graph, services, scanPath);
|
|
6853
|
+
const routePhase = await addRoutes(graph, services);
|
|
6854
|
+
const grpcPhase = await addGrpcMethods(graph, services);
|
|
5913
6855
|
const phase4 = await addCallEdges(graph, services);
|
|
5914
6856
|
const phase5 = await addInfra(graph, scanPath, services);
|
|
5915
6857
|
const ghostsRetired = retireExtractedEdgesByMissingFile(
|
|
@@ -5931,7 +6873,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
5931
6873
|
}
|
|
5932
6874
|
const droppedEntries = drainDroppedExtracted();
|
|
5933
6875
|
if (isRejectedLogEnabled() && opts.errorsPath && droppedEntries.length > 0) {
|
|
5934
|
-
const rejectedPath =
|
|
6876
|
+
const rejectedPath = import_node_path36.default.join(import_node_path36.default.dirname(opts.errorsPath), "rejected.ndjson");
|
|
5935
6877
|
try {
|
|
5936
6878
|
await writeRejectedExtracted(droppedEntries, rejectedPath);
|
|
5937
6879
|
} catch (err) {
|
|
@@ -5941,8 +6883,8 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
5941
6883
|
}
|
|
5942
6884
|
}
|
|
5943
6885
|
const result = {
|
|
5944
|
-
nodesAdded: phase1Nodes + fileEnum.nodesAdded + importGraph.nodesAdded + phase2.nodesAdded + phase3.nodesAdded + phase4.nodesAdded + phase5.nodesAdded,
|
|
5945
|
-
edgesAdded: fileEnum.edgesAdded + importGraph.edgesAdded + phase2.edgesAdded + phase3.edgesAdded + phase4.edgesAdded + phase5.edgesAdded,
|
|
6886
|
+
nodesAdded: phase1Nodes + fileEnum.nodesAdded + importGraph.nodesAdded + phase2.nodesAdded + phase3.nodesAdded + routePhase.nodesAdded + grpcPhase.nodesAdded + phase4.nodesAdded + phase5.nodesAdded,
|
|
6887
|
+
edgesAdded: fileEnum.edgesAdded + importGraph.edgesAdded + phase2.edgesAdded + phase3.edgesAdded + routePhase.edgesAdded + grpcPhase.edgesAdded + phase4.edgesAdded + phase5.edgesAdded,
|
|
5946
6888
|
frontiersPromoted,
|
|
5947
6889
|
extractionErrors: errorEntries.length,
|
|
5948
6890
|
errorEntries,
|
|
@@ -5965,9 +6907,9 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
5965
6907
|
|
|
5966
6908
|
// src/persist.ts
|
|
5967
6909
|
init_cjs_shims();
|
|
5968
|
-
var
|
|
5969
|
-
var
|
|
5970
|
-
var
|
|
6910
|
+
var import_node_fs20 = require("fs");
|
|
6911
|
+
var import_node_path37 = __toESM(require("path"), 1);
|
|
6912
|
+
var import_types26 = require("@neat.is/types");
|
|
5971
6913
|
var SCHEMA_VERSION = 4;
|
|
5972
6914
|
function migrateV1ToV2(payload) {
|
|
5973
6915
|
const nodes = payload.graph.nodes;
|
|
@@ -5989,12 +6931,12 @@ function migrateV2ToV3(payload) {
|
|
|
5989
6931
|
for (const edge of edges) {
|
|
5990
6932
|
const attrs = edge.attributes;
|
|
5991
6933
|
if (!attrs || attrs.provenance !== "FRONTIER") continue;
|
|
5992
|
-
attrs.provenance =
|
|
6934
|
+
attrs.provenance = import_types26.Provenance.OBSERVED;
|
|
5993
6935
|
const type = typeof attrs.type === "string" ? attrs.type : void 0;
|
|
5994
6936
|
const source = typeof attrs.source === "string" ? attrs.source : void 0;
|
|
5995
6937
|
const target = typeof attrs.target === "string" ? attrs.target : void 0;
|
|
5996
6938
|
if (type && source && target) {
|
|
5997
|
-
const newId = (0,
|
|
6939
|
+
const newId = (0, import_types26.observedEdgeId)(source, target, type);
|
|
5998
6940
|
attrs.id = newId;
|
|
5999
6941
|
if (edge.key) edge.key = newId;
|
|
6000
6942
|
}
|
|
@@ -6003,7 +6945,7 @@ function migrateV2ToV3(payload) {
|
|
|
6003
6945
|
return { ...payload, schemaVersion: 3 };
|
|
6004
6946
|
}
|
|
6005
6947
|
async function ensureDir(filePath) {
|
|
6006
|
-
await
|
|
6948
|
+
await import_node_fs20.promises.mkdir(import_node_path37.default.dirname(filePath), { recursive: true });
|
|
6007
6949
|
}
|
|
6008
6950
|
async function saveGraphToDisk(graph, outPath) {
|
|
6009
6951
|
await ensureDir(outPath);
|
|
@@ -6013,13 +6955,13 @@ async function saveGraphToDisk(graph, outPath) {
|
|
|
6013
6955
|
graph: graph.export()
|
|
6014
6956
|
};
|
|
6015
6957
|
const tmp = `${outPath}.tmp`;
|
|
6016
|
-
await
|
|
6017
|
-
await
|
|
6958
|
+
await import_node_fs20.promises.writeFile(tmp, JSON.stringify(payload), "utf8");
|
|
6959
|
+
await import_node_fs20.promises.rename(tmp, outPath);
|
|
6018
6960
|
}
|
|
6019
6961
|
async function loadGraphFromDisk(graph, outPath) {
|
|
6020
6962
|
let raw;
|
|
6021
6963
|
try {
|
|
6022
|
-
raw = await
|
|
6964
|
+
raw = await import_node_fs20.promises.readFile(outPath, "utf8");
|
|
6023
6965
|
} catch (err) {
|
|
6024
6966
|
if (err.code === "ENOENT") return;
|
|
6025
6967
|
throw err;
|
|
@@ -6086,19 +7028,19 @@ function startPersistLoop(graph, outPath, opts = {}) {
|
|
|
6086
7028
|
init_cjs_shims();
|
|
6087
7029
|
var import_fastify = __toESM(require("fastify"), 1);
|
|
6088
7030
|
var import_cors = __toESM(require("@fastify/cors"), 1);
|
|
6089
|
-
var
|
|
7031
|
+
var import_types29 = require("@neat.is/types");
|
|
6090
7032
|
|
|
6091
7033
|
// src/extend/index.ts
|
|
6092
7034
|
init_cjs_shims();
|
|
6093
|
-
var
|
|
6094
|
-
var
|
|
7035
|
+
var import_node_fs22 = require("fs");
|
|
7036
|
+
var import_node_path39 = __toESM(require("path"), 1);
|
|
6095
7037
|
var import_node_os2 = __toESM(require("os"), 1);
|
|
6096
7038
|
var import_instrumentation_registry = require("@neat.is/instrumentation-registry");
|
|
6097
7039
|
|
|
6098
7040
|
// src/installers/package-manager.ts
|
|
6099
7041
|
init_cjs_shims();
|
|
6100
|
-
var
|
|
6101
|
-
var
|
|
7042
|
+
var import_node_fs21 = require("fs");
|
|
7043
|
+
var import_node_path38 = __toESM(require("path"), 1);
|
|
6102
7044
|
var import_node_child_process = require("child_process");
|
|
6103
7045
|
var LOCKFILE_PRIORITY = [
|
|
6104
7046
|
{ lockfile: "bun.lockb", pm: "bun", args: ["install", "--no-summary"] },
|
|
@@ -6113,29 +7055,29 @@ var LOCKFILE_PRIORITY = [
|
|
|
6113
7055
|
var NPM_FALLBACK_ARGS = ["install", "--no-audit", "--no-fund", "--prefer-offline"];
|
|
6114
7056
|
async function exists2(p) {
|
|
6115
7057
|
try {
|
|
6116
|
-
await
|
|
7058
|
+
await import_node_fs21.promises.access(p);
|
|
6117
7059
|
return true;
|
|
6118
7060
|
} catch {
|
|
6119
7061
|
return false;
|
|
6120
7062
|
}
|
|
6121
7063
|
}
|
|
6122
7064
|
async function detectPackageManager(serviceDir) {
|
|
6123
|
-
let dir =
|
|
7065
|
+
let dir = import_node_path38.default.resolve(serviceDir);
|
|
6124
7066
|
const stops = /* @__PURE__ */ new Set();
|
|
6125
7067
|
for (let i = 0; i < 64; i++) {
|
|
6126
7068
|
if (stops.has(dir)) break;
|
|
6127
7069
|
stops.add(dir);
|
|
6128
7070
|
for (const candidate of LOCKFILE_PRIORITY) {
|
|
6129
|
-
const lockPath =
|
|
7071
|
+
const lockPath = import_node_path38.default.join(dir, candidate.lockfile);
|
|
6130
7072
|
if (await exists2(lockPath)) {
|
|
6131
7073
|
return { pm: candidate.pm, cwd: dir, args: [...candidate.args] };
|
|
6132
7074
|
}
|
|
6133
7075
|
}
|
|
6134
|
-
const parent =
|
|
7076
|
+
const parent = import_node_path38.default.dirname(dir);
|
|
6135
7077
|
if (parent === dir) break;
|
|
6136
7078
|
dir = parent;
|
|
6137
7079
|
}
|
|
6138
|
-
return { pm: "npm", cwd:
|
|
7080
|
+
return { pm: "npm", cwd: import_node_path38.default.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
|
|
6139
7081
|
}
|
|
6140
7082
|
async function runPackageManagerInstall(cmd) {
|
|
6141
7083
|
return new Promise((resolve) => {
|
|
@@ -6177,30 +7119,30 @@ ${err.message}`
|
|
|
6177
7119
|
// src/extend/index.ts
|
|
6178
7120
|
async function fileExists2(p) {
|
|
6179
7121
|
try {
|
|
6180
|
-
await
|
|
7122
|
+
await import_node_fs22.promises.access(p);
|
|
6181
7123
|
return true;
|
|
6182
7124
|
} catch {
|
|
6183
7125
|
return false;
|
|
6184
7126
|
}
|
|
6185
7127
|
}
|
|
6186
7128
|
async function readPackageJson(scanPath) {
|
|
6187
|
-
const pkgPath =
|
|
6188
|
-
const raw = await
|
|
7129
|
+
const pkgPath = import_node_path39.default.join(scanPath, "package.json");
|
|
7130
|
+
const raw = await import_node_fs22.promises.readFile(pkgPath, "utf8");
|
|
6189
7131
|
return JSON.parse(raw);
|
|
6190
7132
|
}
|
|
6191
7133
|
async function findHookFiles(scanPath) {
|
|
6192
|
-
const entries = await
|
|
7134
|
+
const entries = await import_node_fs22.promises.readdir(scanPath);
|
|
6193
7135
|
return entries.filter(
|
|
6194
7136
|
(e) => (e.startsWith("instrumentation") || e.startsWith("otel-init")) && /\.(ts|js)$/.test(e)
|
|
6195
7137
|
).sort();
|
|
6196
7138
|
}
|
|
6197
7139
|
function extendLogPath() {
|
|
6198
|
-
return process.env.NEAT_EXTEND_LOG ??
|
|
7140
|
+
return process.env.NEAT_EXTEND_LOG ?? import_node_path39.default.join(import_node_os2.default.homedir(), ".neat", "extend-log.ndjson");
|
|
6199
7141
|
}
|
|
6200
7142
|
async function appendExtendLog(entry) {
|
|
6201
7143
|
const logPath = extendLogPath();
|
|
6202
|
-
await
|
|
6203
|
-
await
|
|
7144
|
+
await import_node_fs22.promises.mkdir(import_node_path39.default.dirname(logPath), { recursive: true });
|
|
7145
|
+
await import_node_fs22.promises.appendFile(logPath, JSON.stringify(entry) + "\n", "utf8");
|
|
6204
7146
|
}
|
|
6205
7147
|
function splicedContent(fileContent, snippet2) {
|
|
6206
7148
|
if (fileContent.includes("__INSTRUMENTATION_BLOCK__")) {
|
|
@@ -6258,7 +7200,7 @@ function lookupInstrumentation(library, installedVersion) {
|
|
|
6258
7200
|
}
|
|
6259
7201
|
async function describeProjectInstrumentation(ctx) {
|
|
6260
7202
|
const hookFiles = await findHookFiles(ctx.scanPath);
|
|
6261
|
-
const envNeat = await fileExists2(
|
|
7203
|
+
const envNeat = await fileExists2(import_node_path39.default.join(ctx.scanPath, ".env.neat"));
|
|
6262
7204
|
const registryInstrPackages = new Set(
|
|
6263
7205
|
(0, import_instrumentation_registry.list)().map((e) => e.instrumentation_package).filter((p) => !!p)
|
|
6264
7206
|
);
|
|
@@ -6280,31 +7222,31 @@ async function applyExtension(ctx, args, options) {
|
|
|
6280
7222
|
);
|
|
6281
7223
|
}
|
|
6282
7224
|
for (const file of hookFiles) {
|
|
6283
|
-
const content = await
|
|
7225
|
+
const content = await import_node_fs22.promises.readFile(import_node_path39.default.join(ctx.scanPath, file), "utf8");
|
|
6284
7226
|
if (content.includes(args.registration_snippet)) {
|
|
6285
7227
|
return { library: args.library, filesTouched: [], depsAdded: [], installOutput: "", alreadyApplied: true };
|
|
6286
7228
|
}
|
|
6287
7229
|
}
|
|
6288
7230
|
const primaryFile = hookFiles[0];
|
|
6289
|
-
const primaryPath =
|
|
7231
|
+
const primaryPath = import_node_path39.default.join(ctx.scanPath, primaryFile);
|
|
6290
7232
|
const filesTouched = [];
|
|
6291
7233
|
const depsAdded = [];
|
|
6292
|
-
const pkgPath =
|
|
7234
|
+
const pkgPath = import_node_path39.default.join(ctx.scanPath, "package.json");
|
|
6293
7235
|
const pkg = await readPackageJson(ctx.scanPath);
|
|
6294
7236
|
if (!(pkg.dependencies ?? {})[args.instrumentation_package]) {
|
|
6295
7237
|
pkg.dependencies = { ...pkg.dependencies ?? {}, [args.instrumentation_package]: args.version };
|
|
6296
|
-
await
|
|
7238
|
+
await import_node_fs22.promises.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
|
|
6297
7239
|
filesTouched.push("package.json");
|
|
6298
7240
|
depsAdded.push(`${args.instrumentation_package}@${args.version}`);
|
|
6299
7241
|
}
|
|
6300
|
-
const hookContent = await
|
|
7242
|
+
const hookContent = await import_node_fs22.promises.readFile(primaryPath, "utf8");
|
|
6301
7243
|
const patched = splicedContent(hookContent, args.registration_snippet);
|
|
6302
7244
|
if (!patched) {
|
|
6303
7245
|
throw new Error(
|
|
6304
7246
|
`Could not find instrumentation insertion point in ${primaryFile}. Expected __INSTRUMENTATION_BLOCK__, instrumentations.push(, or new NodeSDK(.`
|
|
6305
7247
|
);
|
|
6306
7248
|
}
|
|
6307
|
-
await
|
|
7249
|
+
await import_node_fs22.promises.writeFile(primaryPath, patched, "utf8");
|
|
6308
7250
|
filesTouched.push(primaryFile);
|
|
6309
7251
|
const cmd = await detectPackageManager(ctx.scanPath);
|
|
6310
7252
|
const installer = options?.runInstall ?? runPackageManagerInstall;
|
|
@@ -6335,7 +7277,7 @@ async function dryRunExtension(ctx, args) {
|
|
|
6335
7277
|
};
|
|
6336
7278
|
}
|
|
6337
7279
|
for (const file of hookFiles) {
|
|
6338
|
-
const content = await
|
|
7280
|
+
const content = await import_node_fs22.promises.readFile(import_node_path39.default.join(ctx.scanPath, file), "utf8");
|
|
6339
7281
|
if (content.includes(args.registration_snippet)) {
|
|
6340
7282
|
return {
|
|
6341
7283
|
library: args.library,
|
|
@@ -6357,7 +7299,7 @@ async function dryRunExtension(ctx, args) {
|
|
|
6357
7299
|
depsToAdd.push(`${args.instrumentation_package}@${args.version}`);
|
|
6358
7300
|
filesTouched.push("package.json");
|
|
6359
7301
|
}
|
|
6360
|
-
const hookContent = await
|
|
7302
|
+
const hookContent = await import_node_fs22.promises.readFile(import_node_path39.default.join(ctx.scanPath, primaryFile), "utf8");
|
|
6361
7303
|
const patched = splicedContent(hookContent, args.registration_snippet);
|
|
6362
7304
|
if (patched) {
|
|
6363
7305
|
filesTouched.push(primaryFile);
|
|
@@ -6372,28 +7314,28 @@ async function rollbackExtension(ctx, args) {
|
|
|
6372
7314
|
if (!await fileExists2(logPath)) {
|
|
6373
7315
|
return { undone: false, message: "no apply found for library" };
|
|
6374
7316
|
}
|
|
6375
|
-
const raw = await
|
|
7317
|
+
const raw = await import_node_fs22.promises.readFile(logPath, "utf8");
|
|
6376
7318
|
const entries = raw.trim().split("\n").filter(Boolean).map((line) => JSON.parse(line));
|
|
6377
7319
|
const match = [...entries].reverse().find((e) => e.project === ctx.project && e.library === args.library);
|
|
6378
7320
|
if (!match) {
|
|
6379
7321
|
return { undone: false, message: "no apply found for library" };
|
|
6380
7322
|
}
|
|
6381
|
-
const pkgPath =
|
|
7323
|
+
const pkgPath = import_node_path39.default.join(ctx.scanPath, "package.json");
|
|
6382
7324
|
if (await fileExists2(pkgPath)) {
|
|
6383
7325
|
const pkg = await readPackageJson(ctx.scanPath);
|
|
6384
7326
|
if (pkg.dependencies?.[match.instrumentation_package]) {
|
|
6385
7327
|
const { [match.instrumentation_package]: _removed, ...rest } = pkg.dependencies;
|
|
6386
7328
|
pkg.dependencies = rest;
|
|
6387
|
-
await
|
|
7329
|
+
await import_node_fs22.promises.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
|
|
6388
7330
|
}
|
|
6389
7331
|
}
|
|
6390
7332
|
const hookFiles = await findHookFiles(ctx.scanPath);
|
|
6391
7333
|
for (const file of hookFiles) {
|
|
6392
|
-
const filePath =
|
|
6393
|
-
const content = await
|
|
7334
|
+
const filePath = import_node_path39.default.join(ctx.scanPath, file);
|
|
7335
|
+
const content = await import_node_fs22.promises.readFile(filePath, "utf8");
|
|
6394
7336
|
if (content.includes(match.registration_snippet)) {
|
|
6395
7337
|
const filtered = content.split("\n").filter((line) => !line.includes(match.registration_snippet)).join("\n");
|
|
6396
|
-
await
|
|
7338
|
+
await import_node_fs22.promises.writeFile(filePath, filtered, "utf8");
|
|
6397
7339
|
break;
|
|
6398
7340
|
}
|
|
6399
7341
|
}
|
|
@@ -6405,7 +7347,7 @@ async function rollbackExtension(ctx, args) {
|
|
|
6405
7347
|
|
|
6406
7348
|
// src/divergences.ts
|
|
6407
7349
|
init_cjs_shims();
|
|
6408
|
-
var
|
|
7350
|
+
var import_types27 = require("@neat.is/types");
|
|
6409
7351
|
function bucketKey(source, target, type) {
|
|
6410
7352
|
return `${type}|${source}|${target}`;
|
|
6411
7353
|
}
|
|
@@ -6413,22 +7355,22 @@ function bucketEdges(graph) {
|
|
|
6413
7355
|
const buckets = /* @__PURE__ */ new Map();
|
|
6414
7356
|
graph.forEachEdge((id, attrs) => {
|
|
6415
7357
|
const e = attrs;
|
|
6416
|
-
const parsed = (0,
|
|
7358
|
+
const parsed = (0, import_types27.parseEdgeId)(id);
|
|
6417
7359
|
const provenance = parsed?.provenance ?? e.provenance;
|
|
6418
7360
|
const key = bucketKey(e.source, e.target, e.type);
|
|
6419
7361
|
const cur = buckets.get(key) ?? { source: e.source, target: e.target, type: e.type };
|
|
6420
7362
|
switch (provenance) {
|
|
6421
|
-
case
|
|
7363
|
+
case import_types27.Provenance.EXTRACTED:
|
|
6422
7364
|
cur.extracted = e;
|
|
6423
7365
|
break;
|
|
6424
|
-
case
|
|
7366
|
+
case import_types27.Provenance.OBSERVED:
|
|
6425
7367
|
cur.observed = e;
|
|
6426
7368
|
break;
|
|
6427
|
-
case
|
|
7369
|
+
case import_types27.Provenance.INFERRED:
|
|
6428
7370
|
cur.inferred = e;
|
|
6429
7371
|
break;
|
|
6430
7372
|
default:
|
|
6431
|
-
if (e.provenance ===
|
|
7373
|
+
if (e.provenance === import_types27.Provenance.STALE) cur.stale = e;
|
|
6432
7374
|
}
|
|
6433
7375
|
buckets.set(key, cur);
|
|
6434
7376
|
});
|
|
@@ -6437,7 +7379,12 @@ function bucketEdges(graph) {
|
|
|
6437
7379
|
function nodeIsFrontier(graph, nodeId) {
|
|
6438
7380
|
if (!graph.hasNode(nodeId)) return false;
|
|
6439
7381
|
const attrs = graph.getNodeAttributes(nodeId);
|
|
6440
|
-
return attrs.type ===
|
|
7382
|
+
return attrs.type === import_types27.NodeType.FrontierNode;
|
|
7383
|
+
}
|
|
7384
|
+
function nodeIsWebsocketChannel(graph, nodeId) {
|
|
7385
|
+
if (!graph.hasNode(nodeId)) return false;
|
|
7386
|
+
const attrs = graph.getNodeAttributes(nodeId);
|
|
7387
|
+
return attrs.type === import_types27.NodeType.WebSocketChannelNode;
|
|
6441
7388
|
}
|
|
6442
7389
|
function clampConfidence(n) {
|
|
6443
7390
|
if (!Number.isFinite(n)) return 0;
|
|
@@ -6457,14 +7404,14 @@ function gradedConfidence(edge) {
|
|
|
6457
7404
|
return clampConfidence(confidenceForEdge(edge));
|
|
6458
7405
|
}
|
|
6459
7406
|
var OBSERVABLE_EDGE_TYPES = /* @__PURE__ */ new Set([
|
|
6460
|
-
|
|
6461
|
-
|
|
6462
|
-
|
|
6463
|
-
|
|
7407
|
+
import_types27.EdgeType.CALLS,
|
|
7408
|
+
import_types27.EdgeType.CONNECTS_TO,
|
|
7409
|
+
import_types27.EdgeType.PUBLISHES_TO,
|
|
7410
|
+
import_types27.EdgeType.CONSUMES_FROM
|
|
6464
7411
|
]);
|
|
6465
7412
|
function detectMissingDivergences(graph, bucket) {
|
|
6466
7413
|
const out = [];
|
|
6467
|
-
if (bucket.type ===
|
|
7414
|
+
if (bucket.type === import_types27.EdgeType.CONTAINS) return out;
|
|
6468
7415
|
if (bucket.extracted && !bucket.observed && OBSERVABLE_EDGE_TYPES.has(bucket.type)) {
|
|
6469
7416
|
if (!nodeIsFrontier(graph, bucket.target)) {
|
|
6470
7417
|
out.push({
|
|
@@ -6479,7 +7426,7 @@ function detectMissingDivergences(graph, bucket) {
|
|
|
6479
7426
|
});
|
|
6480
7427
|
}
|
|
6481
7428
|
}
|
|
6482
|
-
if (bucket.observed && !bucket.extracted) {
|
|
7429
|
+
if (bucket.observed && !bucket.extracted && !nodeIsWebsocketChannel(graph, bucket.target)) {
|
|
6483
7430
|
out.push({
|
|
6484
7431
|
type: "missing-extracted",
|
|
6485
7432
|
source: bucket.source,
|
|
@@ -6505,7 +7452,7 @@ function declaredHostFor(svc) {
|
|
|
6505
7452
|
function hasExtractedConfiguredBy(graph, svcId) {
|
|
6506
7453
|
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
6507
7454
|
const e = graph.getEdgeAttributes(edgeId);
|
|
6508
|
-
if (e.type ===
|
|
7455
|
+
if (e.type === import_types27.EdgeType.CONFIGURED_BY && e.provenance === import_types27.Provenance.EXTRACTED) {
|
|
6509
7456
|
return true;
|
|
6510
7457
|
}
|
|
6511
7458
|
}
|
|
@@ -6518,10 +7465,10 @@ function detectHostMismatch(graph, svcId, svc) {
|
|
|
6518
7465
|
const out = [];
|
|
6519
7466
|
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
6520
7467
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
6521
|
-
if (edge.type !==
|
|
6522
|
-
if (edge.provenance !==
|
|
7468
|
+
if (edge.type !== import_types27.EdgeType.CONNECTS_TO) continue;
|
|
7469
|
+
if (edge.provenance !== import_types27.Provenance.OBSERVED) continue;
|
|
6523
7470
|
const target = graph.getNodeAttributes(edge.target);
|
|
6524
|
-
if (target.type !==
|
|
7471
|
+
if (target.type !== import_types27.NodeType.DatabaseNode) continue;
|
|
6525
7472
|
const observedHost = target.host?.trim();
|
|
6526
7473
|
if (!observedHost) continue;
|
|
6527
7474
|
if (observedHost === declaredHost) continue;
|
|
@@ -6543,10 +7490,10 @@ function detectCompatDivergences(graph, svcId, svc) {
|
|
|
6543
7490
|
const deps = svc.dependencies ?? {};
|
|
6544
7491
|
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
6545
7492
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
6546
|
-
if (edge.type !==
|
|
6547
|
-
if (edge.provenance !==
|
|
7493
|
+
if (edge.type !== import_types27.EdgeType.CONNECTS_TO) continue;
|
|
7494
|
+
if (edge.provenance !== import_types27.Provenance.OBSERVED) continue;
|
|
6548
7495
|
const target = graph.getNodeAttributes(edge.target);
|
|
6549
|
-
if (target.type !==
|
|
7496
|
+
if (target.type !== import_types27.NodeType.DatabaseNode) continue;
|
|
6550
7497
|
for (const pair of compatPairs()) {
|
|
6551
7498
|
if (pair.engine !== target.engine) continue;
|
|
6552
7499
|
const declared = deps[pair.driver];
|
|
@@ -6605,7 +7552,7 @@ function suppressHostMismatchHalves(all) {
|
|
|
6605
7552
|
for (const d of all) {
|
|
6606
7553
|
if (d.type !== "host-mismatch") continue;
|
|
6607
7554
|
observedHalf.add(`${d.source}->${d.target}`);
|
|
6608
|
-
declaredHalf.add((0,
|
|
7555
|
+
declaredHalf.add((0, import_types27.databaseId)(d.extractedHost));
|
|
6609
7556
|
}
|
|
6610
7557
|
if (observedHalf.size === 0) return all;
|
|
6611
7558
|
return all.filter((d) => {
|
|
@@ -6624,7 +7571,7 @@ function computeDivergences(graph, opts = {}) {
|
|
|
6624
7571
|
}
|
|
6625
7572
|
graph.forEachNode((nodeId, attrs) => {
|
|
6626
7573
|
const n = attrs;
|
|
6627
|
-
if (n.type !==
|
|
7574
|
+
if (n.type !== import_types27.NodeType.ServiceNode) return;
|
|
6628
7575
|
const svc = n;
|
|
6629
7576
|
for (const d of detectHostMismatch(graph, nodeId, svc)) all.push(d);
|
|
6630
7577
|
for (const d of detectCompatDivergences(graph, nodeId, svc)) all.push(d);
|
|
@@ -6658,7 +7605,7 @@ function computeDivergences(graph, opts = {}) {
|
|
|
6658
7605
|
if (a.source !== b.source) return a.source.localeCompare(b.source);
|
|
6659
7606
|
return a.target.localeCompare(b.target);
|
|
6660
7607
|
});
|
|
6661
|
-
return
|
|
7608
|
+
return import_types27.DivergenceResultSchema.parse({
|
|
6662
7609
|
divergences: filtered,
|
|
6663
7610
|
totalAffected: filtered.length,
|
|
6664
7611
|
computedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -6667,7 +7614,7 @@ function computeDivergences(graph, opts = {}) {
|
|
|
6667
7614
|
|
|
6668
7615
|
// src/diff.ts
|
|
6669
7616
|
init_cjs_shims();
|
|
6670
|
-
var
|
|
7617
|
+
var import_node_fs23 = require("fs");
|
|
6671
7618
|
async function loadSnapshotForDiff(target) {
|
|
6672
7619
|
if (/^https?:\/\//i.test(target)) {
|
|
6673
7620
|
const res = await fetch(target);
|
|
@@ -6676,7 +7623,7 @@ async function loadSnapshotForDiff(target) {
|
|
|
6676
7623
|
}
|
|
6677
7624
|
return await res.json();
|
|
6678
7625
|
}
|
|
6679
|
-
const raw = await
|
|
7626
|
+
const raw = await import_node_fs23.promises.readFile(target, "utf8");
|
|
6680
7627
|
return JSON.parse(raw);
|
|
6681
7628
|
}
|
|
6682
7629
|
function indexEntries(entries) {
|
|
@@ -6744,23 +7691,23 @@ function canonicalJson(value) {
|
|
|
6744
7691
|
|
|
6745
7692
|
// src/projects.ts
|
|
6746
7693
|
init_cjs_shims();
|
|
6747
|
-
var
|
|
7694
|
+
var import_node_path40 = __toESM(require("path"), 1);
|
|
6748
7695
|
function pathsForProject(project, baseDir) {
|
|
6749
7696
|
if (project === DEFAULT_PROJECT) {
|
|
6750
7697
|
return {
|
|
6751
|
-
snapshotPath:
|
|
6752
|
-
errorsPath:
|
|
6753
|
-
staleEventsPath:
|
|
6754
|
-
embeddingsCachePath:
|
|
6755
|
-
policyViolationsPath:
|
|
7698
|
+
snapshotPath: import_node_path40.default.join(baseDir, "graph.json"),
|
|
7699
|
+
errorsPath: import_node_path40.default.join(baseDir, "errors.ndjson"),
|
|
7700
|
+
staleEventsPath: import_node_path40.default.join(baseDir, "stale-events.ndjson"),
|
|
7701
|
+
embeddingsCachePath: import_node_path40.default.join(baseDir, "embeddings.json"),
|
|
7702
|
+
policyViolationsPath: import_node_path40.default.join(baseDir, "policy-violations.ndjson")
|
|
6756
7703
|
};
|
|
6757
7704
|
}
|
|
6758
7705
|
return {
|
|
6759
|
-
snapshotPath:
|
|
6760
|
-
errorsPath:
|
|
6761
|
-
staleEventsPath:
|
|
6762
|
-
embeddingsCachePath:
|
|
6763
|
-
policyViolationsPath:
|
|
7706
|
+
snapshotPath: import_node_path40.default.join(baseDir, `${project}.json`),
|
|
7707
|
+
errorsPath: import_node_path40.default.join(baseDir, `errors.${project}.ndjson`),
|
|
7708
|
+
staleEventsPath: import_node_path40.default.join(baseDir, `stale-events.${project}.ndjson`),
|
|
7709
|
+
embeddingsCachePath: import_node_path40.default.join(baseDir, `embeddings.${project}.json`),
|
|
7710
|
+
policyViolationsPath: import_node_path40.default.join(baseDir, `policy-violations.${project}.ndjson`)
|
|
6764
7711
|
};
|
|
6765
7712
|
}
|
|
6766
7713
|
var Projects = class {
|
|
@@ -6796,25 +7743,25 @@ var Projects = class {
|
|
|
6796
7743
|
|
|
6797
7744
|
// src/registry.ts
|
|
6798
7745
|
init_cjs_shims();
|
|
6799
|
-
var
|
|
7746
|
+
var import_node_fs24 = require("fs");
|
|
6800
7747
|
var import_node_os3 = __toESM(require("os"), 1);
|
|
6801
|
-
var
|
|
6802
|
-
var
|
|
7748
|
+
var import_node_path41 = __toESM(require("path"), 1);
|
|
7749
|
+
var import_types28 = require("@neat.is/types");
|
|
6803
7750
|
var LOCK_TIMEOUT_MS = 5e3;
|
|
6804
7751
|
var LOCK_RETRY_MS = 50;
|
|
6805
7752
|
function neatHome() {
|
|
6806
7753
|
const override = process.env.NEAT_HOME;
|
|
6807
|
-
if (override && override.length > 0) return
|
|
6808
|
-
return
|
|
7754
|
+
if (override && override.length > 0) return import_node_path41.default.resolve(override);
|
|
7755
|
+
return import_node_path41.default.join(import_node_os3.default.homedir(), ".neat");
|
|
6809
7756
|
}
|
|
6810
7757
|
function registryPath() {
|
|
6811
|
-
return
|
|
7758
|
+
return import_node_path41.default.join(neatHome(), "projects.json");
|
|
6812
7759
|
}
|
|
6813
7760
|
function registryLockPath() {
|
|
6814
|
-
return
|
|
7761
|
+
return import_node_path41.default.join(neatHome(), "projects.json.lock");
|
|
6815
7762
|
}
|
|
6816
7763
|
function daemonPidPath() {
|
|
6817
|
-
return
|
|
7764
|
+
return import_node_path41.default.join(neatHome(), "neatd.pid");
|
|
6818
7765
|
}
|
|
6819
7766
|
function isPidAliveDefault(pid) {
|
|
6820
7767
|
try {
|
|
@@ -6826,7 +7773,7 @@ function isPidAliveDefault(pid) {
|
|
|
6826
7773
|
}
|
|
6827
7774
|
async function readPidFile(file) {
|
|
6828
7775
|
try {
|
|
6829
|
-
const raw = await
|
|
7776
|
+
const raw = await import_node_fs24.promises.readFile(file, "utf8");
|
|
6830
7777
|
const pid = Number.parseInt(raw.trim(), 10);
|
|
6831
7778
|
return Number.isInteger(pid) && pid > 0 ? pid : void 0;
|
|
6832
7779
|
} catch {
|
|
@@ -6874,32 +7821,32 @@ function lockHolderMessage(holder, lockPath, timeoutMs) {
|
|
|
6874
7821
|
}
|
|
6875
7822
|
}
|
|
6876
7823
|
async function normalizeProjectPath(input) {
|
|
6877
|
-
const resolved =
|
|
7824
|
+
const resolved = import_node_path41.default.resolve(input);
|
|
6878
7825
|
try {
|
|
6879
|
-
return await
|
|
7826
|
+
return await import_node_fs24.promises.realpath(resolved);
|
|
6880
7827
|
} catch {
|
|
6881
7828
|
return resolved;
|
|
6882
7829
|
}
|
|
6883
7830
|
}
|
|
6884
7831
|
async function writeAtomically(target, contents) {
|
|
6885
|
-
await
|
|
7832
|
+
await import_node_fs24.promises.mkdir(import_node_path41.default.dirname(target), { recursive: true });
|
|
6886
7833
|
const tmp = `${target}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
|
|
6887
|
-
const fd = await
|
|
7834
|
+
const fd = await import_node_fs24.promises.open(tmp, "w");
|
|
6888
7835
|
try {
|
|
6889
7836
|
await fd.writeFile(contents, "utf8");
|
|
6890
7837
|
await fd.sync();
|
|
6891
7838
|
} finally {
|
|
6892
7839
|
await fd.close();
|
|
6893
7840
|
}
|
|
6894
|
-
await
|
|
7841
|
+
await import_node_fs24.promises.rename(tmp, target);
|
|
6895
7842
|
}
|
|
6896
7843
|
async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaultLockHolderProbe) {
|
|
6897
7844
|
const deadline = Date.now() + timeoutMs;
|
|
6898
|
-
await
|
|
7845
|
+
await import_node_fs24.promises.mkdir(import_node_path41.default.dirname(lockPath), { recursive: true });
|
|
6899
7846
|
let probedHolder = false;
|
|
6900
7847
|
while (true) {
|
|
6901
7848
|
try {
|
|
6902
|
-
const fd = await
|
|
7849
|
+
const fd = await import_node_fs24.promises.open(lockPath, "wx");
|
|
6903
7850
|
try {
|
|
6904
7851
|
await fd.writeFile(`${process.pid}
|
|
6905
7852
|
`, "utf8");
|
|
@@ -6924,7 +7871,7 @@ async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaul
|
|
|
6924
7871
|
}
|
|
6925
7872
|
}
|
|
6926
7873
|
async function releaseLock(lockPath) {
|
|
6927
|
-
await
|
|
7874
|
+
await import_node_fs24.promises.unlink(lockPath).catch(() => {
|
|
6928
7875
|
});
|
|
6929
7876
|
}
|
|
6930
7877
|
async function withLock(fn) {
|
|
@@ -6940,7 +7887,7 @@ async function readRegistry() {
|
|
|
6940
7887
|
const file = registryPath();
|
|
6941
7888
|
let raw;
|
|
6942
7889
|
try {
|
|
6943
|
-
raw = await
|
|
7890
|
+
raw = await import_node_fs24.promises.readFile(file, "utf8");
|
|
6944
7891
|
} catch (err) {
|
|
6945
7892
|
if (err.code === "ENOENT") {
|
|
6946
7893
|
return { version: 1, projects: [] };
|
|
@@ -6948,10 +7895,10 @@ async function readRegistry() {
|
|
|
6948
7895
|
throw err;
|
|
6949
7896
|
}
|
|
6950
7897
|
const parsed = JSON.parse(raw);
|
|
6951
|
-
return
|
|
7898
|
+
return import_types28.RegistryFileSchema.parse(parsed);
|
|
6952
7899
|
}
|
|
6953
7900
|
async function writeRegistry(reg) {
|
|
6954
|
-
const validated =
|
|
7901
|
+
const validated = import_types28.RegistryFileSchema.parse(reg);
|
|
6955
7902
|
await writeAtomically(registryPath(), JSON.stringify(validated, null, 2) + "\n");
|
|
6956
7903
|
}
|
|
6957
7904
|
var ProjectNameCollisionError = class extends Error {
|
|
@@ -7045,7 +7992,7 @@ function pruneTtlMs() {
|
|
|
7045
7992
|
}
|
|
7046
7993
|
async function statPathStatus(p) {
|
|
7047
7994
|
try {
|
|
7048
|
-
const stat = await
|
|
7995
|
+
const stat = await import_node_fs24.promises.stat(p);
|
|
7049
7996
|
return stat.isDirectory() ? "present" : "unknown";
|
|
7050
7997
|
} catch (err) {
|
|
7051
7998
|
return err.code === "ENOENT" ? "gone" : "unknown";
|
|
@@ -7292,11 +8239,11 @@ function registerRoutes(scope, ctx) {
|
|
|
7292
8239
|
const candidates = req.query.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
7293
8240
|
const parsed = [];
|
|
7294
8241
|
for (const c of candidates) {
|
|
7295
|
-
const r =
|
|
8242
|
+
const r = import_types29.DivergenceTypeSchema.safeParse(c);
|
|
7296
8243
|
if (!r.success) {
|
|
7297
8244
|
return reply.code(400).send({
|
|
7298
8245
|
error: `unknown divergence type "${c}"`,
|
|
7299
|
-
allowed:
|
|
8246
|
+
allowed: import_types29.DivergenceTypeSchema.options
|
|
7300
8247
|
});
|
|
7301
8248
|
}
|
|
7302
8249
|
parsed.push(r.data);
|
|
@@ -7515,7 +8462,7 @@ function registerRoutes(scope, ctx) {
|
|
|
7515
8462
|
const log = new PolicyViolationsLog(proj.paths.policyViolationsPath);
|
|
7516
8463
|
let violations = await log.readAll();
|
|
7517
8464
|
if (req.query.severity) {
|
|
7518
|
-
const sev =
|
|
8465
|
+
const sev = import_types29.PolicySeveritySchema.safeParse(req.query.severity);
|
|
7519
8466
|
if (!sev.success) {
|
|
7520
8467
|
return reply.code(400).send({
|
|
7521
8468
|
error: "invalid severity",
|
|
@@ -7554,7 +8501,7 @@ function registerRoutes(scope, ctx) {
|
|
|
7554
8501
|
scope.post("/policies/check", async (req, reply) => {
|
|
7555
8502
|
const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
|
|
7556
8503
|
if (!proj) return;
|
|
7557
|
-
const parsed =
|
|
8504
|
+
const parsed = import_types29.PoliciesCheckBodySchema.safeParse(req.body ?? {});
|
|
7558
8505
|
if (!parsed.success) {
|
|
7559
8506
|
return reply.code(400).send({
|
|
7560
8507
|
error: "invalid /policies/check body",
|
|
@@ -7816,16 +8763,95 @@ init_otel_grpc();
|
|
|
7816
8763
|
|
|
7817
8764
|
// src/daemon.ts
|
|
7818
8765
|
init_cjs_shims();
|
|
7819
|
-
var
|
|
7820
|
-
var
|
|
8766
|
+
var import_node_fs26 = require("fs");
|
|
8767
|
+
var import_node_path45 = __toESM(require("path"), 1);
|
|
7821
8768
|
var import_node_module = require("module");
|
|
7822
8769
|
init_otel();
|
|
8770
|
+
|
|
8771
|
+
// src/connectors/index.ts
|
|
8772
|
+
init_cjs_shims();
|
|
8773
|
+
var NO_ENV = "unknown";
|
|
8774
|
+
async function runConnectorPoll(connector, ctx, graph, resolveTarget) {
|
|
8775
|
+
const signals = await connector.poll(ctx);
|
|
8776
|
+
let edgesCreated = 0;
|
|
8777
|
+
let edgesUpdated = 0;
|
|
8778
|
+
let unresolved = 0;
|
|
8779
|
+
for (const signal of signals) {
|
|
8780
|
+
const resolved = resolveTarget(signal, ctx);
|
|
8781
|
+
if (!resolved) {
|
|
8782
|
+
unresolved++;
|
|
8783
|
+
continue;
|
|
8784
|
+
}
|
|
8785
|
+
const serviceNodeId = ensureServiceNode(graph, resolved.serviceName, NO_ENV);
|
|
8786
|
+
const callSite = signal.callSite ? { relPath: signal.callSite.file, line: signal.callSite.line } : void 0;
|
|
8787
|
+
const sourceId = callSite ? ensureObservedFileNode(graph, resolved.serviceName, serviceNodeId, callSite) : serviceNodeId;
|
|
8788
|
+
const evidence = callSite ? {
|
|
8789
|
+
file: reconcileObservedRelPath(graph, resolved.serviceName, callSite.relPath),
|
|
8790
|
+
line: callSite.line
|
|
8791
|
+
} : void 0;
|
|
8792
|
+
const calls = Math.trunc(signal.callCount);
|
|
8793
|
+
if (calls < 1) continue;
|
|
8794
|
+
const errors = Math.min(Math.max(Math.trunc(signal.errorCount), 0), calls);
|
|
8795
|
+
let created = false;
|
|
8796
|
+
let ok = true;
|
|
8797
|
+
for (let i = 0; i < calls; i++) {
|
|
8798
|
+
const result = upsertObservedEdge(
|
|
8799
|
+
graph,
|
|
8800
|
+
resolved.edgeType,
|
|
8801
|
+
sourceId,
|
|
8802
|
+
resolved.targetNodeId,
|
|
8803
|
+
signal.lastObservedIso,
|
|
8804
|
+
i < errors,
|
|
8805
|
+
evidence
|
|
8806
|
+
);
|
|
8807
|
+
if (!result) {
|
|
8808
|
+
ok = false;
|
|
8809
|
+
break;
|
|
8810
|
+
}
|
|
8811
|
+
if (i === 0) created = result.created;
|
|
8812
|
+
}
|
|
8813
|
+
if (!ok) {
|
|
8814
|
+
unresolved++;
|
|
8815
|
+
continue;
|
|
8816
|
+
}
|
|
8817
|
+
if (created) edgesCreated++;
|
|
8818
|
+
else edgesUpdated++;
|
|
8819
|
+
}
|
|
8820
|
+
return { signalCount: signals.length, edgesCreated, edgesUpdated, unresolved };
|
|
8821
|
+
}
|
|
8822
|
+
var DEFAULT_POLL_INTERVAL_MS = 6e4;
|
|
8823
|
+
function startConnectorPollLoop(connector, ctx, graph, resolveTarget, options = {}) {
|
|
8824
|
+
let stopped = false;
|
|
8825
|
+
let since = ctx.since;
|
|
8826
|
+
const intervalMs = options.intervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
8827
|
+
const onError = options.onError ?? ((err) => console.error(`[neatd] connector poll failed (${connector.provider})`, err));
|
|
8828
|
+
const tick = () => {
|
|
8829
|
+
if (stopped) return;
|
|
8830
|
+
void (async () => {
|
|
8831
|
+
const tickStartedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
8832
|
+
try {
|
|
8833
|
+
await runConnectorPoll(connector, { ...ctx, since }, graph, resolveTarget);
|
|
8834
|
+
since = tickStartedAt;
|
|
8835
|
+
} catch (err) {
|
|
8836
|
+
onError(err);
|
|
8837
|
+
}
|
|
8838
|
+
})();
|
|
8839
|
+
};
|
|
8840
|
+
const interval = setInterval(tick, intervalMs);
|
|
8841
|
+
if (typeof interval.unref === "function") interval.unref();
|
|
8842
|
+
return () => {
|
|
8843
|
+
stopped = true;
|
|
8844
|
+
clearInterval(interval);
|
|
8845
|
+
};
|
|
8846
|
+
}
|
|
8847
|
+
|
|
8848
|
+
// src/daemon.ts
|
|
7823
8849
|
init_auth();
|
|
7824
8850
|
|
|
7825
8851
|
// src/unrouted.ts
|
|
7826
8852
|
init_cjs_shims();
|
|
7827
|
-
var
|
|
7828
|
-
var
|
|
8853
|
+
var import_node_fs25 = require("fs");
|
|
8854
|
+
var import_node_path44 = __toESM(require("path"), 1);
|
|
7829
8855
|
function buildUnroutedSpanRecord(serviceName, traceId, now = /* @__PURE__ */ new Date()) {
|
|
7830
8856
|
return {
|
|
7831
8857
|
timestamp: now.toISOString(),
|
|
@@ -7835,34 +8861,34 @@ function buildUnroutedSpanRecord(serviceName, traceId, now = /* @__PURE__ */ new
|
|
|
7835
8861
|
};
|
|
7836
8862
|
}
|
|
7837
8863
|
async function appendUnroutedSpan(neatHome2, record) {
|
|
7838
|
-
const target =
|
|
7839
|
-
await
|
|
7840
|
-
await
|
|
8864
|
+
const target = import_node_path44.default.join(neatHome2, "errors.ndjson");
|
|
8865
|
+
await import_node_fs25.promises.mkdir(neatHome2, { recursive: true });
|
|
8866
|
+
await import_node_fs25.promises.appendFile(target, JSON.stringify(record) + "\n", "utf8");
|
|
7841
8867
|
}
|
|
7842
8868
|
function unroutedErrorsPath(neatHome2) {
|
|
7843
|
-
return
|
|
8869
|
+
return import_node_path44.default.join(neatHome2, "errors.ndjson");
|
|
7844
8870
|
}
|
|
7845
8871
|
|
|
7846
8872
|
// src/daemon.ts
|
|
7847
|
-
var
|
|
8873
|
+
var import_types30 = require("@neat.is/types");
|
|
7848
8874
|
function daemonJsonPath(scanPath) {
|
|
7849
|
-
return
|
|
8875
|
+
return import_node_path45.default.join(scanPath, "neat-out", "daemon.json");
|
|
7850
8876
|
}
|
|
7851
8877
|
function daemonsDiscoveryDir(home) {
|
|
7852
8878
|
const base = home && home.length > 0 ? home : neatHomeFromEnv();
|
|
7853
|
-
return
|
|
8879
|
+
return import_node_path45.default.join(base, "daemons");
|
|
7854
8880
|
}
|
|
7855
8881
|
function daemonDiscoveryPath(project, home) {
|
|
7856
|
-
return
|
|
8882
|
+
return import_node_path45.default.join(daemonsDiscoveryDir(home), `${sanitizeDiscoveryName(project)}.json`);
|
|
7857
8883
|
}
|
|
7858
8884
|
function sanitizeDiscoveryName(project) {
|
|
7859
8885
|
return project.replace(/[^A-Za-z0-9._-]/g, "_");
|
|
7860
8886
|
}
|
|
7861
8887
|
function neatHomeFromEnv() {
|
|
7862
8888
|
const env = process.env.NEAT_HOME;
|
|
7863
|
-
if (env && env.length > 0) return
|
|
8889
|
+
if (env && env.length > 0) return import_node_path45.default.resolve(env);
|
|
7864
8890
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
7865
|
-
return
|
|
8891
|
+
return import_node_path45.default.join(home, ".neat");
|
|
7866
8892
|
}
|
|
7867
8893
|
function resolveNeatVersion() {
|
|
7868
8894
|
if (process.env.NEAT_LOCAL_VERSION && process.env.NEAT_LOCAL_VERSION.length > 0) {
|
|
@@ -7894,7 +8920,7 @@ async function clearDaemonRecord(record, home) {
|
|
|
7894
8920
|
} catch {
|
|
7895
8921
|
}
|
|
7896
8922
|
try {
|
|
7897
|
-
await
|
|
8923
|
+
await import_node_fs26.promises.unlink(daemonDiscoveryPath(record.project, home));
|
|
7898
8924
|
} catch {
|
|
7899
8925
|
}
|
|
7900
8926
|
}
|
|
@@ -7903,17 +8929,25 @@ function teardownSlot(slot) {
|
|
|
7903
8929
|
slot.stopPersist();
|
|
7904
8930
|
} catch {
|
|
7905
8931
|
}
|
|
8932
|
+
try {
|
|
8933
|
+
slot.stopStaleness();
|
|
8934
|
+
} catch {
|
|
8935
|
+
}
|
|
8936
|
+
try {
|
|
8937
|
+
slot.stopConnectors();
|
|
8938
|
+
} catch {
|
|
8939
|
+
}
|
|
7906
8940
|
try {
|
|
7907
8941
|
slot.detachEvents();
|
|
7908
8942
|
} catch {
|
|
7909
8943
|
}
|
|
7910
8944
|
}
|
|
7911
8945
|
function neatHomeFor(opts) {
|
|
7912
|
-
if (opts.neatHome && opts.neatHome.length > 0) return
|
|
8946
|
+
if (opts.neatHome && opts.neatHome.length > 0) return import_node_path45.default.resolve(opts.neatHome);
|
|
7913
8947
|
const env = process.env.NEAT_HOME;
|
|
7914
|
-
if (env && env.length > 0) return
|
|
8948
|
+
if (env && env.length > 0) return import_node_path45.default.resolve(env);
|
|
7915
8949
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
7916
|
-
return
|
|
8950
|
+
return import_node_path45.default.join(home, ".neat");
|
|
7917
8951
|
}
|
|
7918
8952
|
function routeSpanToProject(serviceName, projects) {
|
|
7919
8953
|
if (!serviceName) return DEFAULT_PROJECT;
|
|
@@ -7957,13 +8991,13 @@ function spanBelongsToSingleProject(graph, project, serviceName) {
|
|
|
7957
8991
|
if (!serviceName) return true;
|
|
7958
8992
|
if (serviceNameMatchesProject(serviceName, project)) return true;
|
|
7959
8993
|
return graph.someNode(
|
|
7960
|
-
(_id, attrs) => attrs.type ===
|
|
8994
|
+
(_id, attrs) => attrs.type === import_types30.NodeType.ServiceNode && attrs.name === serviceName
|
|
7961
8995
|
);
|
|
7962
8996
|
}
|
|
7963
|
-
async function bootstrapProject(entry) {
|
|
7964
|
-
const paths = pathsForProject(entry.name,
|
|
8997
|
+
async function bootstrapProject(entry, connectors = []) {
|
|
8998
|
+
const paths = pathsForProject(entry.name, import_node_path45.default.join(entry.path, "neat-out"));
|
|
7965
8999
|
try {
|
|
7966
|
-
const stat = await
|
|
9000
|
+
const stat = await import_node_fs26.promises.stat(entry.path);
|
|
7967
9001
|
if (!stat.isDirectory()) {
|
|
7968
9002
|
throw new Error(`registered path ${entry.path} is not a directory`);
|
|
7969
9003
|
}
|
|
@@ -7979,6 +9013,10 @@ async function bootstrapProject(entry) {
|
|
|
7979
9013
|
paths,
|
|
7980
9014
|
stopPersist: () => {
|
|
7981
9015
|
},
|
|
9016
|
+
stopStaleness: () => {
|
|
9017
|
+
},
|
|
9018
|
+
stopConnectors: () => {
|
|
9019
|
+
},
|
|
7982
9020
|
detachEvents: () => {
|
|
7983
9021
|
},
|
|
7984
9022
|
status: "broken",
|
|
@@ -7993,6 +9031,22 @@ async function bootstrapProject(entry) {
|
|
|
7993
9031
|
try {
|
|
7994
9032
|
await extractFromDirectory(graph, entry.path);
|
|
7995
9033
|
const stopPersist = startPersistLoop(graph, outPath, { exitOnSignal: false });
|
|
9034
|
+
const stopStaleness = startStalenessLoop(graph, {
|
|
9035
|
+
staleEventsPath: paths.staleEventsPath,
|
|
9036
|
+
project: entry.name
|
|
9037
|
+
});
|
|
9038
|
+
const stopFns = connectors.map(
|
|
9039
|
+
(registration) => startConnectorPollLoop(
|
|
9040
|
+
registration.connector,
|
|
9041
|
+
{ projectDir: entry.path, credentials: registration.credentials },
|
|
9042
|
+
graph,
|
|
9043
|
+
registration.resolveTarget,
|
|
9044
|
+
{ intervalMs: registration.intervalMs }
|
|
9045
|
+
)
|
|
9046
|
+
);
|
|
9047
|
+
const stopConnectors = () => {
|
|
9048
|
+
for (const stop of stopFns) stop();
|
|
9049
|
+
};
|
|
7996
9050
|
await touchLastSeen(entry.name).catch(() => {
|
|
7997
9051
|
});
|
|
7998
9052
|
return {
|
|
@@ -8001,6 +9055,8 @@ async function bootstrapProject(entry) {
|
|
|
8001
9055
|
outPath,
|
|
8002
9056
|
paths,
|
|
8003
9057
|
stopPersist,
|
|
9058
|
+
stopStaleness,
|
|
9059
|
+
stopConnectors,
|
|
8004
9060
|
detachEvents,
|
|
8005
9061
|
status: "active"
|
|
8006
9062
|
};
|
|
@@ -8057,7 +9113,7 @@ async function startDaemon(opts = {}) {
|
|
|
8057
9113
|
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;
|
|
8058
9114
|
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;
|
|
8059
9115
|
const singleProject = projectArg;
|
|
8060
|
-
const singleProjectPath = singleProject && projectPathArg ?
|
|
9116
|
+
const singleProjectPath = singleProject && projectPathArg ? import_node_path45.default.resolve(projectPathArg) : null;
|
|
8061
9117
|
if (singleProject && !singleProjectPath) {
|
|
8062
9118
|
throw new Error(
|
|
8063
9119
|
`neatd: project "${singleProject}" given without a projectPath; pass NEAT_PROJECT_PATH alongside NEAT_PROJECT.`
|
|
@@ -8065,14 +9121,14 @@ async function startDaemon(opts = {}) {
|
|
|
8065
9121
|
}
|
|
8066
9122
|
if (!singleProject) {
|
|
8067
9123
|
try {
|
|
8068
|
-
await
|
|
9124
|
+
await import_node_fs26.promises.access(regPath);
|
|
8069
9125
|
} catch {
|
|
8070
9126
|
throw new Error(
|
|
8071
9127
|
`neatd: registry not found at ${regPath}. Run \`neat init <path>\` to register a project before starting the daemon.`
|
|
8072
9128
|
);
|
|
8073
9129
|
}
|
|
8074
9130
|
}
|
|
8075
|
-
const pidPath =
|
|
9131
|
+
const pidPath = import_node_path45.default.join(home, "neatd.pid");
|
|
8076
9132
|
await writeAtomically(pidPath, `${process.pid}
|
|
8077
9133
|
`);
|
|
8078
9134
|
const slots = /* @__PURE__ */ new Map();
|
|
@@ -8116,7 +9172,7 @@ async function startDaemon(opts = {}) {
|
|
|
8116
9172
|
}
|
|
8117
9173
|
async function tryRecoverSlot(entry) {
|
|
8118
9174
|
try {
|
|
8119
|
-
const fresh = await bootstrapProject(entry);
|
|
9175
|
+
const fresh = await bootstrapProject(entry, opts.connectors ?? []);
|
|
8120
9176
|
const prior = slots.get(entry.name);
|
|
8121
9177
|
if (prior) teardownSlot(prior);
|
|
8122
9178
|
slots.set(entry.name, fresh);
|
|
@@ -8140,7 +9196,7 @@ async function startDaemon(opts = {}) {
|
|
|
8140
9196
|
bootstrapStatus.set(entry.name, "bootstrapping");
|
|
8141
9197
|
bootstrapStartedAt.set(entry.name, Date.now());
|
|
8142
9198
|
try {
|
|
8143
|
-
const slot = await bootstrapProject(entry);
|
|
9199
|
+
const slot = await bootstrapProject(entry, opts.connectors ?? []);
|
|
8144
9200
|
const prior = slots.get(entry.name);
|
|
8145
9201
|
if (prior) teardownSlot(prior);
|
|
8146
9202
|
slots.set(entry.name, slot);
|
|
@@ -8266,7 +9322,7 @@ async function startDaemon(opts = {}) {
|
|
|
8266
9322
|
}
|
|
8267
9323
|
if (restApp) await restApp.close().catch(() => {
|
|
8268
9324
|
});
|
|
8269
|
-
await
|
|
9325
|
+
await import_node_fs26.promises.unlink(pidPath).catch(() => {
|
|
8270
9326
|
});
|
|
8271
9327
|
throw new Error(
|
|
8272
9328
|
`neatd: failed to bind REST on port ${restPort} \u2014 ${err.message}`
|
|
@@ -8392,7 +9448,7 @@ async function startDaemon(opts = {}) {
|
|
|
8392
9448
|
});
|
|
8393
9449
|
if (otlpApp) await otlpApp.close().catch(() => {
|
|
8394
9450
|
});
|
|
8395
|
-
await
|
|
9451
|
+
await import_node_fs26.promises.unlink(pidPath).catch(() => {
|
|
8396
9452
|
});
|
|
8397
9453
|
throw new Error(
|
|
8398
9454
|
`neatd: failed to bind OTLP on port ${otlpPort} \u2014 ${err.message}`
|
|
@@ -8427,7 +9483,7 @@ async function startDaemon(opts = {}) {
|
|
|
8427
9483
|
});
|
|
8428
9484
|
if (otlpApp) await otlpApp.close().catch(() => {
|
|
8429
9485
|
});
|
|
8430
|
-
await
|
|
9486
|
+
await import_node_fs26.promises.unlink(pidPath).catch(() => {
|
|
8431
9487
|
});
|
|
8432
9488
|
throw new Error(
|
|
8433
9489
|
`neatd: failed to write daemon.json for "${singleProject}" \u2014 ${err.message}`
|
|
@@ -8474,9 +9530,9 @@ async function startDaemon(opts = {}) {
|
|
|
8474
9530
|
let registryWatcher = null;
|
|
8475
9531
|
let reloadTimer = null;
|
|
8476
9532
|
if (!singleProject) try {
|
|
8477
|
-
const regDir =
|
|
8478
|
-
const regBase =
|
|
8479
|
-
registryWatcher = (0,
|
|
9533
|
+
const regDir = import_node_path45.default.dirname(regPath);
|
|
9534
|
+
const regBase = import_node_path45.default.basename(regPath);
|
|
9535
|
+
registryWatcher = (0, import_node_fs26.watch)(regDir, (_eventType, filename) => {
|
|
8480
9536
|
if (filename !== null && filename !== regBase) return;
|
|
8481
9537
|
if (reloadTimer) clearTimeout(reloadTimer);
|
|
8482
9538
|
reloadTimer = setTimeout(() => {
|
|
@@ -8525,7 +9581,7 @@ async function startDaemon(opts = {}) {
|
|
|
8525
9581
|
if (daemonRecord) {
|
|
8526
9582
|
await clearDaemonRecord(daemonRecord, home);
|
|
8527
9583
|
}
|
|
8528
|
-
await
|
|
9584
|
+
await import_node_fs26.promises.unlink(pidPath).catch(() => {
|
|
8529
9585
|
});
|
|
8530
9586
|
};
|
|
8531
9587
|
return {
|