@neat.is/core 0.4.26-dev.20260702 → 0.4.26-dev.20260703
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-2LNICOVU.js} +30 -3
- package/dist/chunk-2LNICOVU.js.map +1 -0
- package/dist/{chunk-XOOCA5T7.js → chunk-4OR4RQEO.js} +13 -1
- package/dist/chunk-4OR4RQEO.js.map +1 -0
- package/dist/{chunk-GAFTW2OX.js → chunk-C5NCCKPZ.js} +2 -2
- package/dist/{chunk-IABNGQT2.js → chunk-S7TDPQDD.js} +15 -3
- package/dist/chunk-S7TDPQDD.js.map +1 -0
- package/dist/{chunk-O25KZNZK.js → chunk-WZYH5DVG.js} +957 -238
- package/dist/chunk-WZYH5DVG.js.map +1 -0
- package/dist/cli.cjs +1149 -406
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +5 -5
- package/dist/index.cjs +1011 -269
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +4 -4
- package/dist/neatd.cjs +1112 -278
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +3 -3
- package/dist/{otel-grpc-QYHUJ4OY.js → otel-grpc-LFYZDSK3.js} +3 -3
- package/dist/server.cjs +954 -211
- 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-C5NCCKPZ.js.map} +0 -0
- /package/dist/{otel-grpc-QYHUJ4OY.js.map → otel-grpc-LFYZDSK3.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 path45 = (req.url.split("?")[0] ?? "").replace(/\/+$/, "");
|
|
61
61
|
for (const suffix of suffixes) {
|
|
62
|
-
if (
|
|
62
|
+
if (path45 === suffix || path45.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_path41.default.dirname((0, import_node_url.fileURLToPath)(importMetaUrl));
|
|
193
|
+
return import_node_path41.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_path41, 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_path41 = __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,13 @@ 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
|
+
}
|
|
338
347
|
function parseOtlpRequest(body) {
|
|
339
348
|
const out = [];
|
|
340
349
|
for (const rs of body.resourceSpans ?? []) {
|
|
@@ -361,6 +370,10 @@ function parseOtlpRequest(body) {
|
|
|
361
370
|
attributes: attrs,
|
|
362
371
|
dbSystem: typeof attrs["db.system"] === "string" ? attrs["db.system"] : void 0,
|
|
363
372
|
dbName: typeof attrs["db.name"] === "string" ? attrs["db.name"] : void 0,
|
|
373
|
+
messagingSystem: typeof attrs["messaging.system"] === "string" ? attrs["messaging.system"] : void 0,
|
|
374
|
+
messagingDestination: messagingDestinationOf(attrs),
|
|
375
|
+
graphqlOperationName: typeof attrs["graphql.operation.name"] === "string" && attrs["graphql.operation.name"].length > 0 ? attrs["graphql.operation.name"] : void 0,
|
|
376
|
+
graphqlOperationType: typeof attrs["graphql.operation.type"] === "string" && attrs["graphql.operation.type"].length > 0 ? attrs["graphql.operation.type"] : void 0,
|
|
364
377
|
statusCode: span.status?.code,
|
|
365
378
|
errorMessage: span.status?.message,
|
|
366
379
|
exception: extractExceptionFromEvents(span.events)
|
|
@@ -372,10 +385,10 @@ function parseOtlpRequest(body) {
|
|
|
372
385
|
return out;
|
|
373
386
|
}
|
|
374
387
|
function loadProtoRoot() {
|
|
375
|
-
const here =
|
|
376
|
-
const protoRoot =
|
|
388
|
+
const here = import_node_path42.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
|
|
389
|
+
const protoRoot = import_node_path42.default.resolve(here, "..", "proto");
|
|
377
390
|
const root = new import_protobufjs.default.Root();
|
|
378
|
-
root.resolvePath = (_origin, target) =>
|
|
391
|
+
root.resolvePath = (_origin, target) => import_node_path42.default.resolve(protoRoot, target);
|
|
379
392
|
root.loadSync(
|
|
380
393
|
"opentelemetry/proto/collector/trace/v1/trace_service.proto",
|
|
381
394
|
{ keepCase: true }
|
|
@@ -420,7 +433,21 @@ async function buildOtelReceiver(opts) {
|
|
|
420
433
|
logger: false,
|
|
421
434
|
bodyLimit: opts.bodyLimit ?? 16 * 1024 * 1024
|
|
422
435
|
});
|
|
423
|
-
|
|
436
|
+
const REJECT_WARN_INTERVAL_MS = 6e4;
|
|
437
|
+
let lastRejectWarnAt = 0;
|
|
438
|
+
const warnRejectedOtlp = () => {
|
|
439
|
+
const now = Date.now();
|
|
440
|
+
if (now - lastRejectWarnAt < REJECT_WARN_INTERVAL_MS) return;
|
|
441
|
+
lastRejectWarnAt = now;
|
|
442
|
+
console.warn(
|
|
443
|
+
"[neatd] rejecting OTLP spans on /v1/traces \u2014 missing or invalid bearer token (set NEAT_OTEL_TOKEN on the instrumented app)"
|
|
444
|
+
);
|
|
445
|
+
};
|
|
446
|
+
mountBearerAuth(app, {
|
|
447
|
+
token: opts.authToken,
|
|
448
|
+
trustProxy: opts.trustProxy,
|
|
449
|
+
onReject: warnRejectedOtlp
|
|
450
|
+
});
|
|
424
451
|
const queue = [];
|
|
425
452
|
let draining = false;
|
|
426
453
|
let drainPromise = Promise.resolve();
|
|
@@ -597,12 +624,12 @@ function logSpanHandler(span) {
|
|
|
597
624
|
`otel: ${span.service} ${span.name} parent=${parent} status=${status2}${db}`
|
|
598
625
|
);
|
|
599
626
|
}
|
|
600
|
-
var
|
|
627
|
+
var import_node_path42, 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
628
|
var init_otel = __esm({
|
|
602
629
|
"src/otel.ts"() {
|
|
603
630
|
"use strict";
|
|
604
631
|
init_cjs_shims();
|
|
605
|
-
|
|
632
|
+
import_node_path42 = __toESM(require("path"), 1);
|
|
606
633
|
import_node_url2 = require("url");
|
|
607
634
|
import_fastify2 = __toESM(require("fastify"), 1);
|
|
608
635
|
import_protobufjs = __toESM(require("protobufjs"), 1);
|
|
@@ -1192,19 +1219,19 @@ function confidenceFromMix(edges, now = Date.now()) {
|
|
|
1192
1219
|
function longestIncomingWalk(graph, start, maxDepth) {
|
|
1193
1220
|
let best = { path: [start], edges: [] };
|
|
1194
1221
|
const visited = /* @__PURE__ */ new Set([start]);
|
|
1195
|
-
function step(node,
|
|
1196
|
-
if (
|
|
1197
|
-
best = { path: [...
|
|
1222
|
+
function step(node, path45, edges) {
|
|
1223
|
+
if (path45.length > best.path.length) {
|
|
1224
|
+
best = { path: [...path45], edges: [...edges] };
|
|
1198
1225
|
}
|
|
1199
|
-
if (
|
|
1226
|
+
if (path45.length - 1 >= maxDepth) return;
|
|
1200
1227
|
const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
|
|
1201
1228
|
for (const [srcId, edge] of incoming) {
|
|
1202
1229
|
if (visited.has(srcId)) continue;
|
|
1203
1230
|
visited.add(srcId);
|
|
1204
|
-
|
|
1231
|
+
path45.push(srcId);
|
|
1205
1232
|
edges.push(edge);
|
|
1206
|
-
step(srcId,
|
|
1207
|
-
|
|
1233
|
+
step(srcId, path45, edges);
|
|
1234
|
+
path45.pop();
|
|
1208
1235
|
edges.pop();
|
|
1209
1236
|
visited.delete(srcId);
|
|
1210
1237
|
}
|
|
@@ -1212,14 +1239,14 @@ function longestIncomingWalk(graph, start, maxDepth) {
|
|
|
1212
1239
|
step(start, [start], []);
|
|
1213
1240
|
return best;
|
|
1214
1241
|
}
|
|
1215
|
-
function databaseRootCauseShape(graph, origin,
|
|
1242
|
+
function databaseRootCauseShape(graph, origin, walk3) {
|
|
1216
1243
|
const targetDb = origin;
|
|
1217
1244
|
const candidatePairs = compatPairs().filter((p) => p.engine === targetDb.engine);
|
|
1218
1245
|
if (candidatePairs.length === 0) return null;
|
|
1219
|
-
for (const id of
|
|
1246
|
+
for (const id of walk3.path) {
|
|
1220
1247
|
const owner = resolveOwningService(graph, id);
|
|
1221
1248
|
if (!owner) continue;
|
|
1222
|
-
const { id:
|
|
1249
|
+
const { id: serviceId4, svc } = owner;
|
|
1223
1250
|
const deps = svc.dependencies ?? {};
|
|
1224
1251
|
for (const pair of candidatePairs) {
|
|
1225
1252
|
const declared = deps[pair.driver];
|
|
@@ -1232,7 +1259,7 @@ function databaseRootCauseShape(graph, origin, walk) {
|
|
|
1232
1259
|
);
|
|
1233
1260
|
if (!result.compatible) {
|
|
1234
1261
|
return {
|
|
1235
|
-
rootCauseNode:
|
|
1262
|
+
rootCauseNode: serviceId4,
|
|
1236
1263
|
rootCauseReason: result.reason ?? "incompatible driver",
|
|
1237
1264
|
...result.minDriverVersion ? {
|
|
1238
1265
|
fixRecommendation: `Upgrade ${svc.name} ${pair.driver} driver to >= ${result.minDriverVersion}`
|
|
@@ -1243,11 +1270,11 @@ function databaseRootCauseShape(graph, origin, walk) {
|
|
|
1243
1270
|
}
|
|
1244
1271
|
return null;
|
|
1245
1272
|
}
|
|
1246
|
-
function serviceRootCauseShape(graph, _origin,
|
|
1247
|
-
for (const id of
|
|
1273
|
+
function serviceRootCauseShape(graph, _origin, walk3) {
|
|
1274
|
+
for (const id of walk3.path) {
|
|
1248
1275
|
const owner = resolveOwningService(graph, id);
|
|
1249
1276
|
if (!owner) continue;
|
|
1250
|
-
const { id:
|
|
1277
|
+
const { id: serviceId4, svc } = owner;
|
|
1251
1278
|
const deps = svc.dependencies ?? {};
|
|
1252
1279
|
const serviceNodeEngine = svc.nodeEngine;
|
|
1253
1280
|
for (const constraint of nodeEngineConstraints()) {
|
|
@@ -1256,7 +1283,7 @@ function serviceRootCauseShape(graph, _origin, walk) {
|
|
|
1256
1283
|
const result = checkNodeEngineConstraint(constraint, declared, serviceNodeEngine);
|
|
1257
1284
|
if (!result.compatible && result.reason) {
|
|
1258
1285
|
return {
|
|
1259
|
-
rootCauseNode:
|
|
1286
|
+
rootCauseNode: serviceId4,
|
|
1260
1287
|
rootCauseReason: result.reason,
|
|
1261
1288
|
...result.requiredNodeVersion ? {
|
|
1262
1289
|
fixRecommendation: `Bump ${svc.name}'s engines.node to >= ${result.requiredNodeVersion}`
|
|
@@ -1271,7 +1298,7 @@ function serviceRootCauseShape(graph, _origin, walk) {
|
|
|
1271
1298
|
const result = checkPackageConflict(conflict, declared, requiredDeclared);
|
|
1272
1299
|
if (!result.compatible && result.reason) {
|
|
1273
1300
|
return {
|
|
1274
|
-
rootCauseNode:
|
|
1301
|
+
rootCauseNode: serviceId4,
|
|
1275
1302
|
rootCauseReason: result.reason,
|
|
1276
1303
|
fixRecommendation: `Upgrade ${svc.name}'s ${conflict.requires.name} to >= ${conflict.requires.minVersion}`
|
|
1277
1304
|
};
|
|
@@ -1280,10 +1307,10 @@ function serviceRootCauseShape(graph, _origin, walk) {
|
|
|
1280
1307
|
}
|
|
1281
1308
|
return null;
|
|
1282
1309
|
}
|
|
1283
|
-
function fileRootCauseShape(graph, origin,
|
|
1310
|
+
function fileRootCauseShape(graph, origin, walk3) {
|
|
1284
1311
|
const owner = resolveOwningService(graph, origin.id);
|
|
1285
1312
|
if (!owner) return null;
|
|
1286
|
-
return serviceRootCauseShape(graph, owner.svc,
|
|
1313
|
+
return serviceRootCauseShape(graph, owner.svc, walk3);
|
|
1287
1314
|
}
|
|
1288
1315
|
var rootCauseShapes = {
|
|
1289
1316
|
[import_types.NodeType.DatabaseNode]: databaseRootCauseShape,
|
|
@@ -1295,16 +1322,16 @@ function getRootCause(graph, errorNodeId, errorEvent, incidents) {
|
|
|
1295
1322
|
const origin = graph.getNodeAttributes(errorNodeId);
|
|
1296
1323
|
const shape = rootCauseShapes[origin.type];
|
|
1297
1324
|
if (shape) {
|
|
1298
|
-
const
|
|
1299
|
-
const match = shape(graph, origin,
|
|
1325
|
+
const walk3 = longestIncomingWalk(graph, errorNodeId, ROOT_CAUSE_MAX_DEPTH);
|
|
1326
|
+
const match = shape(graph, origin, walk3);
|
|
1300
1327
|
if (match) {
|
|
1301
1328
|
const reason = errorEvent ? `${match.rootCauseReason} (observed error: ${errorEvent.errorMessage})` : match.rootCauseReason;
|
|
1302
1329
|
return import_types.RootCauseResultSchema.parse({
|
|
1303
1330
|
rootCauseNode: match.rootCauseNode,
|
|
1304
1331
|
rootCauseReason: reason,
|
|
1305
|
-
traversalPath:
|
|
1306
|
-
edgeProvenances:
|
|
1307
|
-
confidence: confidenceFromMix(
|
|
1332
|
+
traversalPath: walk3.path,
|
|
1333
|
+
edgeProvenances: walk3.edges.map((e) => e.provenance),
|
|
1334
|
+
confidence: confidenceFromMix(walk3.edges),
|
|
1308
1335
|
fixRecommendation: match.fixRecommendation
|
|
1309
1336
|
});
|
|
1310
1337
|
}
|
|
@@ -1362,9 +1389,9 @@ function rootCauseFromIncidents(nodeId, incidents, errorEvent) {
|
|
|
1362
1389
|
function isFailingCallEdge(e) {
|
|
1363
1390
|
return e.type === import_types.EdgeType.CALLS && (e.signal?.errorCount ?? 0) > 0;
|
|
1364
1391
|
}
|
|
1365
|
-
function callSourcesForService(graph,
|
|
1366
|
-
const ids = [
|
|
1367
|
-
for (const edgeId of graph.outboundEdges(
|
|
1392
|
+
function callSourcesForService(graph, serviceId4) {
|
|
1393
|
+
const ids = [serviceId4];
|
|
1394
|
+
for (const edgeId of graph.outboundEdges(serviceId4)) {
|
|
1368
1395
|
const e = graph.getEdgeAttributes(edgeId);
|
|
1369
1396
|
if (e.type !== import_types.EdgeType.CONTAINS) continue;
|
|
1370
1397
|
const tgt = graph.getNodeAttributes(e.target);
|
|
@@ -1381,9 +1408,9 @@ function failingCallDominates(e, id, curEdge, curId) {
|
|
|
1381
1408
|
}
|
|
1382
1409
|
return id < curId;
|
|
1383
1410
|
}
|
|
1384
|
-
function dominantFailingCall(graph,
|
|
1411
|
+
function dominantFailingCall(graph, serviceId4, visited) {
|
|
1385
1412
|
let best = null;
|
|
1386
|
-
for (const src of callSourcesForService(graph,
|
|
1413
|
+
for (const src of callSourcesForService(graph, serviceId4)) {
|
|
1387
1414
|
for (const edgeId of graph.outboundEdges(src)) {
|
|
1388
1415
|
const e = graph.getEdgeAttributes(edgeId);
|
|
1389
1416
|
if (!isFailingCallEdge(e)) continue;
|
|
@@ -1398,26 +1425,26 @@ function dominantFailingCall(graph, serviceId3, visited) {
|
|
|
1398
1425
|
return best;
|
|
1399
1426
|
}
|
|
1400
1427
|
function followFailingCallChain(graph, originServiceId, maxDepth) {
|
|
1401
|
-
const
|
|
1428
|
+
const path45 = [originServiceId];
|
|
1402
1429
|
const edges = [];
|
|
1403
1430
|
const visited = /* @__PURE__ */ new Set([originServiceId]);
|
|
1404
1431
|
let current = originServiceId;
|
|
1405
1432
|
for (let depth = 0; depth < maxDepth; depth++) {
|
|
1406
1433
|
const hop = dominantFailingCall(graph, current, visited);
|
|
1407
1434
|
if (!hop) break;
|
|
1408
|
-
|
|
1435
|
+
path45.push(hop.nextService);
|
|
1409
1436
|
edges.push(hop.edge);
|
|
1410
1437
|
visited.add(hop.nextService);
|
|
1411
1438
|
current = hop.nextService;
|
|
1412
1439
|
}
|
|
1413
1440
|
if (edges.length === 0) return null;
|
|
1414
|
-
return { path:
|
|
1441
|
+
return { path: path45, edges, culprit: current };
|
|
1415
1442
|
}
|
|
1416
1443
|
function crossServiceRootCause(graph, originId, incidents, errorEvent) {
|
|
1417
1444
|
const chain = followFailingCallChain(graph, originId, ROOT_CAUSE_MAX_DEPTH);
|
|
1418
1445
|
if (!chain) return null;
|
|
1419
1446
|
const culprit = chain.culprit;
|
|
1420
|
-
const
|
|
1447
|
+
const path45 = [...chain.path];
|
|
1421
1448
|
const edgeProvenances = chain.edges.map((e) => e.provenance);
|
|
1422
1449
|
const baseConfidence = confidenceFromMix(chain.edges);
|
|
1423
1450
|
const confidence = Math.max(0, Math.min(1, baseConfidence * INCIDENT_ROOT_CAUSE_CONFIDENCE));
|
|
@@ -1425,14 +1452,14 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
|
|
|
1425
1452
|
if (loc) {
|
|
1426
1453
|
let rootCauseNode = culprit;
|
|
1427
1454
|
if (loc.fileNode) {
|
|
1428
|
-
|
|
1455
|
+
path45.push(loc.fileNode);
|
|
1429
1456
|
edgeProvenances.push(import_types.Provenance.OBSERVED);
|
|
1430
1457
|
rootCauseNode = loc.fileNode;
|
|
1431
1458
|
}
|
|
1432
1459
|
return import_types.RootCauseResultSchema.parse({
|
|
1433
1460
|
rootCauseNode,
|
|
1434
1461
|
rootCauseReason: loc.rootCauseReason,
|
|
1435
|
-
traversalPath:
|
|
1462
|
+
traversalPath: path45,
|
|
1436
1463
|
edgeProvenances,
|
|
1437
1464
|
confidence,
|
|
1438
1465
|
...loc.fixRecommendation ? { fixRecommendation: loc.fixRecommendation } : {}
|
|
@@ -1444,7 +1471,7 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
|
|
|
1444
1471
|
return import_types.RootCauseResultSchema.parse({
|
|
1445
1472
|
rootCauseNode: culprit,
|
|
1446
1473
|
rootCauseReason: `${culpritName} is failing downstream calls (${errs} observed error${errs === 1 ? "" : "s"})`,
|
|
1447
|
-
traversalPath:
|
|
1474
|
+
traversalPath: path45,
|
|
1448
1475
|
edgeProvenances,
|
|
1449
1476
|
confidence,
|
|
1450
1477
|
fixRecommendation: `Inspect ${culpritName}'s failing handler`
|
|
@@ -2368,10 +2395,48 @@ var STITCH_EDGE_TYPES = /* @__PURE__ */ new Set([
|
|
|
2368
2395
|
]);
|
|
2369
2396
|
var WIRE_SPAN_KIND_CLIENT = 3;
|
|
2370
2397
|
var WIRE_SPAN_KIND_PRODUCER = 4;
|
|
2398
|
+
var WIRE_SPAN_KIND_CONSUMER = 5;
|
|
2371
2399
|
function spanMintsObservedEdge(kind) {
|
|
2372
2400
|
if (kind === void 0 || kind === 0) return true;
|
|
2373
2401
|
return kind === WIRE_SPAN_KIND_CLIENT || kind === WIRE_SPAN_KIND_PRODUCER;
|
|
2374
2402
|
}
|
|
2403
|
+
function spanMintsMessagingEdge(kind) {
|
|
2404
|
+
return kind === WIRE_SPAN_KIND_PRODUCER || kind === WIRE_SPAN_KIND_CONSUMER;
|
|
2405
|
+
}
|
|
2406
|
+
function spanServesGraphqlOperation(kind) {
|
|
2407
|
+
return kind !== WIRE_SPAN_KIND_CLIENT && kind !== WIRE_SPAN_KIND_PRODUCER && kind !== WIRE_SPAN_KIND_CONSUMER;
|
|
2408
|
+
}
|
|
2409
|
+
function ensureGraphqlOperationNode(graph, serviceName, operationType, operationName) {
|
|
2410
|
+
const id = (0, import_types3.graphqlOperationId)(serviceName, operationType, operationName);
|
|
2411
|
+
if (graph.hasNode(id)) return id;
|
|
2412
|
+
const node = {
|
|
2413
|
+
id,
|
|
2414
|
+
type: import_types3.NodeType.GraphQLOperationNode,
|
|
2415
|
+
name: operationName,
|
|
2416
|
+
service: serviceName,
|
|
2417
|
+
operationType: operationType.toLowerCase(),
|
|
2418
|
+
operationName,
|
|
2419
|
+
discoveredVia: "otel"
|
|
2420
|
+
};
|
|
2421
|
+
graph.addNode(id, node);
|
|
2422
|
+
return id;
|
|
2423
|
+
}
|
|
2424
|
+
function messagingDestinationKind(system) {
|
|
2425
|
+
return `${system}-topic`;
|
|
2426
|
+
}
|
|
2427
|
+
function ensureMessagingDestinationNode(graph, system, destination) {
|
|
2428
|
+
const id = (0, import_types3.infraId)(messagingDestinationKind(system), destination);
|
|
2429
|
+
if (graph.hasNode(id)) return id;
|
|
2430
|
+
const node = {
|
|
2431
|
+
id,
|
|
2432
|
+
type: import_types3.NodeType.InfraNode,
|
|
2433
|
+
name: destination,
|
|
2434
|
+
provider: "self",
|
|
2435
|
+
kind: messagingDestinationKind(system)
|
|
2436
|
+
};
|
|
2437
|
+
graph.addNode(id, node);
|
|
2438
|
+
return id;
|
|
2439
|
+
}
|
|
2375
2440
|
var PARENT_SPAN_CACHE_SIZE = 1e4;
|
|
2376
2441
|
var PARENT_SPAN_CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
2377
2442
|
var parentSpanCache = /* @__PURE__ */ new Map();
|
|
@@ -2465,6 +2530,21 @@ function ensureDatabaseNode(graph, host, engine) {
|
|
|
2465
2530
|
graph.addNode(id, node);
|
|
2466
2531
|
return id;
|
|
2467
2532
|
}
|
|
2533
|
+
function ensureLocalDatabaseNode(graph, serviceName, name, engine) {
|
|
2534
|
+
const id = (0, import_types3.localDatabaseId)(serviceName, name);
|
|
2535
|
+
if (graph.hasNode(id)) return id;
|
|
2536
|
+
const node = {
|
|
2537
|
+
id,
|
|
2538
|
+
type: import_types3.NodeType.DatabaseNode,
|
|
2539
|
+
name,
|
|
2540
|
+
engine,
|
|
2541
|
+
engineVersion: "unknown",
|
|
2542
|
+
compatibleDrivers: [],
|
|
2543
|
+
discoveredVia: "otel"
|
|
2544
|
+
};
|
|
2545
|
+
graph.addNode(id, node);
|
|
2546
|
+
return id;
|
|
2547
|
+
}
|
|
2468
2548
|
function ensureFrontierNode(graph, host, ts) {
|
|
2469
2549
|
const id = frontierIdFor(host);
|
|
2470
2550
|
if (graph.hasNode(id)) {
|
|
@@ -2716,10 +2796,21 @@ async function handleSpan(ctx, span) {
|
|
|
2716
2796
|
let affectedNode = sourceId;
|
|
2717
2797
|
const mintsFromCallerSide = spanMintsObservedEdge(span.kind);
|
|
2718
2798
|
if (span.dbSystem) {
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2799
|
+
if (mintsFromCallerSide) {
|
|
2800
|
+
const host = pickAddress(span);
|
|
2801
|
+
let targetId;
|
|
2802
|
+
if (host) {
|
|
2803
|
+
ensureDatabaseNode(ctx.graph, host, span.dbSystem);
|
|
2804
|
+
targetId = (0, import_types3.databaseId)(host);
|
|
2805
|
+
} else {
|
|
2806
|
+
const localName = span.dbName ?? span.dbSystem;
|
|
2807
|
+
targetId = ensureLocalDatabaseNode(
|
|
2808
|
+
ctx.graph,
|
|
2809
|
+
span.service,
|
|
2810
|
+
localName,
|
|
2811
|
+
span.dbSystem
|
|
2812
|
+
);
|
|
2813
|
+
}
|
|
2723
2814
|
const result = upsertObservedEdge(
|
|
2724
2815
|
ctx.graph,
|
|
2725
2816
|
import_types3.EdgeType.CONNECTS_TO,
|
|
@@ -2731,6 +2822,40 @@ async function handleSpan(ctx, span) {
|
|
|
2731
2822
|
);
|
|
2732
2823
|
if (result) affectedNode = targetId;
|
|
2733
2824
|
}
|
|
2825
|
+
} else if (span.messagingSystem && span.messagingDestination && spanMintsMessagingEdge(span.kind)) {
|
|
2826
|
+
const targetId = ensureMessagingDestinationNode(
|
|
2827
|
+
ctx.graph,
|
|
2828
|
+
span.messagingSystem,
|
|
2829
|
+
span.messagingDestination
|
|
2830
|
+
);
|
|
2831
|
+
const edgeType = span.kind === WIRE_SPAN_KIND_CONSUMER ? import_types3.EdgeType.CONSUMES_FROM : import_types3.EdgeType.PUBLISHES_TO;
|
|
2832
|
+
const result = upsertObservedEdge(
|
|
2833
|
+
ctx.graph,
|
|
2834
|
+
edgeType,
|
|
2835
|
+
observedSource(),
|
|
2836
|
+
targetId,
|
|
2837
|
+
ts,
|
|
2838
|
+
isError,
|
|
2839
|
+
callSiteEvidence
|
|
2840
|
+
);
|
|
2841
|
+
if (result) affectedNode = targetId;
|
|
2842
|
+
} else if (span.graphqlOperationName && span.graphqlOperationType && spanServesGraphqlOperation(span.kind)) {
|
|
2843
|
+
const targetId = ensureGraphqlOperationNode(
|
|
2844
|
+
ctx.graph,
|
|
2845
|
+
span.service,
|
|
2846
|
+
span.graphqlOperationType,
|
|
2847
|
+
span.graphqlOperationName
|
|
2848
|
+
);
|
|
2849
|
+
const result = upsertObservedEdge(
|
|
2850
|
+
ctx.graph,
|
|
2851
|
+
import_types3.EdgeType.CONTAINS,
|
|
2852
|
+
observedSource(),
|
|
2853
|
+
targetId,
|
|
2854
|
+
ts,
|
|
2855
|
+
isError,
|
|
2856
|
+
callSiteEvidence
|
|
2857
|
+
);
|
|
2858
|
+
if (result) affectedNode = targetId;
|
|
2734
2859
|
} else {
|
|
2735
2860
|
const host = pickAddress(span);
|
|
2736
2861
|
let resolvedViaAddress = false;
|
|
@@ -2840,29 +2965,29 @@ function promoteFrontierNodes(graph, opts = {}) {
|
|
|
2840
2965
|
toPromote.push({ frontierId: id, serviceId: target });
|
|
2841
2966
|
});
|
|
2842
2967
|
let promoted = 0;
|
|
2843
|
-
for (const { frontierId: frontierId2, serviceId:
|
|
2968
|
+
for (const { frontierId: frontierId2, serviceId: serviceId4 } of toPromote) {
|
|
2844
2969
|
if (opts.policies && opts.policies.length > 0 && opts.policyCtx) {
|
|
2845
2970
|
const gate = canPromoteFrontier(graph, frontierId2, opts.policies, opts.policyCtx);
|
|
2846
2971
|
if (!gate.allowed) {
|
|
2847
2972
|
continue;
|
|
2848
2973
|
}
|
|
2849
2974
|
}
|
|
2850
|
-
rewireFrontierEdges(graph, frontierId2,
|
|
2975
|
+
rewireFrontierEdges(graph, frontierId2, serviceId4);
|
|
2851
2976
|
graph.dropNode(frontierId2);
|
|
2852
2977
|
promoted++;
|
|
2853
2978
|
}
|
|
2854
2979
|
return promoted;
|
|
2855
2980
|
}
|
|
2856
|
-
function rewireFrontierEdges(graph, frontierId2,
|
|
2981
|
+
function rewireFrontierEdges(graph, frontierId2, serviceId4) {
|
|
2857
2982
|
const inbound = [...graph.inboundEdges(frontierId2)];
|
|
2858
2983
|
const outbound = [...graph.outboundEdges(frontierId2)];
|
|
2859
2984
|
for (const edgeId of inbound) {
|
|
2860
2985
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
2861
|
-
rebuildEdge(graph, edge, edge.source,
|
|
2986
|
+
rebuildEdge(graph, edge, edge.source, serviceId4, edgeId);
|
|
2862
2987
|
}
|
|
2863
2988
|
for (const edgeId of outbound) {
|
|
2864
2989
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
2865
|
-
rebuildEdge(graph, edge,
|
|
2990
|
+
rebuildEdge(graph, edge, serviceId4, edge.target, edgeId);
|
|
2866
2991
|
}
|
|
2867
2992
|
}
|
|
2868
2993
|
function rebuildEdge(graph, edge, newSource, newTarget, oldEdgeId) {
|
|
@@ -3624,9 +3749,9 @@ var K8S_KINDS_WITH_HOSTNAMES = /* @__PURE__ */ new Set([
|
|
|
3624
3749
|
"StatefulSet",
|
|
3625
3750
|
"DaemonSet"
|
|
3626
3751
|
]);
|
|
3627
|
-
function addAliases(graph,
|
|
3628
|
-
if (!graph.hasNode(
|
|
3629
|
-
const node = graph.getNodeAttributes(
|
|
3752
|
+
function addAliases(graph, serviceId4, candidates) {
|
|
3753
|
+
if (!graph.hasNode(serviceId4)) return;
|
|
3754
|
+
const node = graph.getNodeAttributes(serviceId4);
|
|
3630
3755
|
if (node.type !== import_types6.NodeType.ServiceNode) return;
|
|
3631
3756
|
const set = new Set(node.aliases ?? []);
|
|
3632
3757
|
for (const c of candidates) {
|
|
@@ -3636,7 +3761,7 @@ function addAliases(graph, serviceId3, candidates) {
|
|
|
3636
3761
|
}
|
|
3637
3762
|
if (set.size === 0) return;
|
|
3638
3763
|
const updated = { ...node, aliases: [...set].sort() };
|
|
3639
|
-
graph.replaceNodeAttributes(
|
|
3764
|
+
graph.replaceNodeAttributes(serviceId4, updated);
|
|
3640
3765
|
}
|
|
3641
3766
|
function indexServicesByName(services) {
|
|
3642
3767
|
const map = /* @__PURE__ */ new Map();
|
|
@@ -3669,12 +3794,12 @@ async function collectComposeAliases(graph, scanPath, serviceIndex) {
|
|
|
3669
3794
|
}
|
|
3670
3795
|
if (!compose?.services) return;
|
|
3671
3796
|
for (const [composeName, svc] of Object.entries(compose.services)) {
|
|
3672
|
-
const
|
|
3673
|
-
if (!
|
|
3797
|
+
const serviceId4 = serviceIndex.get(composeName);
|
|
3798
|
+
if (!serviceId4) continue;
|
|
3674
3799
|
const aliases = /* @__PURE__ */ new Set([composeName]);
|
|
3675
3800
|
if (svc.container_name) aliases.add(svc.container_name);
|
|
3676
3801
|
if (svc.hostname) aliases.add(svc.hostname);
|
|
3677
|
-
addAliases(graph,
|
|
3802
|
+
addAliases(graph, serviceId4, aliases);
|
|
3678
3803
|
}
|
|
3679
3804
|
}
|
|
3680
3805
|
var LABEL_KEYS = /* @__PURE__ */ new Set([
|
|
@@ -3787,16 +3912,28 @@ init_cjs_shims();
|
|
|
3787
3912
|
var import_node_fs10 = require("fs");
|
|
3788
3913
|
var import_node_path10 = __toESM(require("path"), 1);
|
|
3789
3914
|
var import_types7 = require("@neat.is/types");
|
|
3915
|
+
function buildServiceHostIndex(services) {
|
|
3916
|
+
const knownHosts = /* @__PURE__ */ new Set();
|
|
3917
|
+
const hostToNodeId = /* @__PURE__ */ new Map();
|
|
3918
|
+
for (const service of services) {
|
|
3919
|
+
const base = import_node_path10.default.basename(service.dir);
|
|
3920
|
+
knownHosts.add(base);
|
|
3921
|
+
knownHosts.add(service.pkg.name);
|
|
3922
|
+
hostToNodeId.set(base, service.node.id);
|
|
3923
|
+
hostToNodeId.set(service.pkg.name, service.node.id);
|
|
3924
|
+
}
|
|
3925
|
+
return { knownHosts, hostToNodeId };
|
|
3926
|
+
}
|
|
3790
3927
|
async function walkSourceFiles(dir) {
|
|
3791
3928
|
const out = [];
|
|
3792
|
-
async function
|
|
3929
|
+
async function walk3(current) {
|
|
3793
3930
|
const entries = await import_node_fs10.promises.readdir(current, { withFileTypes: true }).catch(() => []);
|
|
3794
3931
|
for (const entry of entries) {
|
|
3795
3932
|
const full = import_node_path10.default.join(current, entry.name);
|
|
3796
3933
|
if (entry.isDirectory()) {
|
|
3797
3934
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
3798
3935
|
if (await isPythonVenvDir(full)) continue;
|
|
3799
|
-
await
|
|
3936
|
+
await walk3(full);
|
|
3800
3937
|
} 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
3938
|
// would attribute our instrumentation imports to the user's service.
|
|
3802
3939
|
!isNeatAuthoredSourceFile(entry.name)) {
|
|
@@ -3804,7 +3941,7 @@ async function walkSourceFiles(dir) {
|
|
|
3804
3941
|
}
|
|
3805
3942
|
}
|
|
3806
3943
|
}
|
|
3807
|
-
await
|
|
3944
|
+
await walk3(dir);
|
|
3808
3945
|
return out;
|
|
3809
3946
|
}
|
|
3810
3947
|
async function loadSourceFiles(dir) {
|
|
@@ -4898,20 +5035,20 @@ var import_node_path21 = __toESM(require("path"), 1);
|
|
|
4898
5035
|
var import_types10 = require("@neat.is/types");
|
|
4899
5036
|
async function walkConfigFiles(dir) {
|
|
4900
5037
|
const out = [];
|
|
4901
|
-
async function
|
|
5038
|
+
async function walk3(current) {
|
|
4902
5039
|
const entries = await import_node_fs14.promises.readdir(current, { withFileTypes: true });
|
|
4903
5040
|
for (const entry of entries) {
|
|
4904
5041
|
const full = import_node_path21.default.join(current, entry.name);
|
|
4905
5042
|
if (entry.isDirectory()) {
|
|
4906
5043
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
4907
5044
|
if (await isPythonVenvDir(full)) continue;
|
|
4908
|
-
await
|
|
5045
|
+
await walk3(full);
|
|
4909
5046
|
} else if (entry.isFile() && isConfigFile(entry.name).match) {
|
|
4910
5047
|
out.push(full);
|
|
4911
5048
|
}
|
|
4912
5049
|
}
|
|
4913
5050
|
}
|
|
4914
|
-
await
|
|
5051
|
+
await walk3(dir);
|
|
4915
5052
|
return out;
|
|
4916
5053
|
}
|
|
4917
5054
|
async function addConfigNodes(graph, services, scanPath) {
|
|
@@ -4959,17 +5096,347 @@ async function addConfigNodes(graph, services, scanPath) {
|
|
|
4959
5096
|
return { nodesAdded, edgesAdded };
|
|
4960
5097
|
}
|
|
4961
5098
|
|
|
5099
|
+
// src/extract/routes.ts
|
|
5100
|
+
init_cjs_shims();
|
|
5101
|
+
var import_node_path22 = __toESM(require("path"), 1);
|
|
5102
|
+
var import_tree_sitter2 = __toESM(require("tree-sitter"), 1);
|
|
5103
|
+
var import_tree_sitter_javascript2 = __toESM(require("tree-sitter-javascript"), 1);
|
|
5104
|
+
var import_types11 = require("@neat.is/types");
|
|
5105
|
+
var PARSE_CHUNK2 = 16384;
|
|
5106
|
+
function parseSource2(parser, source) {
|
|
5107
|
+
return parser.parse(
|
|
5108
|
+
(index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK2)
|
|
5109
|
+
);
|
|
5110
|
+
}
|
|
5111
|
+
function makeJsParser2() {
|
|
5112
|
+
const p = new import_tree_sitter2.default();
|
|
5113
|
+
p.setLanguage(import_tree_sitter_javascript2.default);
|
|
5114
|
+
return p;
|
|
5115
|
+
}
|
|
5116
|
+
var ROUTER_METHODS = /* @__PURE__ */ new Set([
|
|
5117
|
+
"get",
|
|
5118
|
+
"post",
|
|
5119
|
+
"put",
|
|
5120
|
+
"patch",
|
|
5121
|
+
"delete",
|
|
5122
|
+
"options",
|
|
5123
|
+
"head",
|
|
5124
|
+
"all"
|
|
5125
|
+
]);
|
|
5126
|
+
var NEXT_APP_METHODS = /* @__PURE__ */ new Set(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"]);
|
|
5127
|
+
var JS_ROUTE_EXTENSIONS = /* @__PURE__ */ new Set([".js", ".jsx", ".mjs", ".cjs", ".ts", ".tsx"]);
|
|
5128
|
+
function canonicalizeTemplate(raw) {
|
|
5129
|
+
let p = raw.split("?")[0].split("#")[0];
|
|
5130
|
+
if (!p.startsWith("/")) p = "/" + p;
|
|
5131
|
+
if (p.length > 1 && p.endsWith("/")) p = p.slice(0, -1);
|
|
5132
|
+
return p;
|
|
5133
|
+
}
|
|
5134
|
+
function isDynamicSegment(seg) {
|
|
5135
|
+
if (seg.length === 0) return false;
|
|
5136
|
+
if (seg.includes(":")) return true;
|
|
5137
|
+
if (seg.startsWith("{") || seg.startsWith("[")) return true;
|
|
5138
|
+
if (/^\d+$/.test(seg)) return true;
|
|
5139
|
+
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;
|
|
5140
|
+
if (/^[0-9a-f]{24,}$/i.test(seg)) return true;
|
|
5141
|
+
return false;
|
|
5142
|
+
}
|
|
5143
|
+
function normalizePathTemplate(raw) {
|
|
5144
|
+
const canonical = canonicalizeTemplate(raw);
|
|
5145
|
+
const segments = canonical.split("/").filter((s) => s.length > 0);
|
|
5146
|
+
const normalised = segments.map((seg) => isDynamicSegment(seg) ? ":param" : seg.toLowerCase());
|
|
5147
|
+
return "/" + normalised.join("/");
|
|
5148
|
+
}
|
|
5149
|
+
function walk(node, visit) {
|
|
5150
|
+
visit(node);
|
|
5151
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
5152
|
+
const child = node.namedChild(i);
|
|
5153
|
+
if (child) walk(child, visit);
|
|
5154
|
+
}
|
|
5155
|
+
}
|
|
5156
|
+
function staticStringText(node) {
|
|
5157
|
+
if (node.type === "string") {
|
|
5158
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
5159
|
+
const child = node.namedChild(i);
|
|
5160
|
+
if (child?.type === "string_fragment") return child.text;
|
|
5161
|
+
}
|
|
5162
|
+
return "";
|
|
5163
|
+
}
|
|
5164
|
+
if (node.type === "template_string") {
|
|
5165
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
5166
|
+
if (node.namedChild(i)?.type === "template_substitution") return null;
|
|
5167
|
+
}
|
|
5168
|
+
const raw = node.text;
|
|
5169
|
+
return raw.length >= 2 ? raw.slice(1, -1) : "";
|
|
5170
|
+
}
|
|
5171
|
+
return null;
|
|
5172
|
+
}
|
|
5173
|
+
function objectStringProp(objNode, key) {
|
|
5174
|
+
for (let i = 0; i < objNode.namedChildCount; i++) {
|
|
5175
|
+
const pair = objNode.namedChild(i);
|
|
5176
|
+
if (!pair || pair.type !== "pair") continue;
|
|
5177
|
+
const k = pair.childForFieldName("key");
|
|
5178
|
+
if (!k) continue;
|
|
5179
|
+
const kText = k.type === "string" ? staticStringText(k) : k.text;
|
|
5180
|
+
if (kText !== key) continue;
|
|
5181
|
+
const v = pair.childForFieldName("value");
|
|
5182
|
+
if (v) return staticStringText(v);
|
|
5183
|
+
}
|
|
5184
|
+
return null;
|
|
5185
|
+
}
|
|
5186
|
+
function fastifyRouteMethods(objNode) {
|
|
5187
|
+
for (let i = 0; i < objNode.namedChildCount; i++) {
|
|
5188
|
+
const pair = objNode.namedChild(i);
|
|
5189
|
+
if (!pair || pair.type !== "pair") continue;
|
|
5190
|
+
const k = pair.childForFieldName("key");
|
|
5191
|
+
const kText = k ? k.type === "string" ? staticStringText(k) : k.text : null;
|
|
5192
|
+
if (kText !== "method") continue;
|
|
5193
|
+
const v = pair.childForFieldName("value");
|
|
5194
|
+
if (!v) return [];
|
|
5195
|
+
if (v.type === "string" || v.type === "template_string") {
|
|
5196
|
+
const s = staticStringText(v);
|
|
5197
|
+
return s ? [s.toUpperCase()] : [];
|
|
5198
|
+
}
|
|
5199
|
+
if (v.type === "array") {
|
|
5200
|
+
const out = [];
|
|
5201
|
+
for (let j = 0; j < v.namedChildCount; j++) {
|
|
5202
|
+
const el = v.namedChild(j);
|
|
5203
|
+
if (el && (el.type === "string" || el.type === "template_string")) {
|
|
5204
|
+
const s = staticStringText(el);
|
|
5205
|
+
if (s) out.push(s.toUpperCase());
|
|
5206
|
+
}
|
|
5207
|
+
}
|
|
5208
|
+
return out;
|
|
5209
|
+
}
|
|
5210
|
+
}
|
|
5211
|
+
return [];
|
|
5212
|
+
}
|
|
5213
|
+
function serverRoutesFromSource(source, parser, hasExpress, hasFastify) {
|
|
5214
|
+
const tree = parseSource2(parser, source);
|
|
5215
|
+
const out = [];
|
|
5216
|
+
const framework = hasExpress ? "express" : "fastify";
|
|
5217
|
+
walk(tree.rootNode, (node) => {
|
|
5218
|
+
if (node.type !== "call_expression") return;
|
|
5219
|
+
const fn = node.childForFieldName("function");
|
|
5220
|
+
if (!fn || fn.type !== "member_expression") return;
|
|
5221
|
+
const prop = fn.childForFieldName("property");
|
|
5222
|
+
if (!prop) return;
|
|
5223
|
+
const method = prop.text.toLowerCase();
|
|
5224
|
+
const args = node.childForFieldName("arguments");
|
|
5225
|
+
const first = args?.namedChild(0);
|
|
5226
|
+
if (!first) return;
|
|
5227
|
+
const line = node.startPosition.row + 1;
|
|
5228
|
+
if (ROUTER_METHODS.has(method)) {
|
|
5229
|
+
const p = staticStringText(first);
|
|
5230
|
+
if (p && p.startsWith("/")) {
|
|
5231
|
+
out.push({
|
|
5232
|
+
method: method === "all" ? "ALL" : method.toUpperCase(),
|
|
5233
|
+
pathTemplate: canonicalizeTemplate(p),
|
|
5234
|
+
line,
|
|
5235
|
+
framework
|
|
5236
|
+
});
|
|
5237
|
+
}
|
|
5238
|
+
return;
|
|
5239
|
+
}
|
|
5240
|
+
if (method === "route" && hasFastify && first.type === "object") {
|
|
5241
|
+
const url = objectStringProp(first, "url");
|
|
5242
|
+
if (!url || !url.startsWith("/")) return;
|
|
5243
|
+
const methods = fastifyRouteMethods(first);
|
|
5244
|
+
const list = methods.length > 0 ? methods : ["ALL"];
|
|
5245
|
+
for (const m of list) {
|
|
5246
|
+
out.push({
|
|
5247
|
+
method: m === "ALL" ? "ALL" : m.toUpperCase(),
|
|
5248
|
+
pathTemplate: canonicalizeTemplate(url),
|
|
5249
|
+
line,
|
|
5250
|
+
framework: "fastify"
|
|
5251
|
+
});
|
|
5252
|
+
}
|
|
5253
|
+
}
|
|
5254
|
+
});
|
|
5255
|
+
return out;
|
|
5256
|
+
}
|
|
5257
|
+
function segmentsOf(relFile) {
|
|
5258
|
+
return toPosix2(relFile).split("/").filter((s) => s.length > 0);
|
|
5259
|
+
}
|
|
5260
|
+
function isNextAppRouteFile(relFile) {
|
|
5261
|
+
const segs = segmentsOf(relFile);
|
|
5262
|
+
if (!segs.includes("app")) return false;
|
|
5263
|
+
const base = segs[segs.length - 1] ?? "";
|
|
5264
|
+
return /^route\.(?:js|jsx|mjs|cjs|ts|tsx)$/.test(base);
|
|
5265
|
+
}
|
|
5266
|
+
function isNextPagesApiFile(relFile) {
|
|
5267
|
+
const segs = segmentsOf(relFile);
|
|
5268
|
+
const pagesIdx = segs.indexOf("pages");
|
|
5269
|
+
if (pagesIdx === -1 || segs[pagesIdx + 1] !== "api") return false;
|
|
5270
|
+
const base = segs[segs.length - 1] ?? "";
|
|
5271
|
+
if (/^_(app|document|middleware)\./.test(base)) return false;
|
|
5272
|
+
return JS_ROUTE_EXTENSIONS.has(import_node_path22.default.extname(base));
|
|
5273
|
+
}
|
|
5274
|
+
function nextSegment(seg) {
|
|
5275
|
+
if (seg.startsWith("(") && seg.endsWith(")")) return null;
|
|
5276
|
+
const catchAll = seg.match(/^\[\[?\.\.\.(.+?)\]?\]$/);
|
|
5277
|
+
if (catchAll) return ":" + catchAll[1];
|
|
5278
|
+
const dynamic = seg.match(/^\[(.+?)\]$/);
|
|
5279
|
+
if (dynamic) return ":" + dynamic[1];
|
|
5280
|
+
return seg;
|
|
5281
|
+
}
|
|
5282
|
+
function nextAppPathTemplate(relFile) {
|
|
5283
|
+
const segs = segmentsOf(relFile);
|
|
5284
|
+
const appIdx = segs.lastIndexOf("app");
|
|
5285
|
+
const between = segs.slice(appIdx + 1, segs.length - 1);
|
|
5286
|
+
const parts = [];
|
|
5287
|
+
for (const seg of between) {
|
|
5288
|
+
const mapped = nextSegment(seg);
|
|
5289
|
+
if (mapped !== null) parts.push(mapped);
|
|
5290
|
+
}
|
|
5291
|
+
return "/" + parts.join("/");
|
|
5292
|
+
}
|
|
5293
|
+
function nextPagesApiPathTemplate(relFile) {
|
|
5294
|
+
const segs = segmentsOf(relFile);
|
|
5295
|
+
const pagesIdx = segs.indexOf("pages");
|
|
5296
|
+
const rest = segs.slice(pagesIdx + 1);
|
|
5297
|
+
const parts = [];
|
|
5298
|
+
for (let i = 0; i < rest.length; i++) {
|
|
5299
|
+
let seg = rest[i];
|
|
5300
|
+
if (i === rest.length - 1) {
|
|
5301
|
+
seg = seg.replace(/\.(?:js|jsx|mjs|cjs|ts|tsx)$/, "");
|
|
5302
|
+
if (seg === "index") continue;
|
|
5303
|
+
}
|
|
5304
|
+
const mapped = nextSegment(seg);
|
|
5305
|
+
if (mapped !== null) parts.push(mapped);
|
|
5306
|
+
}
|
|
5307
|
+
return "/" + parts.join("/");
|
|
5308
|
+
}
|
|
5309
|
+
function nextAppMethods(root) {
|
|
5310
|
+
const out = [];
|
|
5311
|
+
walk(root, (node) => {
|
|
5312
|
+
if (node.type !== "export_statement") return;
|
|
5313
|
+
const decl = node.childForFieldName("declaration");
|
|
5314
|
+
if (!decl) return;
|
|
5315
|
+
const line = node.startPosition.row + 1;
|
|
5316
|
+
if (decl.type === "function_declaration") {
|
|
5317
|
+
const name = decl.childForFieldName("name")?.text;
|
|
5318
|
+
if (name && NEXT_APP_METHODS.has(name)) out.push({ method: name, line });
|
|
5319
|
+
return;
|
|
5320
|
+
}
|
|
5321
|
+
if (decl.type === "lexical_declaration" || decl.type === "variable_declaration") {
|
|
5322
|
+
for (let i = 0; i < decl.namedChildCount; i++) {
|
|
5323
|
+
const d = decl.namedChild(i);
|
|
5324
|
+
if (d?.type !== "variable_declarator") continue;
|
|
5325
|
+
const name = d.childForFieldName("name")?.text;
|
|
5326
|
+
if (name && NEXT_APP_METHODS.has(name)) out.push({ method: name, line });
|
|
5327
|
+
}
|
|
5328
|
+
}
|
|
5329
|
+
});
|
|
5330
|
+
return out;
|
|
5331
|
+
}
|
|
5332
|
+
function nextRoutesFromFile(source, relFile, parser) {
|
|
5333
|
+
if (isNextAppRouteFile(relFile)) {
|
|
5334
|
+
const tree = parseSource2(parser, source);
|
|
5335
|
+
const template = nextAppPathTemplate(relFile);
|
|
5336
|
+
return nextAppMethods(tree.rootNode).map(({ method, line }) => ({
|
|
5337
|
+
method,
|
|
5338
|
+
pathTemplate: canonicalizeTemplate(template),
|
|
5339
|
+
line,
|
|
5340
|
+
framework: "next"
|
|
5341
|
+
}));
|
|
5342
|
+
}
|
|
5343
|
+
if (isNextPagesApiFile(relFile)) {
|
|
5344
|
+
return [
|
|
5345
|
+
{
|
|
5346
|
+
method: "ALL",
|
|
5347
|
+
pathTemplate: canonicalizeTemplate(nextPagesApiPathTemplate(relFile)),
|
|
5348
|
+
line: 1,
|
|
5349
|
+
framework: "next"
|
|
5350
|
+
}
|
|
5351
|
+
];
|
|
5352
|
+
}
|
|
5353
|
+
return [];
|
|
5354
|
+
}
|
|
5355
|
+
async function addRoutes(graph, services) {
|
|
5356
|
+
const jsParser = makeJsParser2();
|
|
5357
|
+
let nodesAdded = 0;
|
|
5358
|
+
let edgesAdded = 0;
|
|
5359
|
+
for (const service of services) {
|
|
5360
|
+
const deps = {
|
|
5361
|
+
...service.pkg.dependencies ?? {},
|
|
5362
|
+
...service.pkg.devDependencies ?? {}
|
|
5363
|
+
};
|
|
5364
|
+
const hasExpress = deps["express"] !== void 0;
|
|
5365
|
+
const hasFastify = deps["fastify"] !== void 0;
|
|
5366
|
+
const hasNext = deps["next"] !== void 0;
|
|
5367
|
+
if (!hasExpress && !hasFastify && !hasNext) continue;
|
|
5368
|
+
const files = await loadSourceFiles(service.dir);
|
|
5369
|
+
for (const file of files) {
|
|
5370
|
+
if (isTestPath(file.path)) continue;
|
|
5371
|
+
if (!JS_ROUTE_EXTENSIONS.has(import_node_path22.default.extname(file.path))) continue;
|
|
5372
|
+
const relFile = toPosix2(import_node_path22.default.relative(service.dir, file.path));
|
|
5373
|
+
let routes;
|
|
5374
|
+
try {
|
|
5375
|
+
if (hasNext && (isNextAppRouteFile(relFile) || isNextPagesApiFile(relFile))) {
|
|
5376
|
+
routes = nextRoutesFromFile(file.content, relFile, jsParser);
|
|
5377
|
+
} else if (hasExpress || hasFastify) {
|
|
5378
|
+
routes = serverRoutesFromSource(file.content, jsParser, hasExpress, hasFastify);
|
|
5379
|
+
} else {
|
|
5380
|
+
routes = [];
|
|
5381
|
+
}
|
|
5382
|
+
} catch (err) {
|
|
5383
|
+
recordExtractionError("route extraction", file.path, err);
|
|
5384
|
+
continue;
|
|
5385
|
+
}
|
|
5386
|
+
if (routes.length === 0) continue;
|
|
5387
|
+
for (const route of routes) {
|
|
5388
|
+
const rid = (0, import_types11.routeId)(service.pkg.name, route.method, route.pathTemplate);
|
|
5389
|
+
if (!graph.hasNode(rid)) {
|
|
5390
|
+
const node = {
|
|
5391
|
+
id: rid,
|
|
5392
|
+
type: import_types11.NodeType.RouteNode,
|
|
5393
|
+
name: `${route.method} ${route.pathTemplate}`,
|
|
5394
|
+
service: service.pkg.name,
|
|
5395
|
+
method: route.method,
|
|
5396
|
+
pathTemplate: route.pathTemplate,
|
|
5397
|
+
path: relFile,
|
|
5398
|
+
line: route.line,
|
|
5399
|
+
framework: route.framework,
|
|
5400
|
+
discoveredVia: "static"
|
|
5401
|
+
};
|
|
5402
|
+
graph.addNode(rid, node);
|
|
5403
|
+
nodesAdded++;
|
|
5404
|
+
}
|
|
5405
|
+
const containsId = (0, import_types11.extractedEdgeId)(service.node.id, rid, import_types11.EdgeType.CONTAINS);
|
|
5406
|
+
if (!graph.hasEdge(containsId)) {
|
|
5407
|
+
const edge = {
|
|
5408
|
+
id: containsId,
|
|
5409
|
+
source: service.node.id,
|
|
5410
|
+
target: rid,
|
|
5411
|
+
type: import_types11.EdgeType.CONTAINS,
|
|
5412
|
+
provenance: import_types11.Provenance.EXTRACTED,
|
|
5413
|
+
confidence: (0, import_types11.confidenceForExtracted)("structural"),
|
|
5414
|
+
evidence: {
|
|
5415
|
+
file: relFile,
|
|
5416
|
+
line: route.line,
|
|
5417
|
+
snippet: snippet(file.content, route.line)
|
|
5418
|
+
}
|
|
5419
|
+
};
|
|
5420
|
+
graph.addEdgeWithKey(containsId, service.node.id, rid, edge);
|
|
5421
|
+
edgesAdded++;
|
|
5422
|
+
}
|
|
5423
|
+
}
|
|
5424
|
+
}
|
|
5425
|
+
}
|
|
5426
|
+
return { nodesAdded, edgesAdded };
|
|
5427
|
+
}
|
|
5428
|
+
|
|
4962
5429
|
// src/extract/calls/index.ts
|
|
4963
5430
|
init_cjs_shims();
|
|
4964
|
-
var
|
|
5431
|
+
var import_types19 = require("@neat.is/types");
|
|
4965
5432
|
|
|
4966
5433
|
// src/extract/calls/http.ts
|
|
4967
5434
|
init_cjs_shims();
|
|
4968
|
-
var
|
|
4969
|
-
var
|
|
4970
|
-
var
|
|
5435
|
+
var import_node_path23 = __toESM(require("path"), 1);
|
|
5436
|
+
var import_tree_sitter3 = __toESM(require("tree-sitter"), 1);
|
|
5437
|
+
var import_tree_sitter_javascript3 = __toESM(require("tree-sitter-javascript"), 1);
|
|
4971
5438
|
var import_tree_sitter_python2 = __toESM(require("tree-sitter-python"), 1);
|
|
4972
|
-
var
|
|
5439
|
+
var import_types12 = require("@neat.is/types");
|
|
4973
5440
|
var STRING_LITERAL_NODE_TYPES = /* @__PURE__ */ new Set(["string_fragment", "string_content"]);
|
|
4974
5441
|
var JSX_EXTERNAL_LINK_TAGS = /* @__PURE__ */ new Set(["a", "Link", "NavLink", "ExternalLink", "Anchor"]);
|
|
4975
5442
|
function isInsideJsxExternalLink(node) {
|
|
@@ -4997,14 +5464,14 @@ function collectStringLiterals(node, out) {
|
|
|
4997
5464
|
if (child) collectStringLiterals(child, out);
|
|
4998
5465
|
}
|
|
4999
5466
|
}
|
|
5000
|
-
var
|
|
5001
|
-
function
|
|
5467
|
+
var PARSE_CHUNK3 = 16384;
|
|
5468
|
+
function parseSource3(parser, source) {
|
|
5002
5469
|
return parser.parse(
|
|
5003
|
-
(index) => index >= source.length ? "" : source.slice(index, index +
|
|
5470
|
+
(index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK3)
|
|
5004
5471
|
);
|
|
5005
5472
|
}
|
|
5006
5473
|
function callsFromSource(source, parser, knownHosts) {
|
|
5007
|
-
const tree =
|
|
5474
|
+
const tree = parseSource3(parser, source);
|
|
5008
5475
|
const literals = [];
|
|
5009
5476
|
collectStringLiterals(tree.rootNode, literals);
|
|
5010
5477
|
const out = [];
|
|
@@ -5018,27 +5485,20 @@ function callsFromSource(source, parser, knownHosts) {
|
|
|
5018
5485
|
}
|
|
5019
5486
|
return out;
|
|
5020
5487
|
}
|
|
5021
|
-
function
|
|
5022
|
-
const p = new
|
|
5023
|
-
p.setLanguage(
|
|
5488
|
+
function makeJsParser3() {
|
|
5489
|
+
const p = new import_tree_sitter3.default();
|
|
5490
|
+
p.setLanguage(import_tree_sitter_javascript3.default);
|
|
5024
5491
|
return p;
|
|
5025
5492
|
}
|
|
5026
5493
|
function makePyParser2() {
|
|
5027
|
-
const p = new
|
|
5494
|
+
const p = new import_tree_sitter3.default();
|
|
5028
5495
|
p.setLanguage(import_tree_sitter_python2.default);
|
|
5029
5496
|
return p;
|
|
5030
5497
|
}
|
|
5031
5498
|
async function addHttpCallEdges(graph, services) {
|
|
5032
|
-
const jsParser =
|
|
5499
|
+
const jsParser = makeJsParser3();
|
|
5033
5500
|
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
|
-
}
|
|
5501
|
+
const { knownHosts, hostToNodeId } = buildServiceHostIndex(services);
|
|
5042
5502
|
let nodesAdded = 0;
|
|
5043
5503
|
let edgesAdded = 0;
|
|
5044
5504
|
for (const service of services) {
|
|
@@ -5046,7 +5506,7 @@ async function addHttpCallEdges(graph, services) {
|
|
|
5046
5506
|
const seen = /* @__PURE__ */ new Set();
|
|
5047
5507
|
for (const file of files) {
|
|
5048
5508
|
if (isTestPath(file.path)) continue;
|
|
5049
|
-
const parser =
|
|
5509
|
+
const parser = import_node_path23.default.extname(file.path) === ".py" ? pyParser : jsParser;
|
|
5050
5510
|
let sites;
|
|
5051
5511
|
try {
|
|
5052
5512
|
sites = callsFromSource(file.content, parser, knownHosts);
|
|
@@ -5055,14 +5515,14 @@ async function addHttpCallEdges(graph, services) {
|
|
|
5055
5515
|
continue;
|
|
5056
5516
|
}
|
|
5057
5517
|
if (sites.length === 0) continue;
|
|
5058
|
-
const relFile = toPosix2(
|
|
5518
|
+
const relFile = toPosix2(import_node_path23.default.relative(service.dir, file.path));
|
|
5059
5519
|
for (const site of sites) {
|
|
5060
5520
|
const targetId = hostToNodeId.get(site.host);
|
|
5061
5521
|
if (!targetId || targetId === service.node.id) continue;
|
|
5062
5522
|
const dedupKey = `${relFile}|${targetId}`;
|
|
5063
5523
|
if (seen.has(dedupKey)) continue;
|
|
5064
5524
|
seen.add(dedupKey);
|
|
5065
|
-
const confidence = (0,
|
|
5525
|
+
const confidence = (0, import_types12.confidenceForExtracted)("url-literal-service-target");
|
|
5066
5526
|
const ev = {
|
|
5067
5527
|
file: relFile,
|
|
5068
5528
|
line: site.line,
|
|
@@ -5076,25 +5536,25 @@ async function addHttpCallEdges(graph, services) {
|
|
|
5076
5536
|
);
|
|
5077
5537
|
nodesAdded += n;
|
|
5078
5538
|
edgesAdded += e;
|
|
5079
|
-
if (!(0,
|
|
5539
|
+
if (!(0, import_types12.passesExtractedFloor)(confidence)) {
|
|
5080
5540
|
noteExtractedDropped({
|
|
5081
5541
|
source: fileNodeId,
|
|
5082
5542
|
target: targetId,
|
|
5083
|
-
type:
|
|
5543
|
+
type: import_types12.EdgeType.CALLS,
|
|
5084
5544
|
confidence,
|
|
5085
5545
|
confidenceKind: "url-literal-service-target",
|
|
5086
5546
|
evidence: ev
|
|
5087
5547
|
});
|
|
5088
5548
|
continue;
|
|
5089
5549
|
}
|
|
5090
|
-
const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, targetId,
|
|
5550
|
+
const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, targetId, import_types12.EdgeType.CALLS);
|
|
5091
5551
|
if (!graph.hasEdge(edgeId)) {
|
|
5092
5552
|
const edge = {
|
|
5093
5553
|
id: edgeId,
|
|
5094
5554
|
source: fileNodeId,
|
|
5095
5555
|
target: targetId,
|
|
5096
|
-
type:
|
|
5097
|
-
provenance:
|
|
5556
|
+
type: import_types12.EdgeType.CALLS,
|
|
5557
|
+
provenance: import_types12.Provenance.EXTRACTED,
|
|
5098
5558
|
confidence,
|
|
5099
5559
|
evidence: ev
|
|
5100
5560
|
};
|
|
@@ -5107,10 +5567,279 @@ async function addHttpCallEdges(graph, services) {
|
|
|
5107
5567
|
return { nodesAdded, edgesAdded };
|
|
5108
5568
|
}
|
|
5109
5569
|
|
|
5570
|
+
// src/extract/calls/route-match.ts
|
|
5571
|
+
init_cjs_shims();
|
|
5572
|
+
var import_node_path24 = __toESM(require("path"), 1);
|
|
5573
|
+
var import_tree_sitter4 = __toESM(require("tree-sitter"), 1);
|
|
5574
|
+
var import_tree_sitter_javascript4 = __toESM(require("tree-sitter-javascript"), 1);
|
|
5575
|
+
var import_types13 = require("@neat.is/types");
|
|
5576
|
+
var PARSE_CHUNK4 = 16384;
|
|
5577
|
+
function parseSource4(parser, source) {
|
|
5578
|
+
return parser.parse(
|
|
5579
|
+
(index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK4)
|
|
5580
|
+
);
|
|
5581
|
+
}
|
|
5582
|
+
function makeJsParser4() {
|
|
5583
|
+
const p = new import_tree_sitter4.default();
|
|
5584
|
+
p.setLanguage(import_tree_sitter_javascript4.default);
|
|
5585
|
+
return p;
|
|
5586
|
+
}
|
|
5587
|
+
var JS_CLIENT_EXTENSIONS = /* @__PURE__ */ new Set([".js", ".jsx", ".mjs", ".cjs", ".ts", ".tsx"]);
|
|
5588
|
+
var AXIOS_METHODS = /* @__PURE__ */ new Set(["get", "post", "put", "patch", "delete", "head", "options", "request"]);
|
|
5589
|
+
function walk2(node, visit) {
|
|
5590
|
+
visit(node);
|
|
5591
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
5592
|
+
const child = node.namedChild(i);
|
|
5593
|
+
if (child) walk2(child, visit);
|
|
5594
|
+
}
|
|
5595
|
+
}
|
|
5596
|
+
function reconstructUrl(node) {
|
|
5597
|
+
if (node.type === "string") {
|
|
5598
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
5599
|
+
const child = node.namedChild(i);
|
|
5600
|
+
if (child?.type === "string_fragment") return child.text;
|
|
5601
|
+
}
|
|
5602
|
+
return "";
|
|
5603
|
+
}
|
|
5604
|
+
if (node.type === "template_string") {
|
|
5605
|
+
let out = "";
|
|
5606
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
5607
|
+
const child = node.namedChild(i);
|
|
5608
|
+
if (!child) continue;
|
|
5609
|
+
if (child.type === "string_fragment") out += child.text;
|
|
5610
|
+
else if (child.type === "template_substitution") out += ":param";
|
|
5611
|
+
}
|
|
5612
|
+
if (out.length === 0) {
|
|
5613
|
+
const raw = node.text;
|
|
5614
|
+
return raw.length >= 2 ? raw.slice(1, -1) : "";
|
|
5615
|
+
}
|
|
5616
|
+
return out;
|
|
5617
|
+
}
|
|
5618
|
+
return null;
|
|
5619
|
+
}
|
|
5620
|
+
function methodFromOptions(objNode) {
|
|
5621
|
+
for (let i = 0; i < objNode.namedChildCount; i++) {
|
|
5622
|
+
const pair = objNode.namedChild(i);
|
|
5623
|
+
if (!pair || pair.type !== "pair") continue;
|
|
5624
|
+
const k = pair.childForFieldName("key");
|
|
5625
|
+
const kText = k ? k.type === "string" ? stringText(k) : k.text : null;
|
|
5626
|
+
if (kText !== "method") continue;
|
|
5627
|
+
const v = pair.childForFieldName("value");
|
|
5628
|
+
if (v && (v.type === "string" || v.type === "template_string")) {
|
|
5629
|
+
const s = stringText(v);
|
|
5630
|
+
return s ? s.toUpperCase() : void 0;
|
|
5631
|
+
}
|
|
5632
|
+
}
|
|
5633
|
+
return void 0;
|
|
5634
|
+
}
|
|
5635
|
+
function stringText(node) {
|
|
5636
|
+
if (node.type === "string") {
|
|
5637
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
5638
|
+
const child = node.namedChild(i);
|
|
5639
|
+
if (child?.type === "string_fragment") return child.text;
|
|
5640
|
+
}
|
|
5641
|
+
return "";
|
|
5642
|
+
}
|
|
5643
|
+
return null;
|
|
5644
|
+
}
|
|
5645
|
+
function urlNodeFromConfig(objNode) {
|
|
5646
|
+
for (let i = 0; i < objNode.namedChildCount; i++) {
|
|
5647
|
+
const pair = objNode.namedChild(i);
|
|
5648
|
+
if (!pair || pair.type !== "pair") continue;
|
|
5649
|
+
const k = pair.childForFieldName("key");
|
|
5650
|
+
const kText = k ? k.type === "string" ? stringText(k) : k.text : null;
|
|
5651
|
+
if (kText === "url") return pair.childForFieldName("value");
|
|
5652
|
+
}
|
|
5653
|
+
return null;
|
|
5654
|
+
}
|
|
5655
|
+
function pathOf(urlStr) {
|
|
5656
|
+
try {
|
|
5657
|
+
const candidate = urlStr.startsWith("//") ? `http:${urlStr}` : urlStr;
|
|
5658
|
+
const parsed = new URL(candidate);
|
|
5659
|
+
return parsed.pathname || "/";
|
|
5660
|
+
} catch {
|
|
5661
|
+
return null;
|
|
5662
|
+
}
|
|
5663
|
+
}
|
|
5664
|
+
function matchHost(urlStr, knownHosts) {
|
|
5665
|
+
for (const host of knownHosts) {
|
|
5666
|
+
if (urlMatchesHost(urlStr, host)) return host;
|
|
5667
|
+
}
|
|
5668
|
+
return null;
|
|
5669
|
+
}
|
|
5670
|
+
function clientCallSitesFromSource(source, parser, knownHosts) {
|
|
5671
|
+
const tree = parseSource4(parser, source);
|
|
5672
|
+
const out = [];
|
|
5673
|
+
const push = (urlNode, method, callNode) => {
|
|
5674
|
+
const urlStr = reconstructUrl(urlNode);
|
|
5675
|
+
if (!urlStr) return;
|
|
5676
|
+
const host = matchHost(urlStr, knownHosts);
|
|
5677
|
+
if (!host) return;
|
|
5678
|
+
const p = pathOf(urlStr);
|
|
5679
|
+
if (p === null) return;
|
|
5680
|
+
const line = callNode.startPosition.row + 1;
|
|
5681
|
+
out.push({
|
|
5682
|
+
host,
|
|
5683
|
+
method,
|
|
5684
|
+
pathTemplate: p,
|
|
5685
|
+
line,
|
|
5686
|
+
snippet: snippet(source, line)
|
|
5687
|
+
});
|
|
5688
|
+
};
|
|
5689
|
+
walk2(tree.rootNode, (node) => {
|
|
5690
|
+
if (node.type !== "call_expression") return;
|
|
5691
|
+
const fn = node.childForFieldName("function");
|
|
5692
|
+
if (!fn) return;
|
|
5693
|
+
const args = node.childForFieldName("arguments");
|
|
5694
|
+
const first = args?.namedChild(0);
|
|
5695
|
+
if (!first) return;
|
|
5696
|
+
const fnName = fn.type === "identifier" ? fn.text : fn.type === "member_expression" ? fn.childForFieldName("property")?.text ?? "" : "";
|
|
5697
|
+
if (fn.type === "identifier" && fnName === "fetch") {
|
|
5698
|
+
const opts = args?.namedChild(1);
|
|
5699
|
+
const method = opts && opts.type === "object" ? methodFromOptions(opts) ?? "GET" : "GET";
|
|
5700
|
+
push(first, method, node);
|
|
5701
|
+
return;
|
|
5702
|
+
}
|
|
5703
|
+
if (fn.type === "identifier" && fnName === "axios") {
|
|
5704
|
+
if (first.type === "object") {
|
|
5705
|
+
const urlNode = urlNodeFromConfig(first);
|
|
5706
|
+
if (urlNode) push(urlNode, methodFromOptions(first) ?? "GET", node);
|
|
5707
|
+
} else {
|
|
5708
|
+
const opts = args?.namedChild(1);
|
|
5709
|
+
const method = opts && opts.type === "object" ? methodFromOptions(opts) ?? "GET" : "GET";
|
|
5710
|
+
push(first, method, node);
|
|
5711
|
+
}
|
|
5712
|
+
return;
|
|
5713
|
+
}
|
|
5714
|
+
if (fn.type === "member_expression") {
|
|
5715
|
+
const obj = fn.childForFieldName("object");
|
|
5716
|
+
const objName = obj?.text ?? "";
|
|
5717
|
+
if (objName === "axios" && AXIOS_METHODS.has(fnName)) {
|
|
5718
|
+
if (fnName === "request" && first.type === "object") {
|
|
5719
|
+
const urlNode = urlNodeFromConfig(first);
|
|
5720
|
+
if (urlNode) push(urlNode, methodFromOptions(first) ?? "GET", node);
|
|
5721
|
+
} else {
|
|
5722
|
+
push(first, fnName.toUpperCase(), node);
|
|
5723
|
+
}
|
|
5724
|
+
return;
|
|
5725
|
+
}
|
|
5726
|
+
if ((objName === "http" || objName === "https") && (fnName === "request" || fnName === "get")) {
|
|
5727
|
+
const opts = args?.namedChild(1);
|
|
5728
|
+
const method = opts && opts.type === "object" ? methodFromOptions(opts) ?? (fnName === "get" ? "GET" : "GET") : "GET";
|
|
5729
|
+
push(first, method, node);
|
|
5730
|
+
return;
|
|
5731
|
+
}
|
|
5732
|
+
}
|
|
5733
|
+
});
|
|
5734
|
+
return out;
|
|
5735
|
+
}
|
|
5736
|
+
function buildRouteIndex(graph) {
|
|
5737
|
+
const index = /* @__PURE__ */ new Map();
|
|
5738
|
+
graph.forEachNode((_id, attrs) => {
|
|
5739
|
+
const node = attrs;
|
|
5740
|
+
if (node.type !== import_types13.NodeType.RouteNode) return;
|
|
5741
|
+
const route = attrs;
|
|
5742
|
+
const owner = (0, import_types13.serviceId)(route.service);
|
|
5743
|
+
const entry = {
|
|
5744
|
+
method: route.method.toUpperCase(),
|
|
5745
|
+
normalizedPath: normalizePathTemplate(route.pathTemplate),
|
|
5746
|
+
routeNodeId: route.id
|
|
5747
|
+
};
|
|
5748
|
+
const list = index.get(owner);
|
|
5749
|
+
if (list) list.push(entry);
|
|
5750
|
+
else index.set(owner, [entry]);
|
|
5751
|
+
});
|
|
5752
|
+
return index;
|
|
5753
|
+
}
|
|
5754
|
+
function findRoute(entries, method, normalizedPath) {
|
|
5755
|
+
return entries.find(
|
|
5756
|
+
(e) => e.normalizedPath === normalizedPath && (e.method === "ALL" || method === void 0 || e.method === method)
|
|
5757
|
+
);
|
|
5758
|
+
}
|
|
5759
|
+
async function addRouteCallEdges(graph, services) {
|
|
5760
|
+
const jsParser = makeJsParser4();
|
|
5761
|
+
const { knownHosts, hostToNodeId } = buildServiceHostIndex(services);
|
|
5762
|
+
const routeIndex = buildRouteIndex(graph);
|
|
5763
|
+
if (routeIndex.size === 0) return { nodesAdded: 0, edgesAdded: 0 };
|
|
5764
|
+
let nodesAdded = 0;
|
|
5765
|
+
let edgesAdded = 0;
|
|
5766
|
+
for (const service of services) {
|
|
5767
|
+
const files = await loadSourceFiles(service.dir);
|
|
5768
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5769
|
+
for (const file of files) {
|
|
5770
|
+
if (isTestPath(file.path)) continue;
|
|
5771
|
+
if (!JS_CLIENT_EXTENSIONS.has(import_node_path24.default.extname(file.path))) continue;
|
|
5772
|
+
let sites;
|
|
5773
|
+
try {
|
|
5774
|
+
sites = clientCallSitesFromSource(file.content, jsParser, knownHosts);
|
|
5775
|
+
} catch (err) {
|
|
5776
|
+
recordExtractionError("route-match call extraction", file.path, err);
|
|
5777
|
+
continue;
|
|
5778
|
+
}
|
|
5779
|
+
if (sites.length === 0) continue;
|
|
5780
|
+
const relFile = toPosix2(import_node_path24.default.relative(service.dir, file.path));
|
|
5781
|
+
for (const site of sites) {
|
|
5782
|
+
const serverServiceId = hostToNodeId.get(site.host);
|
|
5783
|
+
if (!serverServiceId || serverServiceId === service.node.id) continue;
|
|
5784
|
+
const entries = routeIndex.get(serverServiceId);
|
|
5785
|
+
if (!entries) continue;
|
|
5786
|
+
const normalizedPath = normalizePathTemplate(site.pathTemplate);
|
|
5787
|
+
const match = findRoute(entries, site.method, normalizedPath);
|
|
5788
|
+
if (!match) continue;
|
|
5789
|
+
const dedupKey = `${relFile}|${match.routeNodeId}`;
|
|
5790
|
+
if (seen.has(dedupKey)) continue;
|
|
5791
|
+
seen.add(dedupKey);
|
|
5792
|
+
const { fileNodeId, nodesAdded: n, edgesAdded: e } = ensureFileNode(
|
|
5793
|
+
graph,
|
|
5794
|
+
service.pkg.name,
|
|
5795
|
+
service.node.id,
|
|
5796
|
+
relFile
|
|
5797
|
+
);
|
|
5798
|
+
nodesAdded += n;
|
|
5799
|
+
edgesAdded += e;
|
|
5800
|
+
const confidence = (0, import_types13.confidenceForExtracted)("verified-call-site");
|
|
5801
|
+
const ev = {
|
|
5802
|
+
file: relFile,
|
|
5803
|
+
line: site.line,
|
|
5804
|
+
snippet: site.snippet,
|
|
5805
|
+
method: site.method ?? match.method,
|
|
5806
|
+
pathTemplate: site.pathTemplate
|
|
5807
|
+
};
|
|
5808
|
+
if (!(0, import_types13.passesExtractedFloor)(confidence)) {
|
|
5809
|
+
noteExtractedDropped({
|
|
5810
|
+
source: fileNodeId,
|
|
5811
|
+
target: match.routeNodeId,
|
|
5812
|
+
type: import_types13.EdgeType.CALLS,
|
|
5813
|
+
confidence,
|
|
5814
|
+
confidenceKind: "verified-call-site",
|
|
5815
|
+
evidence: ev
|
|
5816
|
+
});
|
|
5817
|
+
continue;
|
|
5818
|
+
}
|
|
5819
|
+
const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, match.routeNodeId, import_types13.EdgeType.CALLS);
|
|
5820
|
+
if (!graph.hasEdge(edgeId)) {
|
|
5821
|
+
const edge = {
|
|
5822
|
+
id: edgeId,
|
|
5823
|
+
source: fileNodeId,
|
|
5824
|
+
target: match.routeNodeId,
|
|
5825
|
+
type: import_types13.EdgeType.CALLS,
|
|
5826
|
+
provenance: import_types13.Provenance.EXTRACTED,
|
|
5827
|
+
confidence,
|
|
5828
|
+
evidence: ev
|
|
5829
|
+
};
|
|
5830
|
+
graph.addEdgeWithKey(edgeId, fileNodeId, match.routeNodeId, edge);
|
|
5831
|
+
edgesAdded++;
|
|
5832
|
+
}
|
|
5833
|
+
}
|
|
5834
|
+
}
|
|
5835
|
+
}
|
|
5836
|
+
return { nodesAdded, edgesAdded };
|
|
5837
|
+
}
|
|
5838
|
+
|
|
5110
5839
|
// src/extract/calls/kafka.ts
|
|
5111
5840
|
init_cjs_shims();
|
|
5112
|
-
var
|
|
5113
|
-
var
|
|
5841
|
+
var import_node_path25 = __toESM(require("path"), 1);
|
|
5842
|
+
var import_types14 = require("@neat.is/types");
|
|
5114
5843
|
var PRODUCER_TOPIC_RE = /(?:producer|kafkaProducer)[\s\S]{0,40}?\.send\s*\(\s*\{[\s\S]{0,200}?topic\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
5115
5844
|
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
5845
|
function findAll(re, text) {
|
|
@@ -5131,7 +5860,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
|
|
|
5131
5860
|
seen.add(key);
|
|
5132
5861
|
const line = lineOf(file.content, topic);
|
|
5133
5862
|
out.push({
|
|
5134
|
-
infraId: (0,
|
|
5863
|
+
infraId: (0, import_types14.infraId)("kafka-topic", topic),
|
|
5135
5864
|
name: topic,
|
|
5136
5865
|
kind: "kafka-topic",
|
|
5137
5866
|
edgeType,
|
|
@@ -5140,7 +5869,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
|
|
|
5140
5869
|
// tier (ADR-066).
|
|
5141
5870
|
confidenceKind: "verified-call-site",
|
|
5142
5871
|
evidence: {
|
|
5143
|
-
file:
|
|
5872
|
+
file: import_node_path25.default.relative(serviceDir, file.path),
|
|
5144
5873
|
line,
|
|
5145
5874
|
snippet: snippet(file.content, line)
|
|
5146
5875
|
}
|
|
@@ -5153,8 +5882,8 @@ function kafkaEndpointsFromFile(file, serviceDir) {
|
|
|
5153
5882
|
|
|
5154
5883
|
// src/extract/calls/redis.ts
|
|
5155
5884
|
init_cjs_shims();
|
|
5156
|
-
var
|
|
5157
|
-
var
|
|
5885
|
+
var import_node_path26 = __toESM(require("path"), 1);
|
|
5886
|
+
var import_types15 = require("@neat.is/types");
|
|
5158
5887
|
var REDIS_URL_RE = /redis(?:s)?:\/\/(?:[^@'"`\s]+@)?([^:/'"`\s]+)(?::(\d+))?/g;
|
|
5159
5888
|
function redisEndpointsFromFile(file, serviceDir) {
|
|
5160
5889
|
const out = [];
|
|
@@ -5167,7 +5896,7 @@ function redisEndpointsFromFile(file, serviceDir) {
|
|
|
5167
5896
|
seen.add(host);
|
|
5168
5897
|
const line = lineOf(file.content, host);
|
|
5169
5898
|
out.push({
|
|
5170
|
-
infraId: (0,
|
|
5899
|
+
infraId: (0, import_types15.infraId)("redis", host),
|
|
5171
5900
|
name: host,
|
|
5172
5901
|
kind: "redis",
|
|
5173
5902
|
edgeType: "CALLS",
|
|
@@ -5176,7 +5905,7 @@ function redisEndpointsFromFile(file, serviceDir) {
|
|
|
5176
5905
|
// support tier (ADR-066).
|
|
5177
5906
|
confidenceKind: "url-with-structural-support",
|
|
5178
5907
|
evidence: {
|
|
5179
|
-
file:
|
|
5908
|
+
file: import_node_path26.default.relative(serviceDir, file.path),
|
|
5180
5909
|
line,
|
|
5181
5910
|
snippet: snippet(file.content, line)
|
|
5182
5911
|
}
|
|
@@ -5187,8 +5916,8 @@ function redisEndpointsFromFile(file, serviceDir) {
|
|
|
5187
5916
|
|
|
5188
5917
|
// src/extract/calls/aws.ts
|
|
5189
5918
|
init_cjs_shims();
|
|
5190
|
-
var
|
|
5191
|
-
var
|
|
5919
|
+
var import_node_path27 = __toESM(require("path"), 1);
|
|
5920
|
+
var import_types16 = require("@neat.is/types");
|
|
5192
5921
|
var S3_BUCKET_RE = /Bucket\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
5193
5922
|
var DYNAMO_TABLE_RE = /TableName\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
5194
5923
|
function hasMarker(text, markers) {
|
|
@@ -5212,7 +5941,7 @@ function awsEndpointsFromFile(file, serviceDir) {
|
|
|
5212
5941
|
seen.add(key);
|
|
5213
5942
|
const line = lineOf(file.content, name);
|
|
5214
5943
|
out.push({
|
|
5215
|
-
infraId: (0,
|
|
5944
|
+
infraId: (0, import_types16.infraId)(kind, name),
|
|
5216
5945
|
name,
|
|
5217
5946
|
kind,
|
|
5218
5947
|
edgeType: "CALLS",
|
|
@@ -5221,7 +5950,7 @@ function awsEndpointsFromFile(file, serviceDir) {
|
|
|
5221
5950
|
// (ADR-066).
|
|
5222
5951
|
confidenceKind: "verified-call-site",
|
|
5223
5952
|
evidence: {
|
|
5224
|
-
file:
|
|
5953
|
+
file: import_node_path27.default.relative(serviceDir, file.path),
|
|
5225
5954
|
line,
|
|
5226
5955
|
snippet: snippet(file.content, line)
|
|
5227
5956
|
}
|
|
@@ -5246,8 +5975,8 @@ function awsEndpointsFromFile(file, serviceDir) {
|
|
|
5246
5975
|
|
|
5247
5976
|
// src/extract/calls/grpc.ts
|
|
5248
5977
|
init_cjs_shims();
|
|
5249
|
-
var
|
|
5250
|
-
var
|
|
5978
|
+
var import_node_path28 = __toESM(require("path"), 1);
|
|
5979
|
+
var import_types17 = require("@neat.is/types");
|
|
5251
5980
|
var GRPC_CLIENT_RE = /new\s+([A-Z][A-Za-z0-9_]*)Client\s*\(\s*['"`]?([^,'"`)]+)?/g;
|
|
5252
5981
|
var AWS_SDK_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@aws-sdk\/client-([a-z0-9-]+)['"`]/g;
|
|
5253
5982
|
var GRPC_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@grpc\/grpc-js['"`]|_grpc_pb['"`]/;
|
|
@@ -5296,7 +6025,7 @@ function grpcEndpointsFromFile(file, serviceDir) {
|
|
|
5296
6025
|
const { kind } = classified;
|
|
5297
6026
|
const line = lineOf(file.content, m[0]);
|
|
5298
6027
|
out.push({
|
|
5299
|
-
infraId: (0,
|
|
6028
|
+
infraId: (0, import_types17.infraId)(kind, name),
|
|
5300
6029
|
name,
|
|
5301
6030
|
kind,
|
|
5302
6031
|
edgeType: "CALLS",
|
|
@@ -5305,7 +6034,7 @@ function grpcEndpointsFromFile(file, serviceDir) {
|
|
|
5305
6034
|
// tier (ADR-066).
|
|
5306
6035
|
confidenceKind: "verified-call-site",
|
|
5307
6036
|
evidence: {
|
|
5308
|
-
file:
|
|
6037
|
+
file: import_node_path28.default.relative(serviceDir, file.path),
|
|
5309
6038
|
line,
|
|
5310
6039
|
snippet: snippet(file.content, line)
|
|
5311
6040
|
}
|
|
@@ -5316,8 +6045,8 @@ function grpcEndpointsFromFile(file, serviceDir) {
|
|
|
5316
6045
|
|
|
5317
6046
|
// src/extract/calls/supabase.ts
|
|
5318
6047
|
init_cjs_shims();
|
|
5319
|
-
var
|
|
5320
|
-
var
|
|
6048
|
+
var import_node_path29 = __toESM(require("path"), 1);
|
|
6049
|
+
var import_types18 = require("@neat.is/types");
|
|
5321
6050
|
var SUPABASE_JS_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@supabase\/supabase-js['"`]/;
|
|
5322
6051
|
var SUPABASE_SSR_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@supabase\/ssr['"`]/;
|
|
5323
6052
|
var SUPABASE_CLIENT_RE = /\b(createClient|createServerClient|createBrowserClient)\s*\(\s*(?:['"`]([^'"`]*)['"`])?/g;
|
|
@@ -5355,7 +6084,7 @@ function supabaseEndpointsFromFile(file, serviceDir) {
|
|
|
5355
6084
|
seen.add(name);
|
|
5356
6085
|
const line = lineOf(file.content, m[0]);
|
|
5357
6086
|
out.push({
|
|
5358
|
-
infraId: (0,
|
|
6087
|
+
infraId: (0, import_types18.infraId)("supabase", name),
|
|
5359
6088
|
name,
|
|
5360
6089
|
kind: "supabase",
|
|
5361
6090
|
edgeType: "CALLS",
|
|
@@ -5365,7 +6094,7 @@ function supabaseEndpointsFromFile(file, serviceDir) {
|
|
|
5365
6094
|
// tier (ADR-066), the same grade aws.ts / grpc.ts emit at.
|
|
5366
6095
|
confidenceKind: "verified-call-site",
|
|
5367
6096
|
evidence: {
|
|
5368
|
-
file:
|
|
6097
|
+
file: import_node_path29.default.relative(serviceDir, file.path),
|
|
5369
6098
|
line,
|
|
5370
6099
|
snippet: snippet(file.content, line)
|
|
5371
6100
|
}
|
|
@@ -5378,11 +6107,11 @@ function supabaseEndpointsFromFile(file, serviceDir) {
|
|
|
5378
6107
|
function edgeTypeFromEndpoint(ep) {
|
|
5379
6108
|
switch (ep.edgeType) {
|
|
5380
6109
|
case "PUBLISHES_TO":
|
|
5381
|
-
return
|
|
6110
|
+
return import_types19.EdgeType.PUBLISHES_TO;
|
|
5382
6111
|
case "CONSUMES_FROM":
|
|
5383
|
-
return
|
|
6112
|
+
return import_types19.EdgeType.CONSUMES_FROM;
|
|
5384
6113
|
default:
|
|
5385
|
-
return
|
|
6114
|
+
return import_types19.EdgeType.CALLS;
|
|
5386
6115
|
}
|
|
5387
6116
|
}
|
|
5388
6117
|
function isAwsKind(kind) {
|
|
@@ -5410,7 +6139,7 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
5410
6139
|
if (!graph.hasNode(ep.infraId)) {
|
|
5411
6140
|
const node = {
|
|
5412
6141
|
id: ep.infraId,
|
|
5413
|
-
type:
|
|
6142
|
+
type: import_types19.NodeType.InfraNode,
|
|
5414
6143
|
name: ep.name,
|
|
5415
6144
|
// #238 — `aws-*` covers AWS-SDK client kinds (aws-s3, aws-dynamodb,
|
|
5416
6145
|
// aws-cognito-identity-provider, …); `s3-` / `dynamodb-` cover the
|
|
@@ -5422,7 +6151,7 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
5422
6151
|
nodesAdded++;
|
|
5423
6152
|
}
|
|
5424
6153
|
const edgeType = edgeTypeFromEndpoint(ep);
|
|
5425
|
-
const confidence = (0,
|
|
6154
|
+
const confidence = (0, import_types19.confidenceForExtracted)(ep.confidenceKind);
|
|
5426
6155
|
const relFile = toPosix2(ep.evidence.file);
|
|
5427
6156
|
const { fileNodeId, nodesAdded: n, edgesAdded: e } = ensureFileNode(
|
|
5428
6157
|
graph,
|
|
@@ -5432,7 +6161,7 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
5432
6161
|
);
|
|
5433
6162
|
nodesAdded += n;
|
|
5434
6163
|
edgesAdded += e;
|
|
5435
|
-
if (!(0,
|
|
6164
|
+
if (!(0, import_types19.passesExtractedFloor)(confidence)) {
|
|
5436
6165
|
noteExtractedDropped({
|
|
5437
6166
|
source: fileNodeId,
|
|
5438
6167
|
target: ep.infraId,
|
|
@@ -5452,7 +6181,7 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
5452
6181
|
source: fileNodeId,
|
|
5453
6182
|
target: ep.infraId,
|
|
5454
6183
|
type: edgeType,
|
|
5455
|
-
provenance:
|
|
6184
|
+
provenance: import_types19.Provenance.EXTRACTED,
|
|
5456
6185
|
confidence,
|
|
5457
6186
|
evidence: ep.evidence
|
|
5458
6187
|
};
|
|
@@ -5466,9 +6195,10 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
5466
6195
|
async function addCallEdges(graph, services) {
|
|
5467
6196
|
const http = await addHttpCallEdges(graph, services);
|
|
5468
6197
|
const ext = await addExternalEndpointEdges(graph, services);
|
|
6198
|
+
const routes = await addRouteCallEdges(graph, services);
|
|
5469
6199
|
return {
|
|
5470
|
-
nodesAdded: http.nodesAdded + ext.nodesAdded,
|
|
5471
|
-
edgesAdded: http.edgesAdded + ext.edgesAdded
|
|
6200
|
+
nodesAdded: http.nodesAdded + ext.nodesAdded + routes.nodesAdded,
|
|
6201
|
+
edgesAdded: http.edgesAdded + ext.edgesAdded + routes.edgesAdded
|
|
5472
6202
|
};
|
|
5473
6203
|
}
|
|
5474
6204
|
|
|
@@ -5477,16 +6207,16 @@ init_cjs_shims();
|
|
|
5477
6207
|
|
|
5478
6208
|
// src/extract/infra/docker-compose.ts
|
|
5479
6209
|
init_cjs_shims();
|
|
5480
|
-
var
|
|
5481
|
-
var
|
|
6210
|
+
var import_node_path30 = __toESM(require("path"), 1);
|
|
6211
|
+
var import_types21 = require("@neat.is/types");
|
|
5482
6212
|
|
|
5483
6213
|
// src/extract/infra/shared.ts
|
|
5484
6214
|
init_cjs_shims();
|
|
5485
|
-
var
|
|
6215
|
+
var import_types20 = require("@neat.is/types");
|
|
5486
6216
|
function makeInfraNode(kind, name, provider = "self", extras) {
|
|
5487
6217
|
return {
|
|
5488
|
-
id: (0,
|
|
5489
|
-
type:
|
|
6218
|
+
id: (0, import_types20.infraId)(kind, name),
|
|
6219
|
+
type: import_types20.NodeType.InfraNode,
|
|
5490
6220
|
name,
|
|
5491
6221
|
provider,
|
|
5492
6222
|
kind,
|
|
@@ -5515,7 +6245,7 @@ function dependsOnList(value) {
|
|
|
5515
6245
|
}
|
|
5516
6246
|
function serviceNameToServiceNode(name, services) {
|
|
5517
6247
|
for (const s of services) {
|
|
5518
|
-
if (s.node.name === name ||
|
|
6248
|
+
if (s.node.name === name || import_node_path30.default.basename(s.dir) === name) return s.node.id;
|
|
5519
6249
|
}
|
|
5520
6250
|
return null;
|
|
5521
6251
|
}
|
|
@@ -5524,7 +6254,7 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
5524
6254
|
let edgesAdded = 0;
|
|
5525
6255
|
let composePath = null;
|
|
5526
6256
|
for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
|
|
5527
|
-
const abs =
|
|
6257
|
+
const abs = import_node_path30.default.join(scanPath, name);
|
|
5528
6258
|
if (await exists(abs)) {
|
|
5529
6259
|
composePath = abs;
|
|
5530
6260
|
break;
|
|
@@ -5537,13 +6267,13 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
5537
6267
|
} catch (err) {
|
|
5538
6268
|
recordExtractionError(
|
|
5539
6269
|
"infra docker-compose",
|
|
5540
|
-
|
|
6270
|
+
import_node_path30.default.relative(scanPath, composePath),
|
|
5541
6271
|
err
|
|
5542
6272
|
);
|
|
5543
6273
|
return { nodesAdded, edgesAdded };
|
|
5544
6274
|
}
|
|
5545
6275
|
if (!compose?.services) return { nodesAdded, edgesAdded };
|
|
5546
|
-
const evidenceFile =
|
|
6276
|
+
const evidenceFile = import_node_path30.default.relative(scanPath, composePath).split(import_node_path30.default.sep).join("/");
|
|
5547
6277
|
const composeNameToNodeId = /* @__PURE__ */ new Map();
|
|
5548
6278
|
for (const [composeName, svc] of Object.entries(compose.services)) {
|
|
5549
6279
|
const matchedServiceId = serviceNameToServiceNode(composeName, services);
|
|
@@ -5565,15 +6295,15 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
5565
6295
|
for (const dep of dependsOnList(svc.depends_on)) {
|
|
5566
6296
|
const targetId = composeNameToNodeId.get(dep);
|
|
5567
6297
|
if (!targetId) continue;
|
|
5568
|
-
const edgeId = (0, import_types4.extractedEdgeId)(sourceId, targetId,
|
|
6298
|
+
const edgeId = (0, import_types4.extractedEdgeId)(sourceId, targetId, import_types21.EdgeType.DEPENDS_ON);
|
|
5569
6299
|
if (graph.hasEdge(edgeId)) continue;
|
|
5570
6300
|
const edge = {
|
|
5571
6301
|
id: edgeId,
|
|
5572
6302
|
source: sourceId,
|
|
5573
6303
|
target: targetId,
|
|
5574
|
-
type:
|
|
5575
|
-
provenance:
|
|
5576
|
-
confidence: (0,
|
|
6304
|
+
type: import_types21.EdgeType.DEPENDS_ON,
|
|
6305
|
+
provenance: import_types21.Provenance.EXTRACTED,
|
|
6306
|
+
confidence: (0, import_types21.confidenceForExtracted)("structural"),
|
|
5577
6307
|
evidence: { file: evidenceFile }
|
|
5578
6308
|
};
|
|
5579
6309
|
graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
|
|
@@ -5585,9 +6315,9 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
5585
6315
|
|
|
5586
6316
|
// src/extract/infra/dockerfile.ts
|
|
5587
6317
|
init_cjs_shims();
|
|
5588
|
-
var
|
|
6318
|
+
var import_node_path31 = __toESM(require("path"), 1);
|
|
5589
6319
|
var import_node_fs15 = require("fs");
|
|
5590
|
-
var
|
|
6320
|
+
var import_types22 = require("@neat.is/types");
|
|
5591
6321
|
function readDockerfile(content) {
|
|
5592
6322
|
let image = null;
|
|
5593
6323
|
const ports = [];
|
|
@@ -5616,7 +6346,7 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
5616
6346
|
let nodesAdded = 0;
|
|
5617
6347
|
let edgesAdded = 0;
|
|
5618
6348
|
for (const service of services) {
|
|
5619
|
-
const dockerfilePath =
|
|
6349
|
+
const dockerfilePath = import_node_path31.default.join(service.dir, "Dockerfile");
|
|
5620
6350
|
if (!await exists(dockerfilePath)) continue;
|
|
5621
6351
|
let content;
|
|
5622
6352
|
try {
|
|
@@ -5624,7 +6354,7 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
5624
6354
|
} catch (err) {
|
|
5625
6355
|
recordExtractionError(
|
|
5626
6356
|
"infra dockerfile",
|
|
5627
|
-
|
|
6357
|
+
import_node_path31.default.relative(scanPath, dockerfilePath),
|
|
5628
6358
|
err
|
|
5629
6359
|
);
|
|
5630
6360
|
continue;
|
|
@@ -5636,8 +6366,8 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
5636
6366
|
graph.addNode(node.id, node);
|
|
5637
6367
|
nodesAdded++;
|
|
5638
6368
|
}
|
|
5639
|
-
const relDockerfile = toPosix2(
|
|
5640
|
-
const evidenceFile = toPosix2(
|
|
6369
|
+
const relDockerfile = toPosix2(import_node_path31.default.relative(service.dir, dockerfilePath));
|
|
6370
|
+
const evidenceFile = toPosix2(import_node_path31.default.relative(scanPath, dockerfilePath));
|
|
5641
6371
|
const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
|
|
5642
6372
|
graph,
|
|
5643
6373
|
service.pkg.name,
|
|
@@ -5646,15 +6376,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
5646
6376
|
);
|
|
5647
6377
|
nodesAdded += fn;
|
|
5648
6378
|
edgesAdded += fe;
|
|
5649
|
-
const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, node.id,
|
|
6379
|
+
const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, node.id, import_types22.EdgeType.RUNS_ON);
|
|
5650
6380
|
if (!graph.hasEdge(edgeId)) {
|
|
5651
6381
|
const edge = {
|
|
5652
6382
|
id: edgeId,
|
|
5653
6383
|
source: fileNodeId,
|
|
5654
6384
|
target: node.id,
|
|
5655
|
-
type:
|
|
5656
|
-
provenance:
|
|
5657
|
-
confidence: (0,
|
|
6385
|
+
type: import_types22.EdgeType.RUNS_ON,
|
|
6386
|
+
provenance: import_types22.Provenance.EXTRACTED,
|
|
6387
|
+
confidence: (0, import_types22.confidenceForExtracted)("structural"),
|
|
5658
6388
|
evidence: {
|
|
5659
6389
|
file: evidenceFile,
|
|
5660
6390
|
...facts.entrypoint ? { snippet: facts.entrypoint.slice(0, 120) } : {}
|
|
@@ -5669,15 +6399,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
5669
6399
|
graph.addNode(portNode.id, portNode);
|
|
5670
6400
|
nodesAdded++;
|
|
5671
6401
|
}
|
|
5672
|
-
const portEdgeId = (0, import_types4.extractedEdgeId)(fileNodeId, portNode.id,
|
|
6402
|
+
const portEdgeId = (0, import_types4.extractedEdgeId)(fileNodeId, portNode.id, import_types22.EdgeType.CONNECTS_TO);
|
|
5673
6403
|
if (graph.hasEdge(portEdgeId)) continue;
|
|
5674
6404
|
const portEdge = {
|
|
5675
6405
|
id: portEdgeId,
|
|
5676
6406
|
source: fileNodeId,
|
|
5677
6407
|
target: portNode.id,
|
|
5678
|
-
type:
|
|
5679
|
-
provenance:
|
|
5680
|
-
confidence: (0,
|
|
6408
|
+
type: import_types22.EdgeType.CONNECTS_TO,
|
|
6409
|
+
provenance: import_types22.Provenance.EXTRACTED,
|
|
6410
|
+
confidence: (0, import_types22.confidenceForExtracted)("structural"),
|
|
5681
6411
|
evidence: { file: evidenceFile, snippet: `EXPOSE ${port}` }
|
|
5682
6412
|
};
|
|
5683
6413
|
graph.addEdgeWithKey(portEdgeId, portEdge.source, portEdge.target, portEdge);
|
|
@@ -5690,8 +6420,8 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
5690
6420
|
// src/extract/infra/terraform.ts
|
|
5691
6421
|
init_cjs_shims();
|
|
5692
6422
|
var import_node_fs16 = require("fs");
|
|
5693
|
-
var
|
|
5694
|
-
var
|
|
6423
|
+
var import_node_path32 = __toESM(require("path"), 1);
|
|
6424
|
+
var import_types23 = require("@neat.is/types");
|
|
5695
6425
|
var RESOURCE_RE = /resource\s+"(aws_[A-Za-z0-9_]+)"\s+"([A-Za-z0-9_-]+)"/g;
|
|
5696
6426
|
var REFERENCE_RE = /(?<![\w.])(aws_[A-Za-z0-9_]+)\.([A-Za-z0-9_-]+)/g;
|
|
5697
6427
|
async function walkTfFiles(start, depth = 0, max = 5) {
|
|
@@ -5701,11 +6431,11 @@ async function walkTfFiles(start, depth = 0, max = 5) {
|
|
|
5701
6431
|
for (const entry of entries) {
|
|
5702
6432
|
if (entry.isDirectory()) {
|
|
5703
6433
|
if (IGNORED_DIRS.has(entry.name) || entry.name === ".terraform") continue;
|
|
5704
|
-
const child =
|
|
6434
|
+
const child = import_node_path32.default.join(start, entry.name);
|
|
5705
6435
|
if (await isPythonVenvDir(child)) continue;
|
|
5706
6436
|
out.push(...await walkTfFiles(child, depth + 1, max));
|
|
5707
6437
|
} else if (entry.isFile() && entry.name.endsWith(".tf")) {
|
|
5708
|
-
out.push(
|
|
6438
|
+
out.push(import_node_path32.default.join(start, entry.name));
|
|
5709
6439
|
}
|
|
5710
6440
|
}
|
|
5711
6441
|
return out;
|
|
@@ -5737,7 +6467,7 @@ async function addTerraformResources(graph, scanPath) {
|
|
|
5737
6467
|
const files = await walkTfFiles(scanPath);
|
|
5738
6468
|
for (const file of files) {
|
|
5739
6469
|
const content = await import_node_fs16.promises.readFile(file, "utf8");
|
|
5740
|
-
const evidenceFile = toPosix2(
|
|
6470
|
+
const evidenceFile = toPosix2(import_node_path32.default.relative(scanPath, file));
|
|
5741
6471
|
const resources = [];
|
|
5742
6472
|
const byKey = /* @__PURE__ */ new Map();
|
|
5743
6473
|
RESOURCE_RE.lastIndex = 0;
|
|
@@ -5772,16 +6502,16 @@ async function addTerraformResources(graph, scanPath) {
|
|
|
5772
6502
|
if (!target) continue;
|
|
5773
6503
|
if (seen.has(target.nodeId)) continue;
|
|
5774
6504
|
seen.add(target.nodeId);
|
|
5775
|
-
const edgeId = (0, import_types4.extractedEdgeId)(resource.nodeId, target.nodeId,
|
|
6505
|
+
const edgeId = (0, import_types4.extractedEdgeId)(resource.nodeId, target.nodeId, import_types23.EdgeType.DEPENDS_ON);
|
|
5776
6506
|
if (graph.hasEdge(edgeId)) continue;
|
|
5777
6507
|
const line = lineAt(content, resource.bodyOffset + ref.index);
|
|
5778
6508
|
const edge = {
|
|
5779
6509
|
id: edgeId,
|
|
5780
6510
|
source: resource.nodeId,
|
|
5781
6511
|
target: target.nodeId,
|
|
5782
|
-
type:
|
|
5783
|
-
provenance:
|
|
5784
|
-
confidence: (0,
|
|
6512
|
+
type: import_types23.EdgeType.DEPENDS_ON,
|
|
6513
|
+
provenance: import_types23.Provenance.EXTRACTED,
|
|
6514
|
+
confidence: (0, import_types23.confidenceForExtracted)("structural"),
|
|
5785
6515
|
evidence: { file: evidenceFile, line, snippet: key }
|
|
5786
6516
|
};
|
|
5787
6517
|
graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
|
|
@@ -5795,7 +6525,7 @@ async function addTerraformResources(graph, scanPath) {
|
|
|
5795
6525
|
// src/extract/infra/k8s.ts
|
|
5796
6526
|
init_cjs_shims();
|
|
5797
6527
|
var import_node_fs17 = require("fs");
|
|
5798
|
-
var
|
|
6528
|
+
var import_node_path33 = __toESM(require("path"), 1);
|
|
5799
6529
|
var import_yaml3 = require("yaml");
|
|
5800
6530
|
var K8S_KIND_TO_INFRA_KIND = {
|
|
5801
6531
|
Service: "k8s-service",
|
|
@@ -5813,11 +6543,11 @@ async function walkYamlFiles2(start, depth = 0, max = 5) {
|
|
|
5813
6543
|
for (const entry of entries) {
|
|
5814
6544
|
if (entry.isDirectory()) {
|
|
5815
6545
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
5816
|
-
const child =
|
|
6546
|
+
const child = import_node_path33.default.join(start, entry.name);
|
|
5817
6547
|
if (await isPythonVenvDir(child)) continue;
|
|
5818
6548
|
out.push(...await walkYamlFiles2(child, depth + 1, max));
|
|
5819
|
-
} else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(
|
|
5820
|
-
out.push(
|
|
6549
|
+
} else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path33.default.extname(entry.name))) {
|
|
6550
|
+
out.push(import_node_path33.default.join(start, entry.name));
|
|
5821
6551
|
}
|
|
5822
6552
|
}
|
|
5823
6553
|
return out;
|
|
@@ -5861,17 +6591,17 @@ async function addInfra(graph, scanPath, services) {
|
|
|
5861
6591
|
}
|
|
5862
6592
|
|
|
5863
6593
|
// src/extract/index.ts
|
|
5864
|
-
var
|
|
6594
|
+
var import_node_path35 = __toESM(require("path"), 1);
|
|
5865
6595
|
|
|
5866
6596
|
// src/extract/retire.ts
|
|
5867
6597
|
init_cjs_shims();
|
|
5868
6598
|
var import_node_fs18 = require("fs");
|
|
5869
|
-
var
|
|
5870
|
-
var
|
|
6599
|
+
var import_node_path34 = __toESM(require("path"), 1);
|
|
6600
|
+
var import_types24 = require("@neat.is/types");
|
|
5871
6601
|
function dropOrphanedFileNodes(graph) {
|
|
5872
6602
|
const orphans = [];
|
|
5873
6603
|
graph.forEachNode((id, attrs) => {
|
|
5874
|
-
if (attrs.type !==
|
|
6604
|
+
if (attrs.type !== import_types24.NodeType.FileNode) return;
|
|
5875
6605
|
if (graph.inboundEdges(id).length === 0 && graph.outboundEdges(id).length === 0) {
|
|
5876
6606
|
orphans.push(id);
|
|
5877
6607
|
}
|
|
@@ -5884,14 +6614,14 @@ function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = []) {
|
|
|
5884
6614
|
const bases = [scanPath, ...serviceDirs];
|
|
5885
6615
|
graph.forEachEdge((id, attrs) => {
|
|
5886
6616
|
const edge = attrs;
|
|
5887
|
-
if (edge.provenance !==
|
|
6617
|
+
if (edge.provenance !== import_types24.Provenance.EXTRACTED) return;
|
|
5888
6618
|
const evidenceFile = edge.evidence?.file;
|
|
5889
6619
|
if (!evidenceFile) return;
|
|
5890
|
-
if (
|
|
6620
|
+
if (import_node_path34.default.isAbsolute(evidenceFile)) {
|
|
5891
6621
|
if (!(0, import_node_fs18.existsSync)(evidenceFile)) toDrop.push(id);
|
|
5892
6622
|
return;
|
|
5893
6623
|
}
|
|
5894
|
-
const found = bases.some((base) => (0, import_node_fs18.existsSync)(
|
|
6624
|
+
const found = bases.some((base) => (0, import_node_fs18.existsSync)(import_node_path34.default.join(base, evidenceFile)));
|
|
5895
6625
|
if (!found) toDrop.push(id);
|
|
5896
6626
|
});
|
|
5897
6627
|
for (const id of toDrop) graph.dropEdge(id);
|
|
@@ -5910,6 +6640,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
5910
6640
|
const importGraph = await addImports(graph, services);
|
|
5911
6641
|
const phase2 = await addDatabasesAndCompat(graph, services, scanPath);
|
|
5912
6642
|
const phase3 = await addConfigNodes(graph, services, scanPath);
|
|
6643
|
+
const routePhase = await addRoutes(graph, services);
|
|
5913
6644
|
const phase4 = await addCallEdges(graph, services);
|
|
5914
6645
|
const phase5 = await addInfra(graph, scanPath, services);
|
|
5915
6646
|
const ghostsRetired = retireExtractedEdgesByMissingFile(
|
|
@@ -5931,7 +6662,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
5931
6662
|
}
|
|
5932
6663
|
const droppedEntries = drainDroppedExtracted();
|
|
5933
6664
|
if (isRejectedLogEnabled() && opts.errorsPath && droppedEntries.length > 0) {
|
|
5934
|
-
const rejectedPath =
|
|
6665
|
+
const rejectedPath = import_node_path35.default.join(import_node_path35.default.dirname(opts.errorsPath), "rejected.ndjson");
|
|
5935
6666
|
try {
|
|
5936
6667
|
await writeRejectedExtracted(droppedEntries, rejectedPath);
|
|
5937
6668
|
} catch (err) {
|
|
@@ -5941,8 +6672,8 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
5941
6672
|
}
|
|
5942
6673
|
}
|
|
5943
6674
|
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,
|
|
6675
|
+
nodesAdded: phase1Nodes + fileEnum.nodesAdded + importGraph.nodesAdded + phase2.nodesAdded + phase3.nodesAdded + routePhase.nodesAdded + phase4.nodesAdded + phase5.nodesAdded,
|
|
6676
|
+
edgesAdded: fileEnum.edgesAdded + importGraph.edgesAdded + phase2.edgesAdded + phase3.edgesAdded + routePhase.edgesAdded + phase4.edgesAdded + phase5.edgesAdded,
|
|
5946
6677
|
frontiersPromoted,
|
|
5947
6678
|
extractionErrors: errorEntries.length,
|
|
5948
6679
|
errorEntries,
|
|
@@ -5966,8 +6697,8 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
5966
6697
|
// src/persist.ts
|
|
5967
6698
|
init_cjs_shims();
|
|
5968
6699
|
var import_node_fs19 = require("fs");
|
|
5969
|
-
var
|
|
5970
|
-
var
|
|
6700
|
+
var import_node_path36 = __toESM(require("path"), 1);
|
|
6701
|
+
var import_types25 = require("@neat.is/types");
|
|
5971
6702
|
var SCHEMA_VERSION = 4;
|
|
5972
6703
|
function migrateV1ToV2(payload) {
|
|
5973
6704
|
const nodes = payload.graph.nodes;
|
|
@@ -5989,12 +6720,12 @@ function migrateV2ToV3(payload) {
|
|
|
5989
6720
|
for (const edge of edges) {
|
|
5990
6721
|
const attrs = edge.attributes;
|
|
5991
6722
|
if (!attrs || attrs.provenance !== "FRONTIER") continue;
|
|
5992
|
-
attrs.provenance =
|
|
6723
|
+
attrs.provenance = import_types25.Provenance.OBSERVED;
|
|
5993
6724
|
const type = typeof attrs.type === "string" ? attrs.type : void 0;
|
|
5994
6725
|
const source = typeof attrs.source === "string" ? attrs.source : void 0;
|
|
5995
6726
|
const target = typeof attrs.target === "string" ? attrs.target : void 0;
|
|
5996
6727
|
if (type && source && target) {
|
|
5997
|
-
const newId = (0,
|
|
6728
|
+
const newId = (0, import_types25.observedEdgeId)(source, target, type);
|
|
5998
6729
|
attrs.id = newId;
|
|
5999
6730
|
if (edge.key) edge.key = newId;
|
|
6000
6731
|
}
|
|
@@ -6003,7 +6734,7 @@ function migrateV2ToV3(payload) {
|
|
|
6003
6734
|
return { ...payload, schemaVersion: 3 };
|
|
6004
6735
|
}
|
|
6005
6736
|
async function ensureDir(filePath) {
|
|
6006
|
-
await import_node_fs19.promises.mkdir(
|
|
6737
|
+
await import_node_fs19.promises.mkdir(import_node_path36.default.dirname(filePath), { recursive: true });
|
|
6007
6738
|
}
|
|
6008
6739
|
async function saveGraphToDisk(graph, outPath) {
|
|
6009
6740
|
await ensureDir(outPath);
|
|
@@ -6086,19 +6817,19 @@ function startPersistLoop(graph, outPath, opts = {}) {
|
|
|
6086
6817
|
init_cjs_shims();
|
|
6087
6818
|
var import_fastify = __toESM(require("fastify"), 1);
|
|
6088
6819
|
var import_cors = __toESM(require("@fastify/cors"), 1);
|
|
6089
|
-
var
|
|
6820
|
+
var import_types28 = require("@neat.is/types");
|
|
6090
6821
|
|
|
6091
6822
|
// src/extend/index.ts
|
|
6092
6823
|
init_cjs_shims();
|
|
6093
6824
|
var import_node_fs21 = require("fs");
|
|
6094
|
-
var
|
|
6825
|
+
var import_node_path38 = __toESM(require("path"), 1);
|
|
6095
6826
|
var import_node_os2 = __toESM(require("os"), 1);
|
|
6096
6827
|
var import_instrumentation_registry = require("@neat.is/instrumentation-registry");
|
|
6097
6828
|
|
|
6098
6829
|
// src/installers/package-manager.ts
|
|
6099
6830
|
init_cjs_shims();
|
|
6100
6831
|
var import_node_fs20 = require("fs");
|
|
6101
|
-
var
|
|
6832
|
+
var import_node_path37 = __toESM(require("path"), 1);
|
|
6102
6833
|
var import_node_child_process = require("child_process");
|
|
6103
6834
|
var LOCKFILE_PRIORITY = [
|
|
6104
6835
|
{ lockfile: "bun.lockb", pm: "bun", args: ["install", "--no-summary"] },
|
|
@@ -6120,22 +6851,22 @@ async function exists2(p) {
|
|
|
6120
6851
|
}
|
|
6121
6852
|
}
|
|
6122
6853
|
async function detectPackageManager(serviceDir) {
|
|
6123
|
-
let dir =
|
|
6854
|
+
let dir = import_node_path37.default.resolve(serviceDir);
|
|
6124
6855
|
const stops = /* @__PURE__ */ new Set();
|
|
6125
6856
|
for (let i = 0; i < 64; i++) {
|
|
6126
6857
|
if (stops.has(dir)) break;
|
|
6127
6858
|
stops.add(dir);
|
|
6128
6859
|
for (const candidate of LOCKFILE_PRIORITY) {
|
|
6129
|
-
const lockPath =
|
|
6860
|
+
const lockPath = import_node_path37.default.join(dir, candidate.lockfile);
|
|
6130
6861
|
if (await exists2(lockPath)) {
|
|
6131
6862
|
return { pm: candidate.pm, cwd: dir, args: [...candidate.args] };
|
|
6132
6863
|
}
|
|
6133
6864
|
}
|
|
6134
|
-
const parent =
|
|
6865
|
+
const parent = import_node_path37.default.dirname(dir);
|
|
6135
6866
|
if (parent === dir) break;
|
|
6136
6867
|
dir = parent;
|
|
6137
6868
|
}
|
|
6138
|
-
return { pm: "npm", cwd:
|
|
6869
|
+
return { pm: "npm", cwd: import_node_path37.default.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
|
|
6139
6870
|
}
|
|
6140
6871
|
async function runPackageManagerInstall(cmd) {
|
|
6141
6872
|
return new Promise((resolve) => {
|
|
@@ -6184,7 +6915,7 @@ async function fileExists2(p) {
|
|
|
6184
6915
|
}
|
|
6185
6916
|
}
|
|
6186
6917
|
async function readPackageJson(scanPath) {
|
|
6187
|
-
const pkgPath =
|
|
6918
|
+
const pkgPath = import_node_path38.default.join(scanPath, "package.json");
|
|
6188
6919
|
const raw = await import_node_fs21.promises.readFile(pkgPath, "utf8");
|
|
6189
6920
|
return JSON.parse(raw);
|
|
6190
6921
|
}
|
|
@@ -6195,11 +6926,11 @@ async function findHookFiles(scanPath) {
|
|
|
6195
6926
|
).sort();
|
|
6196
6927
|
}
|
|
6197
6928
|
function extendLogPath() {
|
|
6198
|
-
return process.env.NEAT_EXTEND_LOG ??
|
|
6929
|
+
return process.env.NEAT_EXTEND_LOG ?? import_node_path38.default.join(import_node_os2.default.homedir(), ".neat", "extend-log.ndjson");
|
|
6199
6930
|
}
|
|
6200
6931
|
async function appendExtendLog(entry) {
|
|
6201
6932
|
const logPath = extendLogPath();
|
|
6202
|
-
await import_node_fs21.promises.mkdir(
|
|
6933
|
+
await import_node_fs21.promises.mkdir(import_node_path38.default.dirname(logPath), { recursive: true });
|
|
6203
6934
|
await import_node_fs21.promises.appendFile(logPath, JSON.stringify(entry) + "\n", "utf8");
|
|
6204
6935
|
}
|
|
6205
6936
|
function splicedContent(fileContent, snippet2) {
|
|
@@ -6258,7 +6989,7 @@ function lookupInstrumentation(library, installedVersion) {
|
|
|
6258
6989
|
}
|
|
6259
6990
|
async function describeProjectInstrumentation(ctx) {
|
|
6260
6991
|
const hookFiles = await findHookFiles(ctx.scanPath);
|
|
6261
|
-
const envNeat = await fileExists2(
|
|
6992
|
+
const envNeat = await fileExists2(import_node_path38.default.join(ctx.scanPath, ".env.neat"));
|
|
6262
6993
|
const registryInstrPackages = new Set(
|
|
6263
6994
|
(0, import_instrumentation_registry.list)().map((e) => e.instrumentation_package).filter((p) => !!p)
|
|
6264
6995
|
);
|
|
@@ -6280,16 +7011,16 @@ async function applyExtension(ctx, args, options) {
|
|
|
6280
7011
|
);
|
|
6281
7012
|
}
|
|
6282
7013
|
for (const file of hookFiles) {
|
|
6283
|
-
const content = await import_node_fs21.promises.readFile(
|
|
7014
|
+
const content = await import_node_fs21.promises.readFile(import_node_path38.default.join(ctx.scanPath, file), "utf8");
|
|
6284
7015
|
if (content.includes(args.registration_snippet)) {
|
|
6285
7016
|
return { library: args.library, filesTouched: [], depsAdded: [], installOutput: "", alreadyApplied: true };
|
|
6286
7017
|
}
|
|
6287
7018
|
}
|
|
6288
7019
|
const primaryFile = hookFiles[0];
|
|
6289
|
-
const primaryPath =
|
|
7020
|
+
const primaryPath = import_node_path38.default.join(ctx.scanPath, primaryFile);
|
|
6290
7021
|
const filesTouched = [];
|
|
6291
7022
|
const depsAdded = [];
|
|
6292
|
-
const pkgPath =
|
|
7023
|
+
const pkgPath = import_node_path38.default.join(ctx.scanPath, "package.json");
|
|
6293
7024
|
const pkg = await readPackageJson(ctx.scanPath);
|
|
6294
7025
|
if (!(pkg.dependencies ?? {})[args.instrumentation_package]) {
|
|
6295
7026
|
pkg.dependencies = { ...pkg.dependencies ?? {}, [args.instrumentation_package]: args.version };
|
|
@@ -6335,7 +7066,7 @@ async function dryRunExtension(ctx, args) {
|
|
|
6335
7066
|
};
|
|
6336
7067
|
}
|
|
6337
7068
|
for (const file of hookFiles) {
|
|
6338
|
-
const content = await import_node_fs21.promises.readFile(
|
|
7069
|
+
const content = await import_node_fs21.promises.readFile(import_node_path38.default.join(ctx.scanPath, file), "utf8");
|
|
6339
7070
|
if (content.includes(args.registration_snippet)) {
|
|
6340
7071
|
return {
|
|
6341
7072
|
library: args.library,
|
|
@@ -6357,7 +7088,7 @@ async function dryRunExtension(ctx, args) {
|
|
|
6357
7088
|
depsToAdd.push(`${args.instrumentation_package}@${args.version}`);
|
|
6358
7089
|
filesTouched.push("package.json");
|
|
6359
7090
|
}
|
|
6360
|
-
const hookContent = await import_node_fs21.promises.readFile(
|
|
7091
|
+
const hookContent = await import_node_fs21.promises.readFile(import_node_path38.default.join(ctx.scanPath, primaryFile), "utf8");
|
|
6361
7092
|
const patched = splicedContent(hookContent, args.registration_snippet);
|
|
6362
7093
|
if (patched) {
|
|
6363
7094
|
filesTouched.push(primaryFile);
|
|
@@ -6378,7 +7109,7 @@ async function rollbackExtension(ctx, args) {
|
|
|
6378
7109
|
if (!match) {
|
|
6379
7110
|
return { undone: false, message: "no apply found for library" };
|
|
6380
7111
|
}
|
|
6381
|
-
const pkgPath =
|
|
7112
|
+
const pkgPath = import_node_path38.default.join(ctx.scanPath, "package.json");
|
|
6382
7113
|
if (await fileExists2(pkgPath)) {
|
|
6383
7114
|
const pkg = await readPackageJson(ctx.scanPath);
|
|
6384
7115
|
if (pkg.dependencies?.[match.instrumentation_package]) {
|
|
@@ -6389,7 +7120,7 @@ async function rollbackExtension(ctx, args) {
|
|
|
6389
7120
|
}
|
|
6390
7121
|
const hookFiles = await findHookFiles(ctx.scanPath);
|
|
6391
7122
|
for (const file of hookFiles) {
|
|
6392
|
-
const filePath =
|
|
7123
|
+
const filePath = import_node_path38.default.join(ctx.scanPath, file);
|
|
6393
7124
|
const content = await import_node_fs21.promises.readFile(filePath, "utf8");
|
|
6394
7125
|
if (content.includes(match.registration_snippet)) {
|
|
6395
7126
|
const filtered = content.split("\n").filter((line) => !line.includes(match.registration_snippet)).join("\n");
|
|
@@ -6405,7 +7136,7 @@ async function rollbackExtension(ctx, args) {
|
|
|
6405
7136
|
|
|
6406
7137
|
// src/divergences.ts
|
|
6407
7138
|
init_cjs_shims();
|
|
6408
|
-
var
|
|
7139
|
+
var import_types26 = require("@neat.is/types");
|
|
6409
7140
|
function bucketKey(source, target, type) {
|
|
6410
7141
|
return `${type}|${source}|${target}`;
|
|
6411
7142
|
}
|
|
@@ -6413,22 +7144,22 @@ function bucketEdges(graph) {
|
|
|
6413
7144
|
const buckets = /* @__PURE__ */ new Map();
|
|
6414
7145
|
graph.forEachEdge((id, attrs) => {
|
|
6415
7146
|
const e = attrs;
|
|
6416
|
-
const parsed = (0,
|
|
7147
|
+
const parsed = (0, import_types26.parseEdgeId)(id);
|
|
6417
7148
|
const provenance = parsed?.provenance ?? e.provenance;
|
|
6418
7149
|
const key = bucketKey(e.source, e.target, e.type);
|
|
6419
7150
|
const cur = buckets.get(key) ?? { source: e.source, target: e.target, type: e.type };
|
|
6420
7151
|
switch (provenance) {
|
|
6421
|
-
case
|
|
7152
|
+
case import_types26.Provenance.EXTRACTED:
|
|
6422
7153
|
cur.extracted = e;
|
|
6423
7154
|
break;
|
|
6424
|
-
case
|
|
7155
|
+
case import_types26.Provenance.OBSERVED:
|
|
6425
7156
|
cur.observed = e;
|
|
6426
7157
|
break;
|
|
6427
|
-
case
|
|
7158
|
+
case import_types26.Provenance.INFERRED:
|
|
6428
7159
|
cur.inferred = e;
|
|
6429
7160
|
break;
|
|
6430
7161
|
default:
|
|
6431
|
-
if (e.provenance ===
|
|
7162
|
+
if (e.provenance === import_types26.Provenance.STALE) cur.stale = e;
|
|
6432
7163
|
}
|
|
6433
7164
|
buckets.set(key, cur);
|
|
6434
7165
|
});
|
|
@@ -6437,7 +7168,7 @@ function bucketEdges(graph) {
|
|
|
6437
7168
|
function nodeIsFrontier(graph, nodeId) {
|
|
6438
7169
|
if (!graph.hasNode(nodeId)) return false;
|
|
6439
7170
|
const attrs = graph.getNodeAttributes(nodeId);
|
|
6440
|
-
return attrs.type ===
|
|
7171
|
+
return attrs.type === import_types26.NodeType.FrontierNode;
|
|
6441
7172
|
}
|
|
6442
7173
|
function clampConfidence(n) {
|
|
6443
7174
|
if (!Number.isFinite(n)) return 0;
|
|
@@ -6457,14 +7188,14 @@ function gradedConfidence(edge) {
|
|
|
6457
7188
|
return clampConfidence(confidenceForEdge(edge));
|
|
6458
7189
|
}
|
|
6459
7190
|
var OBSERVABLE_EDGE_TYPES = /* @__PURE__ */ new Set([
|
|
6460
|
-
|
|
6461
|
-
|
|
6462
|
-
|
|
6463
|
-
|
|
7191
|
+
import_types26.EdgeType.CALLS,
|
|
7192
|
+
import_types26.EdgeType.CONNECTS_TO,
|
|
7193
|
+
import_types26.EdgeType.PUBLISHES_TO,
|
|
7194
|
+
import_types26.EdgeType.CONSUMES_FROM
|
|
6464
7195
|
]);
|
|
6465
7196
|
function detectMissingDivergences(graph, bucket) {
|
|
6466
7197
|
const out = [];
|
|
6467
|
-
if (bucket.type ===
|
|
7198
|
+
if (bucket.type === import_types26.EdgeType.CONTAINS) return out;
|
|
6468
7199
|
if (bucket.extracted && !bucket.observed && OBSERVABLE_EDGE_TYPES.has(bucket.type)) {
|
|
6469
7200
|
if (!nodeIsFrontier(graph, bucket.target)) {
|
|
6470
7201
|
out.push({
|
|
@@ -6505,7 +7236,7 @@ function declaredHostFor(svc) {
|
|
|
6505
7236
|
function hasExtractedConfiguredBy(graph, svcId) {
|
|
6506
7237
|
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
6507
7238
|
const e = graph.getEdgeAttributes(edgeId);
|
|
6508
|
-
if (e.type ===
|
|
7239
|
+
if (e.type === import_types26.EdgeType.CONFIGURED_BY && e.provenance === import_types26.Provenance.EXTRACTED) {
|
|
6509
7240
|
return true;
|
|
6510
7241
|
}
|
|
6511
7242
|
}
|
|
@@ -6518,10 +7249,10 @@ function detectHostMismatch(graph, svcId, svc) {
|
|
|
6518
7249
|
const out = [];
|
|
6519
7250
|
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
6520
7251
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
6521
|
-
if (edge.type !==
|
|
6522
|
-
if (edge.provenance !==
|
|
7252
|
+
if (edge.type !== import_types26.EdgeType.CONNECTS_TO) continue;
|
|
7253
|
+
if (edge.provenance !== import_types26.Provenance.OBSERVED) continue;
|
|
6523
7254
|
const target = graph.getNodeAttributes(edge.target);
|
|
6524
|
-
if (target.type !==
|
|
7255
|
+
if (target.type !== import_types26.NodeType.DatabaseNode) continue;
|
|
6525
7256
|
const observedHost = target.host?.trim();
|
|
6526
7257
|
if (!observedHost) continue;
|
|
6527
7258
|
if (observedHost === declaredHost) continue;
|
|
@@ -6543,10 +7274,10 @@ function detectCompatDivergences(graph, svcId, svc) {
|
|
|
6543
7274
|
const deps = svc.dependencies ?? {};
|
|
6544
7275
|
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
6545
7276
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
6546
|
-
if (edge.type !==
|
|
6547
|
-
if (edge.provenance !==
|
|
7277
|
+
if (edge.type !== import_types26.EdgeType.CONNECTS_TO) continue;
|
|
7278
|
+
if (edge.provenance !== import_types26.Provenance.OBSERVED) continue;
|
|
6548
7279
|
const target = graph.getNodeAttributes(edge.target);
|
|
6549
|
-
if (target.type !==
|
|
7280
|
+
if (target.type !== import_types26.NodeType.DatabaseNode) continue;
|
|
6550
7281
|
for (const pair of compatPairs()) {
|
|
6551
7282
|
if (pair.engine !== target.engine) continue;
|
|
6552
7283
|
const declared = deps[pair.driver];
|
|
@@ -6605,7 +7336,7 @@ function suppressHostMismatchHalves(all) {
|
|
|
6605
7336
|
for (const d of all) {
|
|
6606
7337
|
if (d.type !== "host-mismatch") continue;
|
|
6607
7338
|
observedHalf.add(`${d.source}->${d.target}`);
|
|
6608
|
-
declaredHalf.add((0,
|
|
7339
|
+
declaredHalf.add((0, import_types26.databaseId)(d.extractedHost));
|
|
6609
7340
|
}
|
|
6610
7341
|
if (observedHalf.size === 0) return all;
|
|
6611
7342
|
return all.filter((d) => {
|
|
@@ -6624,7 +7355,7 @@ function computeDivergences(graph, opts = {}) {
|
|
|
6624
7355
|
}
|
|
6625
7356
|
graph.forEachNode((nodeId, attrs) => {
|
|
6626
7357
|
const n = attrs;
|
|
6627
|
-
if (n.type !==
|
|
7358
|
+
if (n.type !== import_types26.NodeType.ServiceNode) return;
|
|
6628
7359
|
const svc = n;
|
|
6629
7360
|
for (const d of detectHostMismatch(graph, nodeId, svc)) all.push(d);
|
|
6630
7361
|
for (const d of detectCompatDivergences(graph, nodeId, svc)) all.push(d);
|
|
@@ -6658,7 +7389,7 @@ function computeDivergences(graph, opts = {}) {
|
|
|
6658
7389
|
if (a.source !== b.source) return a.source.localeCompare(b.source);
|
|
6659
7390
|
return a.target.localeCompare(b.target);
|
|
6660
7391
|
});
|
|
6661
|
-
return
|
|
7392
|
+
return import_types26.DivergenceResultSchema.parse({
|
|
6662
7393
|
divergences: filtered,
|
|
6663
7394
|
totalAffected: filtered.length,
|
|
6664
7395
|
computedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -6744,23 +7475,23 @@ function canonicalJson(value) {
|
|
|
6744
7475
|
|
|
6745
7476
|
// src/projects.ts
|
|
6746
7477
|
init_cjs_shims();
|
|
6747
|
-
var
|
|
7478
|
+
var import_node_path39 = __toESM(require("path"), 1);
|
|
6748
7479
|
function pathsForProject(project, baseDir) {
|
|
6749
7480
|
if (project === DEFAULT_PROJECT) {
|
|
6750
7481
|
return {
|
|
6751
|
-
snapshotPath:
|
|
6752
|
-
errorsPath:
|
|
6753
|
-
staleEventsPath:
|
|
6754
|
-
embeddingsCachePath:
|
|
6755
|
-
policyViolationsPath:
|
|
7482
|
+
snapshotPath: import_node_path39.default.join(baseDir, "graph.json"),
|
|
7483
|
+
errorsPath: import_node_path39.default.join(baseDir, "errors.ndjson"),
|
|
7484
|
+
staleEventsPath: import_node_path39.default.join(baseDir, "stale-events.ndjson"),
|
|
7485
|
+
embeddingsCachePath: import_node_path39.default.join(baseDir, "embeddings.json"),
|
|
7486
|
+
policyViolationsPath: import_node_path39.default.join(baseDir, "policy-violations.ndjson")
|
|
6756
7487
|
};
|
|
6757
7488
|
}
|
|
6758
7489
|
return {
|
|
6759
|
-
snapshotPath:
|
|
6760
|
-
errorsPath:
|
|
6761
|
-
staleEventsPath:
|
|
6762
|
-
embeddingsCachePath:
|
|
6763
|
-
policyViolationsPath:
|
|
7490
|
+
snapshotPath: import_node_path39.default.join(baseDir, `${project}.json`),
|
|
7491
|
+
errorsPath: import_node_path39.default.join(baseDir, `errors.${project}.ndjson`),
|
|
7492
|
+
staleEventsPath: import_node_path39.default.join(baseDir, `stale-events.${project}.ndjson`),
|
|
7493
|
+
embeddingsCachePath: import_node_path39.default.join(baseDir, `embeddings.${project}.json`),
|
|
7494
|
+
policyViolationsPath: import_node_path39.default.join(baseDir, `policy-violations.${project}.ndjson`)
|
|
6764
7495
|
};
|
|
6765
7496
|
}
|
|
6766
7497
|
var Projects = class {
|
|
@@ -6798,23 +7529,23 @@ var Projects = class {
|
|
|
6798
7529
|
init_cjs_shims();
|
|
6799
7530
|
var import_node_fs23 = require("fs");
|
|
6800
7531
|
var import_node_os3 = __toESM(require("os"), 1);
|
|
6801
|
-
var
|
|
6802
|
-
var
|
|
7532
|
+
var import_node_path40 = __toESM(require("path"), 1);
|
|
7533
|
+
var import_types27 = require("@neat.is/types");
|
|
6803
7534
|
var LOCK_TIMEOUT_MS = 5e3;
|
|
6804
7535
|
var LOCK_RETRY_MS = 50;
|
|
6805
7536
|
function neatHome() {
|
|
6806
7537
|
const override = process.env.NEAT_HOME;
|
|
6807
|
-
if (override && override.length > 0) return
|
|
6808
|
-
return
|
|
7538
|
+
if (override && override.length > 0) return import_node_path40.default.resolve(override);
|
|
7539
|
+
return import_node_path40.default.join(import_node_os3.default.homedir(), ".neat");
|
|
6809
7540
|
}
|
|
6810
7541
|
function registryPath() {
|
|
6811
|
-
return
|
|
7542
|
+
return import_node_path40.default.join(neatHome(), "projects.json");
|
|
6812
7543
|
}
|
|
6813
7544
|
function registryLockPath() {
|
|
6814
|
-
return
|
|
7545
|
+
return import_node_path40.default.join(neatHome(), "projects.json.lock");
|
|
6815
7546
|
}
|
|
6816
7547
|
function daemonPidPath() {
|
|
6817
|
-
return
|
|
7548
|
+
return import_node_path40.default.join(neatHome(), "neatd.pid");
|
|
6818
7549
|
}
|
|
6819
7550
|
function isPidAliveDefault(pid) {
|
|
6820
7551
|
try {
|
|
@@ -6874,7 +7605,7 @@ function lockHolderMessage(holder, lockPath, timeoutMs) {
|
|
|
6874
7605
|
}
|
|
6875
7606
|
}
|
|
6876
7607
|
async function normalizeProjectPath(input) {
|
|
6877
|
-
const resolved =
|
|
7608
|
+
const resolved = import_node_path40.default.resolve(input);
|
|
6878
7609
|
try {
|
|
6879
7610
|
return await import_node_fs23.promises.realpath(resolved);
|
|
6880
7611
|
} catch {
|
|
@@ -6882,7 +7613,7 @@ async function normalizeProjectPath(input) {
|
|
|
6882
7613
|
}
|
|
6883
7614
|
}
|
|
6884
7615
|
async function writeAtomically(target, contents) {
|
|
6885
|
-
await import_node_fs23.promises.mkdir(
|
|
7616
|
+
await import_node_fs23.promises.mkdir(import_node_path40.default.dirname(target), { recursive: true });
|
|
6886
7617
|
const tmp = `${target}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
|
|
6887
7618
|
const fd = await import_node_fs23.promises.open(tmp, "w");
|
|
6888
7619
|
try {
|
|
@@ -6895,7 +7626,7 @@ async function writeAtomically(target, contents) {
|
|
|
6895
7626
|
}
|
|
6896
7627
|
async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaultLockHolderProbe) {
|
|
6897
7628
|
const deadline = Date.now() + timeoutMs;
|
|
6898
|
-
await import_node_fs23.promises.mkdir(
|
|
7629
|
+
await import_node_fs23.promises.mkdir(import_node_path40.default.dirname(lockPath), { recursive: true });
|
|
6899
7630
|
let probedHolder = false;
|
|
6900
7631
|
while (true) {
|
|
6901
7632
|
try {
|
|
@@ -6948,10 +7679,10 @@ async function readRegistry() {
|
|
|
6948
7679
|
throw err;
|
|
6949
7680
|
}
|
|
6950
7681
|
const parsed = JSON.parse(raw);
|
|
6951
|
-
return
|
|
7682
|
+
return import_types27.RegistryFileSchema.parse(parsed);
|
|
6952
7683
|
}
|
|
6953
7684
|
async function writeRegistry(reg) {
|
|
6954
|
-
const validated =
|
|
7685
|
+
const validated = import_types27.RegistryFileSchema.parse(reg);
|
|
6955
7686
|
await writeAtomically(registryPath(), JSON.stringify(validated, null, 2) + "\n");
|
|
6956
7687
|
}
|
|
6957
7688
|
var ProjectNameCollisionError = class extends Error {
|
|
@@ -7292,11 +8023,11 @@ function registerRoutes(scope, ctx) {
|
|
|
7292
8023
|
const candidates = req.query.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
7293
8024
|
const parsed = [];
|
|
7294
8025
|
for (const c of candidates) {
|
|
7295
|
-
const r =
|
|
8026
|
+
const r = import_types28.DivergenceTypeSchema.safeParse(c);
|
|
7296
8027
|
if (!r.success) {
|
|
7297
8028
|
return reply.code(400).send({
|
|
7298
8029
|
error: `unknown divergence type "${c}"`,
|
|
7299
|
-
allowed:
|
|
8030
|
+
allowed: import_types28.DivergenceTypeSchema.options
|
|
7300
8031
|
});
|
|
7301
8032
|
}
|
|
7302
8033
|
parsed.push(r.data);
|
|
@@ -7515,7 +8246,7 @@ function registerRoutes(scope, ctx) {
|
|
|
7515
8246
|
const log = new PolicyViolationsLog(proj.paths.policyViolationsPath);
|
|
7516
8247
|
let violations = await log.readAll();
|
|
7517
8248
|
if (req.query.severity) {
|
|
7518
|
-
const sev =
|
|
8249
|
+
const sev = import_types28.PolicySeveritySchema.safeParse(req.query.severity);
|
|
7519
8250
|
if (!sev.success) {
|
|
7520
8251
|
return reply.code(400).send({
|
|
7521
8252
|
error: "invalid severity",
|
|
@@ -7554,7 +8285,7 @@ function registerRoutes(scope, ctx) {
|
|
|
7554
8285
|
scope.post("/policies/check", async (req, reply) => {
|
|
7555
8286
|
const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
|
|
7556
8287
|
if (!proj) return;
|
|
7557
|
-
const parsed =
|
|
8288
|
+
const parsed = import_types28.PoliciesCheckBodySchema.safeParse(req.body ?? {});
|
|
7558
8289
|
if (!parsed.success) {
|
|
7559
8290
|
return reply.code(400).send({
|
|
7560
8291
|
error: "invalid /policies/check body",
|
|
@@ -7817,7 +8548,7 @@ init_otel_grpc();
|
|
|
7817
8548
|
// src/daemon.ts
|
|
7818
8549
|
init_cjs_shims();
|
|
7819
8550
|
var import_node_fs25 = require("fs");
|
|
7820
|
-
var
|
|
8551
|
+
var import_node_path44 = __toESM(require("path"), 1);
|
|
7821
8552
|
var import_node_module = require("module");
|
|
7822
8553
|
init_otel();
|
|
7823
8554
|
init_auth();
|
|
@@ -7825,7 +8556,7 @@ init_auth();
|
|
|
7825
8556
|
// src/unrouted.ts
|
|
7826
8557
|
init_cjs_shims();
|
|
7827
8558
|
var import_node_fs24 = require("fs");
|
|
7828
|
-
var
|
|
8559
|
+
var import_node_path43 = __toESM(require("path"), 1);
|
|
7829
8560
|
function buildUnroutedSpanRecord(serviceName, traceId, now = /* @__PURE__ */ new Date()) {
|
|
7830
8561
|
return {
|
|
7831
8562
|
timestamp: now.toISOString(),
|
|
@@ -7835,34 +8566,34 @@ function buildUnroutedSpanRecord(serviceName, traceId, now = /* @__PURE__ */ new
|
|
|
7835
8566
|
};
|
|
7836
8567
|
}
|
|
7837
8568
|
async function appendUnroutedSpan(neatHome2, record) {
|
|
7838
|
-
const target =
|
|
8569
|
+
const target = import_node_path43.default.join(neatHome2, "errors.ndjson");
|
|
7839
8570
|
await import_node_fs24.promises.mkdir(neatHome2, { recursive: true });
|
|
7840
8571
|
await import_node_fs24.promises.appendFile(target, JSON.stringify(record) + "\n", "utf8");
|
|
7841
8572
|
}
|
|
7842
8573
|
function unroutedErrorsPath(neatHome2) {
|
|
7843
|
-
return
|
|
8574
|
+
return import_node_path43.default.join(neatHome2, "errors.ndjson");
|
|
7844
8575
|
}
|
|
7845
8576
|
|
|
7846
8577
|
// src/daemon.ts
|
|
7847
|
-
var
|
|
8578
|
+
var import_types29 = require("@neat.is/types");
|
|
7848
8579
|
function daemonJsonPath(scanPath) {
|
|
7849
|
-
return
|
|
8580
|
+
return import_node_path44.default.join(scanPath, "neat-out", "daemon.json");
|
|
7850
8581
|
}
|
|
7851
8582
|
function daemonsDiscoveryDir(home) {
|
|
7852
8583
|
const base = home && home.length > 0 ? home : neatHomeFromEnv();
|
|
7853
|
-
return
|
|
8584
|
+
return import_node_path44.default.join(base, "daemons");
|
|
7854
8585
|
}
|
|
7855
8586
|
function daemonDiscoveryPath(project, home) {
|
|
7856
|
-
return
|
|
8587
|
+
return import_node_path44.default.join(daemonsDiscoveryDir(home), `${sanitizeDiscoveryName(project)}.json`);
|
|
7857
8588
|
}
|
|
7858
8589
|
function sanitizeDiscoveryName(project) {
|
|
7859
8590
|
return project.replace(/[^A-Za-z0-9._-]/g, "_");
|
|
7860
8591
|
}
|
|
7861
8592
|
function neatHomeFromEnv() {
|
|
7862
8593
|
const env = process.env.NEAT_HOME;
|
|
7863
|
-
if (env && env.length > 0) return
|
|
8594
|
+
if (env && env.length > 0) return import_node_path44.default.resolve(env);
|
|
7864
8595
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
7865
|
-
return
|
|
8596
|
+
return import_node_path44.default.join(home, ".neat");
|
|
7866
8597
|
}
|
|
7867
8598
|
function resolveNeatVersion() {
|
|
7868
8599
|
if (process.env.NEAT_LOCAL_VERSION && process.env.NEAT_LOCAL_VERSION.length > 0) {
|
|
@@ -7903,17 +8634,21 @@ function teardownSlot(slot) {
|
|
|
7903
8634
|
slot.stopPersist();
|
|
7904
8635
|
} catch {
|
|
7905
8636
|
}
|
|
8637
|
+
try {
|
|
8638
|
+
slot.stopStaleness();
|
|
8639
|
+
} catch {
|
|
8640
|
+
}
|
|
7906
8641
|
try {
|
|
7907
8642
|
slot.detachEvents();
|
|
7908
8643
|
} catch {
|
|
7909
8644
|
}
|
|
7910
8645
|
}
|
|
7911
8646
|
function neatHomeFor(opts) {
|
|
7912
|
-
if (opts.neatHome && opts.neatHome.length > 0) return
|
|
8647
|
+
if (opts.neatHome && opts.neatHome.length > 0) return import_node_path44.default.resolve(opts.neatHome);
|
|
7913
8648
|
const env = process.env.NEAT_HOME;
|
|
7914
|
-
if (env && env.length > 0) return
|
|
8649
|
+
if (env && env.length > 0) return import_node_path44.default.resolve(env);
|
|
7915
8650
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
7916
|
-
return
|
|
8651
|
+
return import_node_path44.default.join(home, ".neat");
|
|
7917
8652
|
}
|
|
7918
8653
|
function routeSpanToProject(serviceName, projects) {
|
|
7919
8654
|
if (!serviceName) return DEFAULT_PROJECT;
|
|
@@ -7957,11 +8692,11 @@ function spanBelongsToSingleProject(graph, project, serviceName) {
|
|
|
7957
8692
|
if (!serviceName) return true;
|
|
7958
8693
|
if (serviceNameMatchesProject(serviceName, project)) return true;
|
|
7959
8694
|
return graph.someNode(
|
|
7960
|
-
(_id, attrs) => attrs.type ===
|
|
8695
|
+
(_id, attrs) => attrs.type === import_types29.NodeType.ServiceNode && attrs.name === serviceName
|
|
7961
8696
|
);
|
|
7962
8697
|
}
|
|
7963
8698
|
async function bootstrapProject(entry) {
|
|
7964
|
-
const paths = pathsForProject(entry.name,
|
|
8699
|
+
const paths = pathsForProject(entry.name, import_node_path44.default.join(entry.path, "neat-out"));
|
|
7965
8700
|
try {
|
|
7966
8701
|
const stat = await import_node_fs25.promises.stat(entry.path);
|
|
7967
8702
|
if (!stat.isDirectory()) {
|
|
@@ -7979,6 +8714,8 @@ async function bootstrapProject(entry) {
|
|
|
7979
8714
|
paths,
|
|
7980
8715
|
stopPersist: () => {
|
|
7981
8716
|
},
|
|
8717
|
+
stopStaleness: () => {
|
|
8718
|
+
},
|
|
7982
8719
|
detachEvents: () => {
|
|
7983
8720
|
},
|
|
7984
8721
|
status: "broken",
|
|
@@ -7993,6 +8730,10 @@ async function bootstrapProject(entry) {
|
|
|
7993
8730
|
try {
|
|
7994
8731
|
await extractFromDirectory(graph, entry.path);
|
|
7995
8732
|
const stopPersist = startPersistLoop(graph, outPath, { exitOnSignal: false });
|
|
8733
|
+
const stopStaleness = startStalenessLoop(graph, {
|
|
8734
|
+
staleEventsPath: paths.staleEventsPath,
|
|
8735
|
+
project: entry.name
|
|
8736
|
+
});
|
|
7996
8737
|
await touchLastSeen(entry.name).catch(() => {
|
|
7997
8738
|
});
|
|
7998
8739
|
return {
|
|
@@ -8001,6 +8742,7 @@ async function bootstrapProject(entry) {
|
|
|
8001
8742
|
outPath,
|
|
8002
8743
|
paths,
|
|
8003
8744
|
stopPersist,
|
|
8745
|
+
stopStaleness,
|
|
8004
8746
|
detachEvents,
|
|
8005
8747
|
status: "active"
|
|
8006
8748
|
};
|
|
@@ -8057,7 +8799,7 @@ async function startDaemon(opts = {}) {
|
|
|
8057
8799
|
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
8800
|
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
8801
|
const singleProject = projectArg;
|
|
8060
|
-
const singleProjectPath = singleProject && projectPathArg ?
|
|
8802
|
+
const singleProjectPath = singleProject && projectPathArg ? import_node_path44.default.resolve(projectPathArg) : null;
|
|
8061
8803
|
if (singleProject && !singleProjectPath) {
|
|
8062
8804
|
throw new Error(
|
|
8063
8805
|
`neatd: project "${singleProject}" given without a projectPath; pass NEAT_PROJECT_PATH alongside NEAT_PROJECT.`
|
|
@@ -8072,7 +8814,7 @@ async function startDaemon(opts = {}) {
|
|
|
8072
8814
|
);
|
|
8073
8815
|
}
|
|
8074
8816
|
}
|
|
8075
|
-
const pidPath =
|
|
8817
|
+
const pidPath = import_node_path44.default.join(home, "neatd.pid");
|
|
8076
8818
|
await writeAtomically(pidPath, `${process.pid}
|
|
8077
8819
|
`);
|
|
8078
8820
|
const slots = /* @__PURE__ */ new Map();
|
|
@@ -8474,8 +9216,8 @@ async function startDaemon(opts = {}) {
|
|
|
8474
9216
|
let registryWatcher = null;
|
|
8475
9217
|
let reloadTimer = null;
|
|
8476
9218
|
if (!singleProject) try {
|
|
8477
|
-
const regDir =
|
|
8478
|
-
const regBase =
|
|
9219
|
+
const regDir = import_node_path44.default.dirname(regPath);
|
|
9220
|
+
const regBase = import_node_path44.default.basename(regPath);
|
|
8479
9221
|
registryWatcher = (0, import_node_fs25.watch)(regDir, (_eventType, filename) => {
|
|
8480
9222
|
if (filename !== null && filename !== regBase) return;
|
|
8481
9223
|
if (reloadTimer) clearTimeout(reloadTimer);
|