@neat.is/core 0.4.26-dev.20260702 → 0.4.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-ZX7PCMGZ.js → chunk-A3322JYS.js} +57 -3
- package/dist/chunk-A3322JYS.js.map +1 -0
- package/dist/{chunk-XOOCA5T7.js → chunk-BIY46Q6U.js} +25 -1
- package/dist/chunk-BIY46Q6U.js.map +1 -0
- package/dist/{chunk-O25KZNZK.js → chunk-QM6BMPVJ.js} +1200 -280
- package/dist/chunk-QM6BMPVJ.js.map +1 -0
- package/dist/{chunk-GAFTW2OX.js → chunk-UV5WSM7M.js} +2 -2
- package/dist/{chunk-IABNGQT2.js → chunk-XV4D7A3Z.js} +117 -6
- package/dist/chunk-XV4D7A3Z.js.map +1 -0
- package/dist/cli.cjs +1565 -546
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +49 -6
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +1382 -326
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +102 -1
- package/dist/index.d.ts +102 -1
- package/dist/index.js +4 -4
- package/dist/neatd.cjs +1543 -395
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +3 -3
- package/dist/{otel-grpc-QYHUJ4OY.js → otel-grpc-ET5Z6KI6.js} +3 -3
- package/dist/server.cjs +1206 -235
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +4 -4
- package/package.json +2 -2
- package/dist/chunk-IABNGQT2.js.map +0 -1
- package/dist/chunk-O25KZNZK.js.map +0 -1
- package/dist/chunk-XOOCA5T7.js.map +0 -1
- package/dist/chunk-ZX7PCMGZ.js.map +0 -1
- /package/dist/{chunk-GAFTW2OX.js.map → chunk-UV5WSM7M.js.map} +0 -0
- /package/dist/{otel-grpc-QYHUJ4OY.js.map → otel-grpc-ET5Z6KI6.js.map} +0 -0
package/dist/server.cjs
CHANGED
|
@@ -56,9 +56,9 @@ function mountBearerAuth(app, opts) {
|
|
|
56
56
|
const suffixes = [...DEFAULT_UNAUTH_SUFFIXES, ...opts.extraUnauthenticatedSuffixes ?? []];
|
|
57
57
|
const publicRead = opts.publicRead === true;
|
|
58
58
|
app.addHook("preHandler", (req, reply, done) => {
|
|
59
|
-
const
|
|
59
|
+
const path46 = (req.url.split("?")[0] ?? "").replace(/\/+$/, "");
|
|
60
60
|
for (const suffix of suffixes) {
|
|
61
|
-
if (
|
|
61
|
+
if (path46 === suffix || path46.endsWith(suffix)) {
|
|
62
62
|
done();
|
|
63
63
|
return;
|
|
64
64
|
}
|
|
@@ -69,11 +69,13 @@ function mountBearerAuth(app, opts) {
|
|
|
69
69
|
}
|
|
70
70
|
const header = req.headers.authorization;
|
|
71
71
|
if (typeof header !== "string" || !header.startsWith("Bearer ")) {
|
|
72
|
+
opts.onReject?.();
|
|
72
73
|
void reply.code(401).send({ error: "unauthorized" });
|
|
73
74
|
return;
|
|
74
75
|
}
|
|
75
76
|
const provided = Buffer.from(header.slice("Bearer ".length).trim(), "utf8");
|
|
76
77
|
if (provided.length !== expected.length || !(0, import_node_crypto.timingSafeEqual)(provided, expected)) {
|
|
78
|
+
opts.onReject?.();
|
|
77
79
|
void reply.code(401).send({ error: "unauthorized" });
|
|
78
80
|
return;
|
|
79
81
|
}
|
|
@@ -186,8 +188,8 @@ function reshapeGrpcRequest(req) {
|
|
|
186
188
|
};
|
|
187
189
|
}
|
|
188
190
|
function resolveProtoRoot() {
|
|
189
|
-
const here =
|
|
190
|
-
return
|
|
191
|
+
const here = import_node_path42.default.dirname((0, import_node_url.fileURLToPath)(importMetaUrl));
|
|
192
|
+
return import_node_path42.default.resolve(here, "..", "proto");
|
|
191
193
|
}
|
|
192
194
|
function loadTraceService() {
|
|
193
195
|
const protoRoot = resolveProtoRoot();
|
|
@@ -255,13 +257,13 @@ async function startOtelGrpcReceiver(opts) {
|
|
|
255
257
|
})
|
|
256
258
|
};
|
|
257
259
|
}
|
|
258
|
-
var import_node_url,
|
|
260
|
+
var import_node_url, import_node_path42, import_node_crypto2, grpc, protoLoader;
|
|
259
261
|
var init_otel_grpc = __esm({
|
|
260
262
|
"src/otel-grpc.ts"() {
|
|
261
263
|
"use strict";
|
|
262
264
|
init_cjs_shims();
|
|
263
265
|
import_node_url = require("url");
|
|
264
|
-
|
|
266
|
+
import_node_path42 = __toESM(require("path"), 1);
|
|
265
267
|
import_node_crypto2 = require("crypto");
|
|
266
268
|
grpc = __toESM(require("@grpc/grpc-js"), 1);
|
|
267
269
|
protoLoader = __toESM(require("@grpc/proto-loader"), 1);
|
|
@@ -334,6 +336,36 @@ function pickEnv(spanAttrs, resourceAttrs) {
|
|
|
334
336
|
}
|
|
335
337
|
return ENV_FALLBACK;
|
|
336
338
|
}
|
|
339
|
+
function messagingDestinationOf(attrs) {
|
|
340
|
+
for (const key of ["messaging.destination.name", "messaging.destination"]) {
|
|
341
|
+
const v = attrs[key];
|
|
342
|
+
if (typeof v === "string" && v.length > 0) return v;
|
|
343
|
+
}
|
|
344
|
+
return void 0;
|
|
345
|
+
}
|
|
346
|
+
function hasWebsocketUpgradeHeader(attrs) {
|
|
347
|
+
const v = attrs["http.request.header.upgrade"];
|
|
348
|
+
const matches = (s) => typeof s === "string" && s.trim().toLowerCase() === "websocket";
|
|
349
|
+
if (Array.isArray(v)) return v.some(matches);
|
|
350
|
+
return matches(v);
|
|
351
|
+
}
|
|
352
|
+
function websocketChannelPathOf(attrs) {
|
|
353
|
+
const route = attrs["http.route"];
|
|
354
|
+
if (typeof route === "string" && route.length > 0) return route;
|
|
355
|
+
for (const key of ["url.path", "http.target"]) {
|
|
356
|
+
const v = attrs[key];
|
|
357
|
+
if (typeof v === "string" && v.length > 0) {
|
|
358
|
+
const q = v.indexOf("?");
|
|
359
|
+
const path46 = q === -1 ? v : v.slice(0, q);
|
|
360
|
+
if (path46.length > 0) return path46;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
return void 0;
|
|
364
|
+
}
|
|
365
|
+
function websocketChannelOf(attrs) {
|
|
366
|
+
if (!hasWebsocketUpgradeHeader(attrs)) return void 0;
|
|
367
|
+
return websocketChannelPathOf(attrs);
|
|
368
|
+
}
|
|
337
369
|
function parseOtlpRequest(body) {
|
|
338
370
|
const out = [];
|
|
339
371
|
for (const rs of body.resourceSpans ?? []) {
|
|
@@ -360,6 +392,14 @@ function parseOtlpRequest(body) {
|
|
|
360
392
|
attributes: attrs,
|
|
361
393
|
dbSystem: typeof attrs["db.system"] === "string" ? attrs["db.system"] : void 0,
|
|
362
394
|
dbName: typeof attrs["db.name"] === "string" ? attrs["db.name"] : void 0,
|
|
395
|
+
messagingSystem: typeof attrs["messaging.system"] === "string" ? attrs["messaging.system"] : void 0,
|
|
396
|
+
messagingDestination: messagingDestinationOf(attrs),
|
|
397
|
+
graphqlOperationName: typeof attrs["graphql.operation.name"] === "string" && attrs["graphql.operation.name"].length > 0 ? attrs["graphql.operation.name"] : void 0,
|
|
398
|
+
graphqlOperationType: typeof attrs["graphql.operation.type"] === "string" && attrs["graphql.operation.type"].length > 0 ? attrs["graphql.operation.type"] : void 0,
|
|
399
|
+
rpcSystem: typeof attrs["rpc.system"] === "string" && attrs["rpc.system"].length > 0 ? attrs["rpc.system"] : void 0,
|
|
400
|
+
rpcService: typeof attrs["rpc.service"] === "string" && attrs["rpc.service"].length > 0 ? attrs["rpc.service"] : void 0,
|
|
401
|
+
rpcMethod: typeof attrs["rpc.method"] === "string" && attrs["rpc.method"].length > 0 ? attrs["rpc.method"] : void 0,
|
|
402
|
+
websocketChannel: websocketChannelOf(attrs),
|
|
363
403
|
statusCode: span.status?.code,
|
|
364
404
|
errorMessage: span.status?.message,
|
|
365
405
|
exception: extractExceptionFromEvents(span.events)
|
|
@@ -371,10 +411,10 @@ function parseOtlpRequest(body) {
|
|
|
371
411
|
return out;
|
|
372
412
|
}
|
|
373
413
|
function loadProtoRoot() {
|
|
374
|
-
const here =
|
|
375
|
-
const protoRoot =
|
|
414
|
+
const here = import_node_path43.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
|
|
415
|
+
const protoRoot = import_node_path43.default.resolve(here, "..", "proto");
|
|
376
416
|
const root = new import_protobufjs.default.Root();
|
|
377
|
-
root.resolvePath = (_origin, target) =>
|
|
417
|
+
root.resolvePath = (_origin, target) => import_node_path43.default.resolve(protoRoot, target);
|
|
378
418
|
root.loadSync(
|
|
379
419
|
"opentelemetry/proto/collector/trace/v1/trace_service.proto",
|
|
380
420
|
{ keepCase: true }
|
|
@@ -419,7 +459,21 @@ async function buildOtelReceiver(opts) {
|
|
|
419
459
|
logger: false,
|
|
420
460
|
bodyLimit: opts.bodyLimit ?? 16 * 1024 * 1024
|
|
421
461
|
});
|
|
422
|
-
|
|
462
|
+
const REJECT_WARN_INTERVAL_MS = 6e4;
|
|
463
|
+
let lastRejectWarnAt = 0;
|
|
464
|
+
const warnRejectedOtlp = () => {
|
|
465
|
+
const now = Date.now();
|
|
466
|
+
if (now - lastRejectWarnAt < REJECT_WARN_INTERVAL_MS) return;
|
|
467
|
+
lastRejectWarnAt = now;
|
|
468
|
+
console.warn(
|
|
469
|
+
"[neatd] rejecting OTLP spans on /v1/traces \u2014 missing or invalid bearer token (set NEAT_OTEL_TOKEN on the instrumented app)"
|
|
470
|
+
);
|
|
471
|
+
};
|
|
472
|
+
mountBearerAuth(app, {
|
|
473
|
+
token: opts.authToken,
|
|
474
|
+
trustProxy: opts.trustProxy,
|
|
475
|
+
onReject: warnRejectedOtlp
|
|
476
|
+
});
|
|
423
477
|
const queue = [];
|
|
424
478
|
let draining = false;
|
|
425
479
|
let drainPromise = Promise.resolve();
|
|
@@ -572,12 +626,12 @@ async function buildOtelReceiver(opts) {
|
|
|
572
626
|
};
|
|
573
627
|
return decorated;
|
|
574
628
|
}
|
|
575
|
-
var
|
|
629
|
+
var import_node_path43, import_node_url2, import_fastify2, import_protobufjs, ENV_ATTR_CANONICAL, ENV_ATTR_COMPAT, ENV_FALLBACK, exportTraceServiceRequestType, exportTraceServiceResponseType, cachedProtobufResponseBody;
|
|
576
630
|
var init_otel = __esm({
|
|
577
631
|
"src/otel.ts"() {
|
|
578
632
|
"use strict";
|
|
579
633
|
init_cjs_shims();
|
|
580
|
-
|
|
634
|
+
import_node_path43 = __toESM(require("path"), 1);
|
|
581
635
|
import_node_url2 = require("url");
|
|
582
636
|
import_fastify2 = __toESM(require("fastify"), 1);
|
|
583
637
|
import_protobufjs = __toESM(require("protobufjs"), 1);
|
|
@@ -593,7 +647,7 @@ var init_otel = __esm({
|
|
|
593
647
|
|
|
594
648
|
// src/server.ts
|
|
595
649
|
init_cjs_shims();
|
|
596
|
-
var
|
|
650
|
+
var import_node_path45 = __toESM(require("path"), 1);
|
|
597
651
|
|
|
598
652
|
// src/graph.ts
|
|
599
653
|
init_cjs_shims();
|
|
@@ -617,7 +671,7 @@ function getGraph(project = DEFAULT_PROJECT) {
|
|
|
617
671
|
init_cjs_shims();
|
|
618
672
|
var import_fastify = __toESM(require("fastify"), 1);
|
|
619
673
|
var import_cors = __toESM(require("@fastify/cors"), 1);
|
|
620
|
-
var
|
|
674
|
+
var import_types29 = require("@neat.is/types");
|
|
621
675
|
|
|
622
676
|
// src/extend/index.ts
|
|
623
677
|
init_cjs_shims();
|
|
@@ -1360,19 +1414,19 @@ function confidenceFromMix(edges, now = Date.now()) {
|
|
|
1360
1414
|
function longestIncomingWalk(graph, start, maxDepth) {
|
|
1361
1415
|
let best = { path: [start], edges: [] };
|
|
1362
1416
|
const visited = /* @__PURE__ */ new Set([start]);
|
|
1363
|
-
function step(node,
|
|
1364
|
-
if (
|
|
1365
|
-
best = { path: [...
|
|
1417
|
+
function step(node, path46, edges) {
|
|
1418
|
+
if (path46.length > best.path.length) {
|
|
1419
|
+
best = { path: [...path46], edges: [...edges] };
|
|
1366
1420
|
}
|
|
1367
|
-
if (
|
|
1421
|
+
if (path46.length - 1 >= maxDepth) return;
|
|
1368
1422
|
const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
|
|
1369
1423
|
for (const [srcId, edge] of incoming) {
|
|
1370
1424
|
if (visited.has(srcId)) continue;
|
|
1371
1425
|
visited.add(srcId);
|
|
1372
|
-
|
|
1426
|
+
path46.push(srcId);
|
|
1373
1427
|
edges.push(edge);
|
|
1374
|
-
step(srcId,
|
|
1375
|
-
|
|
1428
|
+
step(srcId, path46, edges);
|
|
1429
|
+
path46.pop();
|
|
1376
1430
|
edges.pop();
|
|
1377
1431
|
visited.delete(srcId);
|
|
1378
1432
|
}
|
|
@@ -1380,14 +1434,14 @@ function longestIncomingWalk(graph, start, maxDepth) {
|
|
|
1380
1434
|
step(start, [start], []);
|
|
1381
1435
|
return best;
|
|
1382
1436
|
}
|
|
1383
|
-
function databaseRootCauseShape(graph, origin,
|
|
1437
|
+
function databaseRootCauseShape(graph, origin, walk3) {
|
|
1384
1438
|
const targetDb = origin;
|
|
1385
1439
|
const candidatePairs = compatPairs().filter((p) => p.engine === targetDb.engine);
|
|
1386
1440
|
if (candidatePairs.length === 0) return null;
|
|
1387
|
-
for (const id of
|
|
1441
|
+
for (const id of walk3.path) {
|
|
1388
1442
|
const owner = resolveOwningService(graph, id);
|
|
1389
1443
|
if (!owner) continue;
|
|
1390
|
-
const { id:
|
|
1444
|
+
const { id: serviceId4, svc } = owner;
|
|
1391
1445
|
const deps = svc.dependencies ?? {};
|
|
1392
1446
|
for (const pair of candidatePairs) {
|
|
1393
1447
|
const declared = deps[pair.driver];
|
|
@@ -1400,7 +1454,7 @@ function databaseRootCauseShape(graph, origin, walk) {
|
|
|
1400
1454
|
);
|
|
1401
1455
|
if (!result.compatible) {
|
|
1402
1456
|
return {
|
|
1403
|
-
rootCauseNode:
|
|
1457
|
+
rootCauseNode: serviceId4,
|
|
1404
1458
|
rootCauseReason: result.reason ?? "incompatible driver",
|
|
1405
1459
|
...result.minDriverVersion ? {
|
|
1406
1460
|
fixRecommendation: `Upgrade ${svc.name} ${pair.driver} driver to >= ${result.minDriverVersion}`
|
|
@@ -1411,11 +1465,11 @@ function databaseRootCauseShape(graph, origin, walk) {
|
|
|
1411
1465
|
}
|
|
1412
1466
|
return null;
|
|
1413
1467
|
}
|
|
1414
|
-
function serviceRootCauseShape(graph, _origin,
|
|
1415
|
-
for (const id of
|
|
1468
|
+
function serviceRootCauseShape(graph, _origin, walk3) {
|
|
1469
|
+
for (const id of walk3.path) {
|
|
1416
1470
|
const owner = resolveOwningService(graph, id);
|
|
1417
1471
|
if (!owner) continue;
|
|
1418
|
-
const { id:
|
|
1472
|
+
const { id: serviceId4, svc } = owner;
|
|
1419
1473
|
const deps = svc.dependencies ?? {};
|
|
1420
1474
|
const serviceNodeEngine = svc.nodeEngine;
|
|
1421
1475
|
for (const constraint of nodeEngineConstraints()) {
|
|
@@ -1424,7 +1478,7 @@ function serviceRootCauseShape(graph, _origin, walk) {
|
|
|
1424
1478
|
const result = checkNodeEngineConstraint(constraint, declared, serviceNodeEngine);
|
|
1425
1479
|
if (!result.compatible && result.reason) {
|
|
1426
1480
|
return {
|
|
1427
|
-
rootCauseNode:
|
|
1481
|
+
rootCauseNode: serviceId4,
|
|
1428
1482
|
rootCauseReason: result.reason,
|
|
1429
1483
|
...result.requiredNodeVersion ? {
|
|
1430
1484
|
fixRecommendation: `Bump ${svc.name}'s engines.node to >= ${result.requiredNodeVersion}`
|
|
@@ -1439,7 +1493,7 @@ function serviceRootCauseShape(graph, _origin, walk) {
|
|
|
1439
1493
|
const result = checkPackageConflict(conflict, declared, requiredDeclared);
|
|
1440
1494
|
if (!result.compatible && result.reason) {
|
|
1441
1495
|
return {
|
|
1442
|
-
rootCauseNode:
|
|
1496
|
+
rootCauseNode: serviceId4,
|
|
1443
1497
|
rootCauseReason: result.reason,
|
|
1444
1498
|
fixRecommendation: `Upgrade ${svc.name}'s ${conflict.requires.name} to >= ${conflict.requires.minVersion}`
|
|
1445
1499
|
};
|
|
@@ -1448,10 +1502,10 @@ function serviceRootCauseShape(graph, _origin, walk) {
|
|
|
1448
1502
|
}
|
|
1449
1503
|
return null;
|
|
1450
1504
|
}
|
|
1451
|
-
function fileRootCauseShape(graph, origin,
|
|
1505
|
+
function fileRootCauseShape(graph, origin, walk3) {
|
|
1452
1506
|
const owner = resolveOwningService(graph, origin.id);
|
|
1453
1507
|
if (!owner) return null;
|
|
1454
|
-
return serviceRootCauseShape(graph, owner.svc,
|
|
1508
|
+
return serviceRootCauseShape(graph, owner.svc, walk3);
|
|
1455
1509
|
}
|
|
1456
1510
|
var rootCauseShapes = {
|
|
1457
1511
|
[import_types.NodeType.DatabaseNode]: databaseRootCauseShape,
|
|
@@ -1463,16 +1517,16 @@ function getRootCause(graph, errorNodeId, errorEvent, incidents) {
|
|
|
1463
1517
|
const origin = graph.getNodeAttributes(errorNodeId);
|
|
1464
1518
|
const shape = rootCauseShapes[origin.type];
|
|
1465
1519
|
if (shape) {
|
|
1466
|
-
const
|
|
1467
|
-
const match = shape(graph, origin,
|
|
1520
|
+
const walk3 = longestIncomingWalk(graph, errorNodeId, ROOT_CAUSE_MAX_DEPTH);
|
|
1521
|
+
const match = shape(graph, origin, walk3);
|
|
1468
1522
|
if (match) {
|
|
1469
1523
|
const reason = errorEvent ? `${match.rootCauseReason} (observed error: ${errorEvent.errorMessage})` : match.rootCauseReason;
|
|
1470
1524
|
return import_types.RootCauseResultSchema.parse({
|
|
1471
1525
|
rootCauseNode: match.rootCauseNode,
|
|
1472
1526
|
rootCauseReason: reason,
|
|
1473
|
-
traversalPath:
|
|
1474
|
-
edgeProvenances:
|
|
1475
|
-
confidence: confidenceFromMix(
|
|
1527
|
+
traversalPath: walk3.path,
|
|
1528
|
+
edgeProvenances: walk3.edges.map((e) => e.provenance),
|
|
1529
|
+
confidence: confidenceFromMix(walk3.edges),
|
|
1476
1530
|
fixRecommendation: match.fixRecommendation
|
|
1477
1531
|
});
|
|
1478
1532
|
}
|
|
@@ -1530,9 +1584,9 @@ function rootCauseFromIncidents(nodeId, incidents, errorEvent) {
|
|
|
1530
1584
|
function isFailingCallEdge(e) {
|
|
1531
1585
|
return e.type === import_types.EdgeType.CALLS && (e.signal?.errorCount ?? 0) > 0;
|
|
1532
1586
|
}
|
|
1533
|
-
function callSourcesForService(graph,
|
|
1534
|
-
const ids = [
|
|
1535
|
-
for (const edgeId of graph.outboundEdges(
|
|
1587
|
+
function callSourcesForService(graph, serviceId4) {
|
|
1588
|
+
const ids = [serviceId4];
|
|
1589
|
+
for (const edgeId of graph.outboundEdges(serviceId4)) {
|
|
1536
1590
|
const e = graph.getEdgeAttributes(edgeId);
|
|
1537
1591
|
if (e.type !== import_types.EdgeType.CONTAINS) continue;
|
|
1538
1592
|
const tgt = graph.getNodeAttributes(e.target);
|
|
@@ -1549,9 +1603,9 @@ function failingCallDominates(e, id, curEdge, curId) {
|
|
|
1549
1603
|
}
|
|
1550
1604
|
return id < curId;
|
|
1551
1605
|
}
|
|
1552
|
-
function dominantFailingCall(graph,
|
|
1606
|
+
function dominantFailingCall(graph, serviceId4, visited) {
|
|
1553
1607
|
let best = null;
|
|
1554
|
-
for (const src of callSourcesForService(graph,
|
|
1608
|
+
for (const src of callSourcesForService(graph, serviceId4)) {
|
|
1555
1609
|
for (const edgeId of graph.outboundEdges(src)) {
|
|
1556
1610
|
const e = graph.getEdgeAttributes(edgeId);
|
|
1557
1611
|
if (!isFailingCallEdge(e)) continue;
|
|
@@ -1566,26 +1620,26 @@ function dominantFailingCall(graph, serviceId3, visited) {
|
|
|
1566
1620
|
return best;
|
|
1567
1621
|
}
|
|
1568
1622
|
function followFailingCallChain(graph, originServiceId, maxDepth) {
|
|
1569
|
-
const
|
|
1623
|
+
const path46 = [originServiceId];
|
|
1570
1624
|
const edges = [];
|
|
1571
1625
|
const visited = /* @__PURE__ */ new Set([originServiceId]);
|
|
1572
1626
|
let current = originServiceId;
|
|
1573
1627
|
for (let depth = 0; depth < maxDepth; depth++) {
|
|
1574
1628
|
const hop = dominantFailingCall(graph, current, visited);
|
|
1575
1629
|
if (!hop) break;
|
|
1576
|
-
|
|
1630
|
+
path46.push(hop.nextService);
|
|
1577
1631
|
edges.push(hop.edge);
|
|
1578
1632
|
visited.add(hop.nextService);
|
|
1579
1633
|
current = hop.nextService;
|
|
1580
1634
|
}
|
|
1581
1635
|
if (edges.length === 0) return null;
|
|
1582
|
-
return { path:
|
|
1636
|
+
return { path: path46, edges, culprit: current };
|
|
1583
1637
|
}
|
|
1584
1638
|
function crossServiceRootCause(graph, originId, incidents, errorEvent) {
|
|
1585
1639
|
const chain = followFailingCallChain(graph, originId, ROOT_CAUSE_MAX_DEPTH);
|
|
1586
1640
|
if (!chain) return null;
|
|
1587
1641
|
const culprit = chain.culprit;
|
|
1588
|
-
const
|
|
1642
|
+
const path46 = [...chain.path];
|
|
1589
1643
|
const edgeProvenances = chain.edges.map((e) => e.provenance);
|
|
1590
1644
|
const baseConfidence = confidenceFromMix(chain.edges);
|
|
1591
1645
|
const confidence = Math.max(0, Math.min(1, baseConfidence * INCIDENT_ROOT_CAUSE_CONFIDENCE));
|
|
@@ -1593,14 +1647,14 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
|
|
|
1593
1647
|
if (loc) {
|
|
1594
1648
|
let rootCauseNode = culprit;
|
|
1595
1649
|
if (loc.fileNode) {
|
|
1596
|
-
|
|
1650
|
+
path46.push(loc.fileNode);
|
|
1597
1651
|
edgeProvenances.push(import_types.Provenance.OBSERVED);
|
|
1598
1652
|
rootCauseNode = loc.fileNode;
|
|
1599
1653
|
}
|
|
1600
1654
|
return import_types.RootCauseResultSchema.parse({
|
|
1601
1655
|
rootCauseNode,
|
|
1602
1656
|
rootCauseReason: loc.rootCauseReason,
|
|
1603
|
-
traversalPath:
|
|
1657
|
+
traversalPath: path46,
|
|
1604
1658
|
edgeProvenances,
|
|
1605
1659
|
confidence,
|
|
1606
1660
|
...loc.fixRecommendation ? { fixRecommendation: loc.fixRecommendation } : {}
|
|
@@ -1612,7 +1666,7 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
|
|
|
1612
1666
|
return import_types.RootCauseResultSchema.parse({
|
|
1613
1667
|
rootCauseNode: culprit,
|
|
1614
1668
|
rootCauseReason: `${culpritName} is failing downstream calls (${errs} observed error${errs === 1 ? "" : "s"})`,
|
|
1615
|
-
traversalPath:
|
|
1669
|
+
traversalPath: path46,
|
|
1616
1670
|
edgeProvenances,
|
|
1617
1671
|
confidence,
|
|
1618
1672
|
fixRecommendation: `Inspect ${culpritName}'s failing handler`
|
|
@@ -1792,6 +1846,11 @@ function nodeIsFrontier(graph, nodeId) {
|
|
|
1792
1846
|
const attrs = graph.getNodeAttributes(nodeId);
|
|
1793
1847
|
return attrs.type === import_types2.NodeType.FrontierNode;
|
|
1794
1848
|
}
|
|
1849
|
+
function nodeIsWebsocketChannel(graph, nodeId) {
|
|
1850
|
+
if (!graph.hasNode(nodeId)) return false;
|
|
1851
|
+
const attrs = graph.getNodeAttributes(nodeId);
|
|
1852
|
+
return attrs.type === import_types2.NodeType.WebSocketChannelNode;
|
|
1853
|
+
}
|
|
1795
1854
|
function clampConfidence(n) {
|
|
1796
1855
|
if (!Number.isFinite(n)) return 0;
|
|
1797
1856
|
return Math.max(0, Math.min(1, n));
|
|
@@ -1832,7 +1891,7 @@ function detectMissingDivergences(graph, bucket) {
|
|
|
1832
1891
|
});
|
|
1833
1892
|
}
|
|
1834
1893
|
}
|
|
1835
|
-
if (bucket.observed && !bucket.extracted) {
|
|
1894
|
+
if (bucket.observed && !bucket.extracted && !nodeIsWebsocketChannel(graph, bucket.target)) {
|
|
1836
1895
|
out.push({
|
|
1837
1896
|
type: "missing-extracted",
|
|
1838
1897
|
source: bucket.source,
|
|
@@ -2824,10 +2883,82 @@ var STITCH_EDGE_TYPES = /* @__PURE__ */ new Set([
|
|
|
2824
2883
|
]);
|
|
2825
2884
|
var WIRE_SPAN_KIND_CLIENT = 3;
|
|
2826
2885
|
var WIRE_SPAN_KIND_PRODUCER = 4;
|
|
2886
|
+
var WIRE_SPAN_KIND_CONSUMER = 5;
|
|
2827
2887
|
function spanMintsObservedEdge(kind) {
|
|
2828
2888
|
if (kind === void 0 || kind === 0) return true;
|
|
2829
2889
|
return kind === WIRE_SPAN_KIND_CLIENT || kind === WIRE_SPAN_KIND_PRODUCER;
|
|
2830
2890
|
}
|
|
2891
|
+
function spanMintsMessagingEdge(kind) {
|
|
2892
|
+
return kind === WIRE_SPAN_KIND_PRODUCER || kind === WIRE_SPAN_KIND_CONSUMER;
|
|
2893
|
+
}
|
|
2894
|
+
function spanServesGraphqlOperation(kind) {
|
|
2895
|
+
return kind !== WIRE_SPAN_KIND_CLIENT && kind !== WIRE_SPAN_KIND_PRODUCER && kind !== WIRE_SPAN_KIND_CONSUMER;
|
|
2896
|
+
}
|
|
2897
|
+
function ensureGraphqlOperationNode(graph, serviceName, operationType, operationName) {
|
|
2898
|
+
const id = (0, import_types4.graphqlOperationId)(serviceName, operationType, operationName);
|
|
2899
|
+
if (graph.hasNode(id)) return id;
|
|
2900
|
+
const node = {
|
|
2901
|
+
id,
|
|
2902
|
+
type: import_types4.NodeType.GraphQLOperationNode,
|
|
2903
|
+
name: operationName,
|
|
2904
|
+
service: serviceName,
|
|
2905
|
+
operationType: operationType.toLowerCase(),
|
|
2906
|
+
operationName,
|
|
2907
|
+
discoveredVia: "otel"
|
|
2908
|
+
};
|
|
2909
|
+
graph.addNode(id, node);
|
|
2910
|
+
return id;
|
|
2911
|
+
}
|
|
2912
|
+
function spanServesGrpcMethod(kind) {
|
|
2913
|
+
return kind !== WIRE_SPAN_KIND_CLIENT && kind !== WIRE_SPAN_KIND_PRODUCER && kind !== WIRE_SPAN_KIND_CONSUMER;
|
|
2914
|
+
}
|
|
2915
|
+
function ensureGrpcMethodNode(graph, rpcService, rpcMethod) {
|
|
2916
|
+
const id = (0, import_types4.grpcMethodId)(rpcService, rpcMethod);
|
|
2917
|
+
if (graph.hasNode(id)) return id;
|
|
2918
|
+
const node = {
|
|
2919
|
+
id,
|
|
2920
|
+
type: import_types4.NodeType.GrpcMethodNode,
|
|
2921
|
+
name: `${rpcService}/${rpcMethod}`,
|
|
2922
|
+
rpcService,
|
|
2923
|
+
rpcMethod,
|
|
2924
|
+
discoveredVia: "otel"
|
|
2925
|
+
};
|
|
2926
|
+
graph.addNode(id, node);
|
|
2927
|
+
return id;
|
|
2928
|
+
}
|
|
2929
|
+
function spanServesWebsocketChannel(kind) {
|
|
2930
|
+
return kind !== WIRE_SPAN_KIND_CLIENT && kind !== WIRE_SPAN_KIND_PRODUCER && kind !== WIRE_SPAN_KIND_CONSUMER;
|
|
2931
|
+
}
|
|
2932
|
+
function ensureWebsocketChannelNode(graph, serviceName, channel) {
|
|
2933
|
+
const id = (0, import_types4.websocketChannelId)(serviceName, channel);
|
|
2934
|
+
if (graph.hasNode(id)) return id;
|
|
2935
|
+
const node = {
|
|
2936
|
+
id,
|
|
2937
|
+
type: import_types4.NodeType.WebSocketChannelNode,
|
|
2938
|
+
name: channel,
|
|
2939
|
+
service: serviceName,
|
|
2940
|
+
channel,
|
|
2941
|
+
discoveredVia: "otel"
|
|
2942
|
+
};
|
|
2943
|
+
graph.addNode(id, node);
|
|
2944
|
+
return id;
|
|
2945
|
+
}
|
|
2946
|
+
function messagingDestinationKind(system) {
|
|
2947
|
+
return `${system}-topic`;
|
|
2948
|
+
}
|
|
2949
|
+
function ensureMessagingDestinationNode(graph, system, destination) {
|
|
2950
|
+
const id = (0, import_types4.infraId)(messagingDestinationKind(system), destination);
|
|
2951
|
+
if (graph.hasNode(id)) return id;
|
|
2952
|
+
const node = {
|
|
2953
|
+
id,
|
|
2954
|
+
type: import_types4.NodeType.InfraNode,
|
|
2955
|
+
name: destination,
|
|
2956
|
+
provider: "self",
|
|
2957
|
+
kind: messagingDestinationKind(system)
|
|
2958
|
+
};
|
|
2959
|
+
graph.addNode(id, node);
|
|
2960
|
+
return id;
|
|
2961
|
+
}
|
|
2831
2962
|
var PARENT_SPAN_CACHE_SIZE = 1e4;
|
|
2832
2963
|
var PARENT_SPAN_CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
2833
2964
|
var parentSpanCache = /* @__PURE__ */ new Map();
|
|
@@ -2921,6 +3052,21 @@ function ensureDatabaseNode(graph, host, engine) {
|
|
|
2921
3052
|
graph.addNode(id, node);
|
|
2922
3053
|
return id;
|
|
2923
3054
|
}
|
|
3055
|
+
function ensureLocalDatabaseNode(graph, serviceName, name, engine) {
|
|
3056
|
+
const id = (0, import_types4.localDatabaseId)(serviceName, name);
|
|
3057
|
+
if (graph.hasNode(id)) return id;
|
|
3058
|
+
const node = {
|
|
3059
|
+
id,
|
|
3060
|
+
type: import_types4.NodeType.DatabaseNode,
|
|
3061
|
+
name,
|
|
3062
|
+
engine,
|
|
3063
|
+
engineVersion: "unknown",
|
|
3064
|
+
compatibleDrivers: [],
|
|
3065
|
+
discoveredVia: "otel"
|
|
3066
|
+
};
|
|
3067
|
+
graph.addNode(id, node);
|
|
3068
|
+
return id;
|
|
3069
|
+
}
|
|
2924
3070
|
function ensureFrontierNode(graph, host, ts) {
|
|
2925
3071
|
const id = frontierIdFor(host);
|
|
2926
3072
|
if (graph.hasNode(id)) {
|
|
@@ -3147,10 +3293,21 @@ async function handleSpan(ctx, span) {
|
|
|
3147
3293
|
let affectedNode = sourceId;
|
|
3148
3294
|
const mintsFromCallerSide = spanMintsObservedEdge(span.kind);
|
|
3149
3295
|
if (span.dbSystem) {
|
|
3150
|
-
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
|
|
3296
|
+
if (mintsFromCallerSide) {
|
|
3297
|
+
const host = pickAddress(span);
|
|
3298
|
+
let targetId;
|
|
3299
|
+
if (host) {
|
|
3300
|
+
ensureDatabaseNode(ctx.graph, host, span.dbSystem);
|
|
3301
|
+
targetId = (0, import_types4.databaseId)(host);
|
|
3302
|
+
} else {
|
|
3303
|
+
const localName = span.dbName ?? span.dbSystem;
|
|
3304
|
+
targetId = ensureLocalDatabaseNode(
|
|
3305
|
+
ctx.graph,
|
|
3306
|
+
span.service,
|
|
3307
|
+
localName,
|
|
3308
|
+
span.dbSystem
|
|
3309
|
+
);
|
|
3310
|
+
}
|
|
3154
3311
|
const result = upsertObservedEdge(
|
|
3155
3312
|
ctx.graph,
|
|
3156
3313
|
import_types4.EdgeType.CONNECTS_TO,
|
|
@@ -3162,6 +3319,68 @@ async function handleSpan(ctx, span) {
|
|
|
3162
3319
|
);
|
|
3163
3320
|
if (result) affectedNode = targetId;
|
|
3164
3321
|
}
|
|
3322
|
+
} else if (span.messagingSystem && span.messagingDestination && spanMintsMessagingEdge(span.kind)) {
|
|
3323
|
+
const targetId = ensureMessagingDestinationNode(
|
|
3324
|
+
ctx.graph,
|
|
3325
|
+
span.messagingSystem,
|
|
3326
|
+
span.messagingDestination
|
|
3327
|
+
);
|
|
3328
|
+
const edgeType = span.kind === WIRE_SPAN_KIND_CONSUMER ? import_types4.EdgeType.CONSUMES_FROM : import_types4.EdgeType.PUBLISHES_TO;
|
|
3329
|
+
const result = upsertObservedEdge(
|
|
3330
|
+
ctx.graph,
|
|
3331
|
+
edgeType,
|
|
3332
|
+
observedSource(),
|
|
3333
|
+
targetId,
|
|
3334
|
+
ts,
|
|
3335
|
+
isError,
|
|
3336
|
+
callSiteEvidence
|
|
3337
|
+
);
|
|
3338
|
+
if (result) affectedNode = targetId;
|
|
3339
|
+
} else if (span.graphqlOperationName && span.graphqlOperationType && spanServesGraphqlOperation(span.kind)) {
|
|
3340
|
+
const targetId = ensureGraphqlOperationNode(
|
|
3341
|
+
ctx.graph,
|
|
3342
|
+
span.service,
|
|
3343
|
+
span.graphqlOperationType,
|
|
3344
|
+
span.graphqlOperationName
|
|
3345
|
+
);
|
|
3346
|
+
const result = upsertObservedEdge(
|
|
3347
|
+
ctx.graph,
|
|
3348
|
+
import_types4.EdgeType.CONTAINS,
|
|
3349
|
+
observedSource(),
|
|
3350
|
+
targetId,
|
|
3351
|
+
ts,
|
|
3352
|
+
isError,
|
|
3353
|
+
callSiteEvidence
|
|
3354
|
+
);
|
|
3355
|
+
if (result) affectedNode = targetId;
|
|
3356
|
+
} else if (span.rpcSystem === "grpc" && span.rpcService && span.rpcMethod && spanServesGrpcMethod(span.kind)) {
|
|
3357
|
+
const targetId = ensureGrpcMethodNode(ctx.graph, span.rpcService, span.rpcMethod);
|
|
3358
|
+
const result = upsertObservedEdge(
|
|
3359
|
+
ctx.graph,
|
|
3360
|
+
import_types4.EdgeType.CONTAINS,
|
|
3361
|
+
observedSource(),
|
|
3362
|
+
targetId,
|
|
3363
|
+
ts,
|
|
3364
|
+
isError,
|
|
3365
|
+
callSiteEvidence
|
|
3366
|
+
);
|
|
3367
|
+
if (result) affectedNode = targetId;
|
|
3368
|
+
} else if (span.websocketChannel && spanServesWebsocketChannel(span.kind)) {
|
|
3369
|
+
const targetId = ensureWebsocketChannelNode(
|
|
3370
|
+
ctx.graph,
|
|
3371
|
+
span.service,
|
|
3372
|
+
span.websocketChannel
|
|
3373
|
+
);
|
|
3374
|
+
const result = upsertObservedEdge(
|
|
3375
|
+
ctx.graph,
|
|
3376
|
+
import_types4.EdgeType.CONNECTS_TO,
|
|
3377
|
+
observedSource(),
|
|
3378
|
+
targetId,
|
|
3379
|
+
ts,
|
|
3380
|
+
isError,
|
|
3381
|
+
callSiteEvidence
|
|
3382
|
+
);
|
|
3383
|
+
if (result) affectedNode = targetId;
|
|
3165
3384
|
} else {
|
|
3166
3385
|
const host = pickAddress(span);
|
|
3167
3386
|
let resolvedViaAddress = false;
|
|
@@ -3271,29 +3490,29 @@ function promoteFrontierNodes(graph, opts = {}) {
|
|
|
3271
3490
|
toPromote.push({ frontierId: id, serviceId: target });
|
|
3272
3491
|
});
|
|
3273
3492
|
let promoted = 0;
|
|
3274
|
-
for (const { frontierId: frontierId2, serviceId:
|
|
3493
|
+
for (const { frontierId: frontierId2, serviceId: serviceId4 } of toPromote) {
|
|
3275
3494
|
if (opts.policies && opts.policies.length > 0 && opts.policyCtx) {
|
|
3276
3495
|
const gate = canPromoteFrontier(graph, frontierId2, opts.policies, opts.policyCtx);
|
|
3277
3496
|
if (!gate.allowed) {
|
|
3278
3497
|
continue;
|
|
3279
3498
|
}
|
|
3280
3499
|
}
|
|
3281
|
-
rewireFrontierEdges(graph, frontierId2,
|
|
3500
|
+
rewireFrontierEdges(graph, frontierId2, serviceId4);
|
|
3282
3501
|
graph.dropNode(frontierId2);
|
|
3283
3502
|
promoted++;
|
|
3284
3503
|
}
|
|
3285
3504
|
return promoted;
|
|
3286
3505
|
}
|
|
3287
|
-
function rewireFrontierEdges(graph, frontierId2,
|
|
3506
|
+
function rewireFrontierEdges(graph, frontierId2, serviceId4) {
|
|
3288
3507
|
const inbound = [...graph.inboundEdges(frontierId2)];
|
|
3289
3508
|
const outbound = [...graph.outboundEdges(frontierId2)];
|
|
3290
3509
|
for (const edgeId of inbound) {
|
|
3291
3510
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
3292
|
-
rebuildEdge(graph, edge, edge.source,
|
|
3511
|
+
rebuildEdge(graph, edge, edge.source, serviceId4, edgeId);
|
|
3293
3512
|
}
|
|
3294
3513
|
for (const edgeId of outbound) {
|
|
3295
3514
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
3296
|
-
rebuildEdge(graph, edge,
|
|
3515
|
+
rebuildEdge(graph, edge, serviceId4, edge.target, edgeId);
|
|
3297
3516
|
}
|
|
3298
3517
|
}
|
|
3299
3518
|
function rebuildEdge(graph, edge, newSource, newTarget, oldEdgeId) {
|
|
@@ -4055,9 +4274,9 @@ var K8S_KINDS_WITH_HOSTNAMES = /* @__PURE__ */ new Set([
|
|
|
4055
4274
|
"StatefulSet",
|
|
4056
4275
|
"DaemonSet"
|
|
4057
4276
|
]);
|
|
4058
|
-
function addAliases(graph,
|
|
4059
|
-
if (!graph.hasNode(
|
|
4060
|
-
const node = graph.getNodeAttributes(
|
|
4277
|
+
function addAliases(graph, serviceId4, candidates) {
|
|
4278
|
+
if (!graph.hasNode(serviceId4)) return;
|
|
4279
|
+
const node = graph.getNodeAttributes(serviceId4);
|
|
4061
4280
|
if (node.type !== import_types7.NodeType.ServiceNode) return;
|
|
4062
4281
|
const set = new Set(node.aliases ?? []);
|
|
4063
4282
|
for (const c of candidates) {
|
|
@@ -4067,7 +4286,7 @@ function addAliases(graph, serviceId3, candidates) {
|
|
|
4067
4286
|
}
|
|
4068
4287
|
if (set.size === 0) return;
|
|
4069
4288
|
const updated = { ...node, aliases: [...set].sort() };
|
|
4070
|
-
graph.replaceNodeAttributes(
|
|
4289
|
+
graph.replaceNodeAttributes(serviceId4, updated);
|
|
4071
4290
|
}
|
|
4072
4291
|
function indexServicesByName(services) {
|
|
4073
4292
|
const map = /* @__PURE__ */ new Map();
|
|
@@ -4100,12 +4319,12 @@ async function collectComposeAliases(graph, scanPath, serviceIndex) {
|
|
|
4100
4319
|
}
|
|
4101
4320
|
if (!compose?.services) return;
|
|
4102
4321
|
for (const [composeName, svc] of Object.entries(compose.services)) {
|
|
4103
|
-
const
|
|
4104
|
-
if (!
|
|
4322
|
+
const serviceId4 = serviceIndex.get(composeName);
|
|
4323
|
+
if (!serviceId4) continue;
|
|
4105
4324
|
const aliases = /* @__PURE__ */ new Set([composeName]);
|
|
4106
4325
|
if (svc.container_name) aliases.add(svc.container_name);
|
|
4107
4326
|
if (svc.hostname) aliases.add(svc.hostname);
|
|
4108
|
-
addAliases(graph,
|
|
4327
|
+
addAliases(graph, serviceId4, aliases);
|
|
4109
4328
|
}
|
|
4110
4329
|
}
|
|
4111
4330
|
var LABEL_KEYS = /* @__PURE__ */ new Set([
|
|
@@ -4218,16 +4437,28 @@ init_cjs_shims();
|
|
|
4218
4437
|
var import_node_fs12 = require("fs");
|
|
4219
4438
|
var import_node_path12 = __toESM(require("path"), 1);
|
|
4220
4439
|
var import_types8 = require("@neat.is/types");
|
|
4440
|
+
function buildServiceHostIndex(services) {
|
|
4441
|
+
const knownHosts = /* @__PURE__ */ new Set();
|
|
4442
|
+
const hostToNodeId = /* @__PURE__ */ new Map();
|
|
4443
|
+
for (const service of services) {
|
|
4444
|
+
const base = import_node_path12.default.basename(service.dir);
|
|
4445
|
+
knownHosts.add(base);
|
|
4446
|
+
knownHosts.add(service.pkg.name);
|
|
4447
|
+
hostToNodeId.set(base, service.node.id);
|
|
4448
|
+
hostToNodeId.set(service.pkg.name, service.node.id);
|
|
4449
|
+
}
|
|
4450
|
+
return { knownHosts, hostToNodeId };
|
|
4451
|
+
}
|
|
4221
4452
|
async function walkSourceFiles(dir) {
|
|
4222
4453
|
const out = [];
|
|
4223
|
-
async function
|
|
4454
|
+
async function walk3(current) {
|
|
4224
4455
|
const entries = await import_node_fs12.promises.readdir(current, { withFileTypes: true }).catch(() => []);
|
|
4225
4456
|
for (const entry of entries) {
|
|
4226
4457
|
const full = import_node_path12.default.join(current, entry.name);
|
|
4227
4458
|
if (entry.isDirectory()) {
|
|
4228
4459
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
4229
4460
|
if (await isPythonVenvDir(full)) continue;
|
|
4230
|
-
await
|
|
4461
|
+
await walk3(full);
|
|
4231
4462
|
} else if (entry.isFile() && SERVICE_FILE_EXTENSIONS.has(import_node_path12.default.extname(entry.name)) && // Skip NEAT's own generated `otel-init.*` bootstrap — extracting it
|
|
4232
4463
|
// would attribute our instrumentation imports to the user's service.
|
|
4233
4464
|
!isNeatAuthoredSourceFile(entry.name)) {
|
|
@@ -4235,7 +4466,7 @@ async function walkSourceFiles(dir) {
|
|
|
4235
4466
|
}
|
|
4236
4467
|
}
|
|
4237
4468
|
}
|
|
4238
|
-
await
|
|
4469
|
+
await walk3(dir);
|
|
4239
4470
|
return out;
|
|
4240
4471
|
}
|
|
4241
4472
|
async function loadSourceFiles(dir) {
|
|
@@ -5329,20 +5560,20 @@ var import_node_path23 = __toESM(require("path"), 1);
|
|
|
5329
5560
|
var import_types11 = require("@neat.is/types");
|
|
5330
5561
|
async function walkConfigFiles(dir) {
|
|
5331
5562
|
const out = [];
|
|
5332
|
-
async function
|
|
5563
|
+
async function walk3(current) {
|
|
5333
5564
|
const entries = await import_node_fs16.promises.readdir(current, { withFileTypes: true });
|
|
5334
5565
|
for (const entry of entries) {
|
|
5335
5566
|
const full = import_node_path23.default.join(current, entry.name);
|
|
5336
5567
|
if (entry.isDirectory()) {
|
|
5337
5568
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
5338
5569
|
if (await isPythonVenvDir(full)) continue;
|
|
5339
|
-
await
|
|
5570
|
+
await walk3(full);
|
|
5340
5571
|
} else if (entry.isFile() && isConfigFile(entry.name).match) {
|
|
5341
5572
|
out.push(full);
|
|
5342
5573
|
}
|
|
5343
5574
|
}
|
|
5344
5575
|
}
|
|
5345
|
-
await
|
|
5576
|
+
await walk3(dir);
|
|
5346
5577
|
return out;
|
|
5347
5578
|
}
|
|
5348
5579
|
async function addConfigNodes(graph, services, scanPath) {
|
|
@@ -5390,17 +5621,468 @@ async function addConfigNodes(graph, services, scanPath) {
|
|
|
5390
5621
|
return { nodesAdded, edgesAdded };
|
|
5391
5622
|
}
|
|
5392
5623
|
|
|
5624
|
+
// src/extract/routes.ts
|
|
5625
|
+
init_cjs_shims();
|
|
5626
|
+
var import_node_path24 = __toESM(require("path"), 1);
|
|
5627
|
+
var import_tree_sitter2 = __toESM(require("tree-sitter"), 1);
|
|
5628
|
+
var import_tree_sitter_javascript2 = __toESM(require("tree-sitter-javascript"), 1);
|
|
5629
|
+
var import_types12 = require("@neat.is/types");
|
|
5630
|
+
var PARSE_CHUNK2 = 16384;
|
|
5631
|
+
function parseSource2(parser, source) {
|
|
5632
|
+
return parser.parse(
|
|
5633
|
+
(index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK2)
|
|
5634
|
+
);
|
|
5635
|
+
}
|
|
5636
|
+
function makeJsParser2() {
|
|
5637
|
+
const p = new import_tree_sitter2.default();
|
|
5638
|
+
p.setLanguage(import_tree_sitter_javascript2.default);
|
|
5639
|
+
return p;
|
|
5640
|
+
}
|
|
5641
|
+
var ROUTER_METHODS = /* @__PURE__ */ new Set([
|
|
5642
|
+
"get",
|
|
5643
|
+
"post",
|
|
5644
|
+
"put",
|
|
5645
|
+
"patch",
|
|
5646
|
+
"delete",
|
|
5647
|
+
"options",
|
|
5648
|
+
"head",
|
|
5649
|
+
"all"
|
|
5650
|
+
]);
|
|
5651
|
+
var NEXT_APP_METHODS = /* @__PURE__ */ new Set(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"]);
|
|
5652
|
+
var JS_ROUTE_EXTENSIONS = /* @__PURE__ */ new Set([".js", ".jsx", ".mjs", ".cjs", ".ts", ".tsx"]);
|
|
5653
|
+
function canonicalizeTemplate(raw) {
|
|
5654
|
+
let p = raw.split("?")[0].split("#")[0];
|
|
5655
|
+
if (!p.startsWith("/")) p = "/" + p;
|
|
5656
|
+
if (p.length > 1 && p.endsWith("/")) p = p.slice(0, -1);
|
|
5657
|
+
return p;
|
|
5658
|
+
}
|
|
5659
|
+
function isDynamicSegment(seg) {
|
|
5660
|
+
if (seg.length === 0) return false;
|
|
5661
|
+
if (seg.includes(":")) return true;
|
|
5662
|
+
if (seg.startsWith("{") || seg.startsWith("[")) return true;
|
|
5663
|
+
if (/^\d+$/.test(seg)) return true;
|
|
5664
|
+
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;
|
|
5665
|
+
if (/^[0-9a-f]{24,}$/i.test(seg)) return true;
|
|
5666
|
+
return false;
|
|
5667
|
+
}
|
|
5668
|
+
function normalizePathTemplate(raw) {
|
|
5669
|
+
const canonical = canonicalizeTemplate(raw);
|
|
5670
|
+
const segments = canonical.split("/").filter((s) => s.length > 0);
|
|
5671
|
+
const normalised = segments.map((seg) => isDynamicSegment(seg) ? ":param" : seg.toLowerCase());
|
|
5672
|
+
return "/" + normalised.join("/");
|
|
5673
|
+
}
|
|
5674
|
+
function walk(node, visit) {
|
|
5675
|
+
visit(node);
|
|
5676
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
5677
|
+
const child = node.namedChild(i);
|
|
5678
|
+
if (child) walk(child, visit);
|
|
5679
|
+
}
|
|
5680
|
+
}
|
|
5681
|
+
function staticStringText(node) {
|
|
5682
|
+
if (node.type === "string") {
|
|
5683
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
5684
|
+
const child = node.namedChild(i);
|
|
5685
|
+
if (child?.type === "string_fragment") return child.text;
|
|
5686
|
+
}
|
|
5687
|
+
return "";
|
|
5688
|
+
}
|
|
5689
|
+
if (node.type === "template_string") {
|
|
5690
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
5691
|
+
if (node.namedChild(i)?.type === "template_substitution") return null;
|
|
5692
|
+
}
|
|
5693
|
+
const raw = node.text;
|
|
5694
|
+
return raw.length >= 2 ? raw.slice(1, -1) : "";
|
|
5695
|
+
}
|
|
5696
|
+
return null;
|
|
5697
|
+
}
|
|
5698
|
+
function objectStringProp(objNode, key) {
|
|
5699
|
+
for (let i = 0; i < objNode.namedChildCount; i++) {
|
|
5700
|
+
const pair = objNode.namedChild(i);
|
|
5701
|
+
if (!pair || pair.type !== "pair") continue;
|
|
5702
|
+
const k = pair.childForFieldName("key");
|
|
5703
|
+
if (!k) continue;
|
|
5704
|
+
const kText = k.type === "string" ? staticStringText(k) : k.text;
|
|
5705
|
+
if (kText !== key) continue;
|
|
5706
|
+
const v = pair.childForFieldName("value");
|
|
5707
|
+
if (v) return staticStringText(v);
|
|
5708
|
+
}
|
|
5709
|
+
return null;
|
|
5710
|
+
}
|
|
5711
|
+
function fastifyRouteMethods(objNode) {
|
|
5712
|
+
for (let i = 0; i < objNode.namedChildCount; i++) {
|
|
5713
|
+
const pair = objNode.namedChild(i);
|
|
5714
|
+
if (!pair || pair.type !== "pair") continue;
|
|
5715
|
+
const k = pair.childForFieldName("key");
|
|
5716
|
+
const kText = k ? k.type === "string" ? staticStringText(k) : k.text : null;
|
|
5717
|
+
if (kText !== "method") continue;
|
|
5718
|
+
const v = pair.childForFieldName("value");
|
|
5719
|
+
if (!v) return [];
|
|
5720
|
+
if (v.type === "string" || v.type === "template_string") {
|
|
5721
|
+
const s = staticStringText(v);
|
|
5722
|
+
return s ? [s.toUpperCase()] : [];
|
|
5723
|
+
}
|
|
5724
|
+
if (v.type === "array") {
|
|
5725
|
+
const out = [];
|
|
5726
|
+
for (let j = 0; j < v.namedChildCount; j++) {
|
|
5727
|
+
const el = v.namedChild(j);
|
|
5728
|
+
if (el && (el.type === "string" || el.type === "template_string")) {
|
|
5729
|
+
const s = staticStringText(el);
|
|
5730
|
+
if (s) out.push(s.toUpperCase());
|
|
5731
|
+
}
|
|
5732
|
+
}
|
|
5733
|
+
return out;
|
|
5734
|
+
}
|
|
5735
|
+
}
|
|
5736
|
+
return [];
|
|
5737
|
+
}
|
|
5738
|
+
function serverRoutesFromSource(source, parser, hasExpress, hasFastify) {
|
|
5739
|
+
const tree = parseSource2(parser, source);
|
|
5740
|
+
const out = [];
|
|
5741
|
+
const framework = hasExpress ? "express" : "fastify";
|
|
5742
|
+
walk(tree.rootNode, (node) => {
|
|
5743
|
+
if (node.type !== "call_expression") return;
|
|
5744
|
+
const fn = node.childForFieldName("function");
|
|
5745
|
+
if (!fn || fn.type !== "member_expression") return;
|
|
5746
|
+
const prop = fn.childForFieldName("property");
|
|
5747
|
+
if (!prop) return;
|
|
5748
|
+
const method = prop.text.toLowerCase();
|
|
5749
|
+
const args = node.childForFieldName("arguments");
|
|
5750
|
+
const first = args?.namedChild(0);
|
|
5751
|
+
if (!first) return;
|
|
5752
|
+
const line = node.startPosition.row + 1;
|
|
5753
|
+
if (ROUTER_METHODS.has(method)) {
|
|
5754
|
+
const p = staticStringText(first);
|
|
5755
|
+
if (p && p.startsWith("/")) {
|
|
5756
|
+
out.push({
|
|
5757
|
+
method: method === "all" ? "ALL" : method.toUpperCase(),
|
|
5758
|
+
pathTemplate: canonicalizeTemplate(p),
|
|
5759
|
+
line,
|
|
5760
|
+
framework
|
|
5761
|
+
});
|
|
5762
|
+
}
|
|
5763
|
+
return;
|
|
5764
|
+
}
|
|
5765
|
+
if (method === "route" && hasFastify && first.type === "object") {
|
|
5766
|
+
const url = objectStringProp(first, "url");
|
|
5767
|
+
if (!url || !url.startsWith("/")) return;
|
|
5768
|
+
const methods = fastifyRouteMethods(first);
|
|
5769
|
+
const list = methods.length > 0 ? methods : ["ALL"];
|
|
5770
|
+
for (const m of list) {
|
|
5771
|
+
out.push({
|
|
5772
|
+
method: m === "ALL" ? "ALL" : m.toUpperCase(),
|
|
5773
|
+
pathTemplate: canonicalizeTemplate(url),
|
|
5774
|
+
line,
|
|
5775
|
+
framework: "fastify"
|
|
5776
|
+
});
|
|
5777
|
+
}
|
|
5778
|
+
}
|
|
5779
|
+
});
|
|
5780
|
+
return out;
|
|
5781
|
+
}
|
|
5782
|
+
function segmentsOf(relFile) {
|
|
5783
|
+
return toPosix2(relFile).split("/").filter((s) => s.length > 0);
|
|
5784
|
+
}
|
|
5785
|
+
function isNextAppRouteFile(relFile) {
|
|
5786
|
+
const segs = segmentsOf(relFile);
|
|
5787
|
+
if (!segs.includes("app")) return false;
|
|
5788
|
+
const base = segs[segs.length - 1] ?? "";
|
|
5789
|
+
return /^route\.(?:js|jsx|mjs|cjs|ts|tsx)$/.test(base);
|
|
5790
|
+
}
|
|
5791
|
+
function isNextPagesApiFile(relFile) {
|
|
5792
|
+
const segs = segmentsOf(relFile);
|
|
5793
|
+
const pagesIdx = segs.indexOf("pages");
|
|
5794
|
+
if (pagesIdx === -1 || segs[pagesIdx + 1] !== "api") return false;
|
|
5795
|
+
const base = segs[segs.length - 1] ?? "";
|
|
5796
|
+
if (/^_(app|document|middleware)\./.test(base)) return false;
|
|
5797
|
+
return JS_ROUTE_EXTENSIONS.has(import_node_path24.default.extname(base));
|
|
5798
|
+
}
|
|
5799
|
+
function nextSegment(seg) {
|
|
5800
|
+
if (seg.startsWith("(") && seg.endsWith(")")) return null;
|
|
5801
|
+
const catchAll = seg.match(/^\[\[?\.\.\.(.+?)\]?\]$/);
|
|
5802
|
+
if (catchAll) return ":" + catchAll[1];
|
|
5803
|
+
const dynamic = seg.match(/^\[(.+?)\]$/);
|
|
5804
|
+
if (dynamic) return ":" + dynamic[1];
|
|
5805
|
+
return seg;
|
|
5806
|
+
}
|
|
5807
|
+
function nextAppPathTemplate(relFile) {
|
|
5808
|
+
const segs = segmentsOf(relFile);
|
|
5809
|
+
const appIdx = segs.lastIndexOf("app");
|
|
5810
|
+
const between = segs.slice(appIdx + 1, segs.length - 1);
|
|
5811
|
+
const parts = [];
|
|
5812
|
+
for (const seg of between) {
|
|
5813
|
+
const mapped = nextSegment(seg);
|
|
5814
|
+
if (mapped !== null) parts.push(mapped);
|
|
5815
|
+
}
|
|
5816
|
+
return "/" + parts.join("/");
|
|
5817
|
+
}
|
|
5818
|
+
function nextPagesApiPathTemplate(relFile) {
|
|
5819
|
+
const segs = segmentsOf(relFile);
|
|
5820
|
+
const pagesIdx = segs.indexOf("pages");
|
|
5821
|
+
const rest = segs.slice(pagesIdx + 1);
|
|
5822
|
+
const parts = [];
|
|
5823
|
+
for (let i = 0; i < rest.length; i++) {
|
|
5824
|
+
let seg = rest[i];
|
|
5825
|
+
if (i === rest.length - 1) {
|
|
5826
|
+
seg = seg.replace(/\.(?:js|jsx|mjs|cjs|ts|tsx)$/, "");
|
|
5827
|
+
if (seg === "index") continue;
|
|
5828
|
+
}
|
|
5829
|
+
const mapped = nextSegment(seg);
|
|
5830
|
+
if (mapped !== null) parts.push(mapped);
|
|
5831
|
+
}
|
|
5832
|
+
return "/" + parts.join("/");
|
|
5833
|
+
}
|
|
5834
|
+
function nextAppMethods(root) {
|
|
5835
|
+
const out = [];
|
|
5836
|
+
walk(root, (node) => {
|
|
5837
|
+
if (node.type !== "export_statement") return;
|
|
5838
|
+
const decl = node.childForFieldName("declaration");
|
|
5839
|
+
if (!decl) return;
|
|
5840
|
+
const line = node.startPosition.row + 1;
|
|
5841
|
+
if (decl.type === "function_declaration") {
|
|
5842
|
+
const name = decl.childForFieldName("name")?.text;
|
|
5843
|
+
if (name && NEXT_APP_METHODS.has(name)) out.push({ method: name, line });
|
|
5844
|
+
return;
|
|
5845
|
+
}
|
|
5846
|
+
if (decl.type === "lexical_declaration" || decl.type === "variable_declaration") {
|
|
5847
|
+
for (let i = 0; i < decl.namedChildCount; i++) {
|
|
5848
|
+
const d = decl.namedChild(i);
|
|
5849
|
+
if (d?.type !== "variable_declarator") continue;
|
|
5850
|
+
const name = d.childForFieldName("name")?.text;
|
|
5851
|
+
if (name && NEXT_APP_METHODS.has(name)) out.push({ method: name, line });
|
|
5852
|
+
}
|
|
5853
|
+
}
|
|
5854
|
+
});
|
|
5855
|
+
return out;
|
|
5856
|
+
}
|
|
5857
|
+
function nextRoutesFromFile(source, relFile, parser) {
|
|
5858
|
+
if (isNextAppRouteFile(relFile)) {
|
|
5859
|
+
const tree = parseSource2(parser, source);
|
|
5860
|
+
const template = nextAppPathTemplate(relFile);
|
|
5861
|
+
return nextAppMethods(tree.rootNode).map(({ method, line }) => ({
|
|
5862
|
+
method,
|
|
5863
|
+
pathTemplate: canonicalizeTemplate(template),
|
|
5864
|
+
line,
|
|
5865
|
+
framework: "next"
|
|
5866
|
+
}));
|
|
5867
|
+
}
|
|
5868
|
+
if (isNextPagesApiFile(relFile)) {
|
|
5869
|
+
return [
|
|
5870
|
+
{
|
|
5871
|
+
method: "ALL",
|
|
5872
|
+
pathTemplate: canonicalizeTemplate(nextPagesApiPathTemplate(relFile)),
|
|
5873
|
+
line: 1,
|
|
5874
|
+
framework: "next"
|
|
5875
|
+
}
|
|
5876
|
+
];
|
|
5877
|
+
}
|
|
5878
|
+
return [];
|
|
5879
|
+
}
|
|
5880
|
+
async function addRoutes(graph, services) {
|
|
5881
|
+
const jsParser = makeJsParser2();
|
|
5882
|
+
let nodesAdded = 0;
|
|
5883
|
+
let edgesAdded = 0;
|
|
5884
|
+
for (const service of services) {
|
|
5885
|
+
const deps = {
|
|
5886
|
+
...service.pkg.dependencies ?? {},
|
|
5887
|
+
...service.pkg.devDependencies ?? {}
|
|
5888
|
+
};
|
|
5889
|
+
const hasExpress = deps["express"] !== void 0;
|
|
5890
|
+
const hasFastify = deps["fastify"] !== void 0;
|
|
5891
|
+
const hasNext = deps["next"] !== void 0;
|
|
5892
|
+
if (!hasExpress && !hasFastify && !hasNext) continue;
|
|
5893
|
+
const files = await loadSourceFiles(service.dir);
|
|
5894
|
+
for (const file of files) {
|
|
5895
|
+
if (isTestPath(file.path)) continue;
|
|
5896
|
+
if (!JS_ROUTE_EXTENSIONS.has(import_node_path24.default.extname(file.path))) continue;
|
|
5897
|
+
const relFile = toPosix2(import_node_path24.default.relative(service.dir, file.path));
|
|
5898
|
+
let routes;
|
|
5899
|
+
try {
|
|
5900
|
+
if (hasNext && (isNextAppRouteFile(relFile) || isNextPagesApiFile(relFile))) {
|
|
5901
|
+
routes = nextRoutesFromFile(file.content, relFile, jsParser);
|
|
5902
|
+
} else if (hasExpress || hasFastify) {
|
|
5903
|
+
routes = serverRoutesFromSource(file.content, jsParser, hasExpress, hasFastify);
|
|
5904
|
+
} else {
|
|
5905
|
+
routes = [];
|
|
5906
|
+
}
|
|
5907
|
+
} catch (err) {
|
|
5908
|
+
recordExtractionError("route extraction", file.path, err);
|
|
5909
|
+
continue;
|
|
5910
|
+
}
|
|
5911
|
+
if (routes.length === 0) continue;
|
|
5912
|
+
for (const route of routes) {
|
|
5913
|
+
const rid = (0, import_types12.routeId)(service.pkg.name, route.method, route.pathTemplate);
|
|
5914
|
+
if (!graph.hasNode(rid)) {
|
|
5915
|
+
const node = {
|
|
5916
|
+
id: rid,
|
|
5917
|
+
type: import_types12.NodeType.RouteNode,
|
|
5918
|
+
name: `${route.method} ${route.pathTemplate}`,
|
|
5919
|
+
service: service.pkg.name,
|
|
5920
|
+
method: route.method,
|
|
5921
|
+
pathTemplate: route.pathTemplate,
|
|
5922
|
+
path: relFile,
|
|
5923
|
+
line: route.line,
|
|
5924
|
+
framework: route.framework,
|
|
5925
|
+
discoveredVia: "static"
|
|
5926
|
+
};
|
|
5927
|
+
graph.addNode(rid, node);
|
|
5928
|
+
nodesAdded++;
|
|
5929
|
+
}
|
|
5930
|
+
const containsId = (0, import_types12.extractedEdgeId)(service.node.id, rid, import_types12.EdgeType.CONTAINS);
|
|
5931
|
+
if (!graph.hasEdge(containsId)) {
|
|
5932
|
+
const edge = {
|
|
5933
|
+
id: containsId,
|
|
5934
|
+
source: service.node.id,
|
|
5935
|
+
target: rid,
|
|
5936
|
+
type: import_types12.EdgeType.CONTAINS,
|
|
5937
|
+
provenance: import_types12.Provenance.EXTRACTED,
|
|
5938
|
+
confidence: (0, import_types12.confidenceForExtracted)("structural"),
|
|
5939
|
+
evidence: {
|
|
5940
|
+
file: relFile,
|
|
5941
|
+
line: route.line,
|
|
5942
|
+
snippet: snippet(file.content, route.line)
|
|
5943
|
+
}
|
|
5944
|
+
};
|
|
5945
|
+
graph.addEdgeWithKey(containsId, service.node.id, rid, edge);
|
|
5946
|
+
edgesAdded++;
|
|
5947
|
+
}
|
|
5948
|
+
}
|
|
5949
|
+
}
|
|
5950
|
+
}
|
|
5951
|
+
return { nodesAdded, edgesAdded };
|
|
5952
|
+
}
|
|
5953
|
+
|
|
5954
|
+
// src/extract/proto.ts
|
|
5955
|
+
init_cjs_shims();
|
|
5956
|
+
var import_node_fs17 = require("fs");
|
|
5957
|
+
var import_node_path25 = __toESM(require("path"), 1);
|
|
5958
|
+
var import_types13 = require("@neat.is/types");
|
|
5959
|
+
var PROTO_EXTENSION = ".proto";
|
|
5960
|
+
function packageOf(content) {
|
|
5961
|
+
const m = content.match(/^\s*package\s+([A-Za-z_][A-Za-z0-9_.]*)\s*;/m);
|
|
5962
|
+
return m ? m[1] : null;
|
|
5963
|
+
}
|
|
5964
|
+
function lineAt(content, offset) {
|
|
5965
|
+
return content.slice(0, offset).split("\n").length;
|
|
5966
|
+
}
|
|
5967
|
+
function grpcMethodsFromProto(content, fqPackage) {
|
|
5968
|
+
const out = [];
|
|
5969
|
+
const serviceRe = /\bservice\s+([A-Za-z_][A-Za-z0-9_]*)\s*\{/g;
|
|
5970
|
+
let sm;
|
|
5971
|
+
while ((sm = serviceRe.exec(content)) !== null) {
|
|
5972
|
+
const serviceName = sm[1];
|
|
5973
|
+
const rpcService = fqPackage ? `${fqPackage}.${serviceName}` : serviceName;
|
|
5974
|
+
const bodyStart = serviceRe.lastIndex;
|
|
5975
|
+
let depth = 1;
|
|
5976
|
+
let i = bodyStart;
|
|
5977
|
+
for (; i < content.length && depth > 0; i++) {
|
|
5978
|
+
const ch = content[i];
|
|
5979
|
+
if (ch === "{") depth++;
|
|
5980
|
+
else if (ch === "}") depth--;
|
|
5981
|
+
}
|
|
5982
|
+
const body = content.slice(bodyStart, i - 1);
|
|
5983
|
+
const rpcRe = /\brpc\s+([A-Za-z_][A-Za-z0-9_]*)\s*\(/g;
|
|
5984
|
+
let rm;
|
|
5985
|
+
while ((rm = rpcRe.exec(body)) !== null) {
|
|
5986
|
+
const rpcMethod = rm[1];
|
|
5987
|
+
const line = lineAt(content, bodyStart + rm.index);
|
|
5988
|
+
out.push({ rpcService, rpcMethod, line });
|
|
5989
|
+
}
|
|
5990
|
+
serviceRe.lastIndex = i;
|
|
5991
|
+
}
|
|
5992
|
+
return out;
|
|
5993
|
+
}
|
|
5994
|
+
async function walkProtoFiles(dir) {
|
|
5995
|
+
const out = [];
|
|
5996
|
+
async function walk3(current) {
|
|
5997
|
+
const entries = await import_node_fs17.promises.readdir(current, { withFileTypes: true }).catch(() => []);
|
|
5998
|
+
for (const entry of entries) {
|
|
5999
|
+
const full = import_node_path25.default.join(current, entry.name);
|
|
6000
|
+
if (entry.isDirectory()) {
|
|
6001
|
+
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
6002
|
+
if (await isPythonVenvDir(full)) continue;
|
|
6003
|
+
await walk3(full);
|
|
6004
|
+
} else if (entry.isFile() && import_node_path25.default.extname(entry.name) === PROTO_EXTENSION) {
|
|
6005
|
+
out.push(full);
|
|
6006
|
+
}
|
|
6007
|
+
}
|
|
6008
|
+
}
|
|
6009
|
+
await walk3(dir);
|
|
6010
|
+
return out;
|
|
6011
|
+
}
|
|
6012
|
+
async function addGrpcMethods(graph, services) {
|
|
6013
|
+
let nodesAdded = 0;
|
|
6014
|
+
let edgesAdded = 0;
|
|
6015
|
+
for (const service of services) {
|
|
6016
|
+
const protoPaths = await walkProtoFiles(service.dir);
|
|
6017
|
+
for (const protoPath of protoPaths) {
|
|
6018
|
+
if (isTestPath(protoPath)) continue;
|
|
6019
|
+
const relFile = toPosix2(import_node_path25.default.relative(service.dir, protoPath));
|
|
6020
|
+
let content;
|
|
6021
|
+
try {
|
|
6022
|
+
content = await import_node_fs17.promises.readFile(protoPath, "utf8");
|
|
6023
|
+
} catch (err) {
|
|
6024
|
+
recordExtractionError("proto extraction", protoPath, err);
|
|
6025
|
+
continue;
|
|
6026
|
+
}
|
|
6027
|
+
let methods;
|
|
6028
|
+
try {
|
|
6029
|
+
methods = grpcMethodsFromProto(content, packageOf(content));
|
|
6030
|
+
} catch (err) {
|
|
6031
|
+
recordExtractionError("proto extraction", protoPath, err);
|
|
6032
|
+
continue;
|
|
6033
|
+
}
|
|
6034
|
+
if (methods.length === 0) continue;
|
|
6035
|
+
for (const method of methods) {
|
|
6036
|
+
const mid = (0, import_types13.grpcMethodId)(method.rpcService, method.rpcMethod);
|
|
6037
|
+
if (!graph.hasNode(mid)) {
|
|
6038
|
+
const node = {
|
|
6039
|
+
id: mid,
|
|
6040
|
+
type: import_types13.NodeType.GrpcMethodNode,
|
|
6041
|
+
name: `${method.rpcService}/${method.rpcMethod}`,
|
|
6042
|
+
rpcService: method.rpcService,
|
|
6043
|
+
rpcMethod: method.rpcMethod,
|
|
6044
|
+
path: relFile,
|
|
6045
|
+
line: method.line,
|
|
6046
|
+
discoveredVia: "static"
|
|
6047
|
+
};
|
|
6048
|
+
graph.addNode(mid, node);
|
|
6049
|
+
nodesAdded++;
|
|
6050
|
+
}
|
|
6051
|
+
const containsId = (0, import_types13.extractedEdgeId)(service.node.id, mid, import_types13.EdgeType.CONTAINS);
|
|
6052
|
+
if (!graph.hasEdge(containsId)) {
|
|
6053
|
+
const edge = {
|
|
6054
|
+
id: containsId,
|
|
6055
|
+
source: service.node.id,
|
|
6056
|
+
target: mid,
|
|
6057
|
+
type: import_types13.EdgeType.CONTAINS,
|
|
6058
|
+
provenance: import_types13.Provenance.EXTRACTED,
|
|
6059
|
+
confidence: (0, import_types13.confidenceForExtracted)("structural"),
|
|
6060
|
+
evidence: {
|
|
6061
|
+
file: relFile,
|
|
6062
|
+
line: method.line,
|
|
6063
|
+
snippet: snippet(content, method.line)
|
|
6064
|
+
}
|
|
6065
|
+
};
|
|
6066
|
+
graph.addEdgeWithKey(containsId, service.node.id, mid, edge);
|
|
6067
|
+
edgesAdded++;
|
|
6068
|
+
}
|
|
6069
|
+
}
|
|
6070
|
+
}
|
|
6071
|
+
}
|
|
6072
|
+
return { nodesAdded, edgesAdded };
|
|
6073
|
+
}
|
|
6074
|
+
|
|
5393
6075
|
// src/extract/calls/index.ts
|
|
5394
6076
|
init_cjs_shims();
|
|
5395
|
-
var
|
|
6077
|
+
var import_types21 = require("@neat.is/types");
|
|
5396
6078
|
|
|
5397
6079
|
// src/extract/calls/http.ts
|
|
5398
6080
|
init_cjs_shims();
|
|
5399
|
-
var
|
|
5400
|
-
var
|
|
5401
|
-
var
|
|
6081
|
+
var import_node_path26 = __toESM(require("path"), 1);
|
|
6082
|
+
var import_tree_sitter3 = __toESM(require("tree-sitter"), 1);
|
|
6083
|
+
var import_tree_sitter_javascript3 = __toESM(require("tree-sitter-javascript"), 1);
|
|
5402
6084
|
var import_tree_sitter_python2 = __toESM(require("tree-sitter-python"), 1);
|
|
5403
|
-
var
|
|
6085
|
+
var import_types14 = require("@neat.is/types");
|
|
5404
6086
|
var STRING_LITERAL_NODE_TYPES = /* @__PURE__ */ new Set(["string_fragment", "string_content"]);
|
|
5405
6087
|
var JSX_EXTERNAL_LINK_TAGS = /* @__PURE__ */ new Set(["a", "Link", "NavLink", "ExternalLink", "Anchor"]);
|
|
5406
6088
|
function isInsideJsxExternalLink(node) {
|
|
@@ -5428,14 +6110,14 @@ function collectStringLiterals(node, out) {
|
|
|
5428
6110
|
if (child) collectStringLiterals(child, out);
|
|
5429
6111
|
}
|
|
5430
6112
|
}
|
|
5431
|
-
var
|
|
5432
|
-
function
|
|
6113
|
+
var PARSE_CHUNK3 = 16384;
|
|
6114
|
+
function parseSource3(parser, source) {
|
|
5433
6115
|
return parser.parse(
|
|
5434
|
-
(index) => index >= source.length ? "" : source.slice(index, index +
|
|
6116
|
+
(index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK3)
|
|
5435
6117
|
);
|
|
5436
6118
|
}
|
|
5437
6119
|
function callsFromSource(source, parser, knownHosts) {
|
|
5438
|
-
const tree =
|
|
6120
|
+
const tree = parseSource3(parser, source);
|
|
5439
6121
|
const literals = [];
|
|
5440
6122
|
collectStringLiterals(tree.rootNode, literals);
|
|
5441
6123
|
const out = [];
|
|
@@ -5449,27 +6131,20 @@ function callsFromSource(source, parser, knownHosts) {
|
|
|
5449
6131
|
}
|
|
5450
6132
|
return out;
|
|
5451
6133
|
}
|
|
5452
|
-
function
|
|
5453
|
-
const p = new
|
|
5454
|
-
p.setLanguage(
|
|
6134
|
+
function makeJsParser3() {
|
|
6135
|
+
const p = new import_tree_sitter3.default();
|
|
6136
|
+
p.setLanguage(import_tree_sitter_javascript3.default);
|
|
5455
6137
|
return p;
|
|
5456
6138
|
}
|
|
5457
6139
|
function makePyParser2() {
|
|
5458
|
-
const p = new
|
|
6140
|
+
const p = new import_tree_sitter3.default();
|
|
5459
6141
|
p.setLanguage(import_tree_sitter_python2.default);
|
|
5460
6142
|
return p;
|
|
5461
6143
|
}
|
|
5462
6144
|
async function addHttpCallEdges(graph, services) {
|
|
5463
|
-
const jsParser =
|
|
6145
|
+
const jsParser = makeJsParser3();
|
|
5464
6146
|
const pyParser = makePyParser2();
|
|
5465
|
-
const knownHosts
|
|
5466
|
-
const hostToNodeId = /* @__PURE__ */ new Map();
|
|
5467
|
-
for (const service of services) {
|
|
5468
|
-
knownHosts.add(import_node_path24.default.basename(service.dir));
|
|
5469
|
-
knownHosts.add(service.pkg.name);
|
|
5470
|
-
hostToNodeId.set(import_node_path24.default.basename(service.dir), service.node.id);
|
|
5471
|
-
hostToNodeId.set(service.pkg.name, service.node.id);
|
|
5472
|
-
}
|
|
6147
|
+
const { knownHosts, hostToNodeId } = buildServiceHostIndex(services);
|
|
5473
6148
|
let nodesAdded = 0;
|
|
5474
6149
|
let edgesAdded = 0;
|
|
5475
6150
|
for (const service of services) {
|
|
@@ -5477,7 +6152,7 @@ async function addHttpCallEdges(graph, services) {
|
|
|
5477
6152
|
const seen = /* @__PURE__ */ new Set();
|
|
5478
6153
|
for (const file of files) {
|
|
5479
6154
|
if (isTestPath(file.path)) continue;
|
|
5480
|
-
const parser =
|
|
6155
|
+
const parser = import_node_path26.default.extname(file.path) === ".py" ? pyParser : jsParser;
|
|
5481
6156
|
let sites;
|
|
5482
6157
|
try {
|
|
5483
6158
|
sites = callsFromSource(file.content, parser, knownHosts);
|
|
@@ -5486,14 +6161,14 @@ async function addHttpCallEdges(graph, services) {
|
|
|
5486
6161
|
continue;
|
|
5487
6162
|
}
|
|
5488
6163
|
if (sites.length === 0) continue;
|
|
5489
|
-
const relFile = toPosix2(
|
|
6164
|
+
const relFile = toPosix2(import_node_path26.default.relative(service.dir, file.path));
|
|
5490
6165
|
for (const site of sites) {
|
|
5491
6166
|
const targetId = hostToNodeId.get(site.host);
|
|
5492
6167
|
if (!targetId || targetId === service.node.id) continue;
|
|
5493
6168
|
const dedupKey = `${relFile}|${targetId}`;
|
|
5494
6169
|
if (seen.has(dedupKey)) continue;
|
|
5495
6170
|
seen.add(dedupKey);
|
|
5496
|
-
const confidence = (0,
|
|
6171
|
+
const confidence = (0, import_types14.confidenceForExtracted)("url-literal-service-target");
|
|
5497
6172
|
const ev = {
|
|
5498
6173
|
file: relFile,
|
|
5499
6174
|
line: site.line,
|
|
@@ -5507,25 +6182,25 @@ async function addHttpCallEdges(graph, services) {
|
|
|
5507
6182
|
);
|
|
5508
6183
|
nodesAdded += n;
|
|
5509
6184
|
edgesAdded += e;
|
|
5510
|
-
if (!(0,
|
|
6185
|
+
if (!(0, import_types14.passesExtractedFloor)(confidence)) {
|
|
5511
6186
|
noteExtractedDropped({
|
|
5512
6187
|
source: fileNodeId,
|
|
5513
6188
|
target: targetId,
|
|
5514
|
-
type:
|
|
6189
|
+
type: import_types14.EdgeType.CALLS,
|
|
5515
6190
|
confidence,
|
|
5516
6191
|
confidenceKind: "url-literal-service-target",
|
|
5517
6192
|
evidence: ev
|
|
5518
6193
|
});
|
|
5519
6194
|
continue;
|
|
5520
6195
|
}
|
|
5521
|
-
const edgeId = (0, import_types5.extractedEdgeId)(fileNodeId, targetId,
|
|
6196
|
+
const edgeId = (0, import_types5.extractedEdgeId)(fileNodeId, targetId, import_types14.EdgeType.CALLS);
|
|
5522
6197
|
if (!graph.hasEdge(edgeId)) {
|
|
5523
6198
|
const edge = {
|
|
5524
6199
|
id: edgeId,
|
|
5525
6200
|
source: fileNodeId,
|
|
5526
6201
|
target: targetId,
|
|
5527
|
-
type:
|
|
5528
|
-
provenance:
|
|
6202
|
+
type: import_types14.EdgeType.CALLS,
|
|
6203
|
+
provenance: import_types14.Provenance.EXTRACTED,
|
|
5529
6204
|
confidence,
|
|
5530
6205
|
evidence: ev
|
|
5531
6206
|
};
|
|
@@ -5538,10 +6213,279 @@ async function addHttpCallEdges(graph, services) {
|
|
|
5538
6213
|
return { nodesAdded, edgesAdded };
|
|
5539
6214
|
}
|
|
5540
6215
|
|
|
6216
|
+
// src/extract/calls/route-match.ts
|
|
6217
|
+
init_cjs_shims();
|
|
6218
|
+
var import_node_path27 = __toESM(require("path"), 1);
|
|
6219
|
+
var import_tree_sitter4 = __toESM(require("tree-sitter"), 1);
|
|
6220
|
+
var import_tree_sitter_javascript4 = __toESM(require("tree-sitter-javascript"), 1);
|
|
6221
|
+
var import_types15 = require("@neat.is/types");
|
|
6222
|
+
var PARSE_CHUNK4 = 16384;
|
|
6223
|
+
function parseSource4(parser, source) {
|
|
6224
|
+
return parser.parse(
|
|
6225
|
+
(index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK4)
|
|
6226
|
+
);
|
|
6227
|
+
}
|
|
6228
|
+
function makeJsParser4() {
|
|
6229
|
+
const p = new import_tree_sitter4.default();
|
|
6230
|
+
p.setLanguage(import_tree_sitter_javascript4.default);
|
|
6231
|
+
return p;
|
|
6232
|
+
}
|
|
6233
|
+
var JS_CLIENT_EXTENSIONS = /* @__PURE__ */ new Set([".js", ".jsx", ".mjs", ".cjs", ".ts", ".tsx"]);
|
|
6234
|
+
var AXIOS_METHODS = /* @__PURE__ */ new Set(["get", "post", "put", "patch", "delete", "head", "options", "request"]);
|
|
6235
|
+
function walk2(node, visit) {
|
|
6236
|
+
visit(node);
|
|
6237
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
6238
|
+
const child = node.namedChild(i);
|
|
6239
|
+
if (child) walk2(child, visit);
|
|
6240
|
+
}
|
|
6241
|
+
}
|
|
6242
|
+
function reconstructUrl(node) {
|
|
6243
|
+
if (node.type === "string") {
|
|
6244
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
6245
|
+
const child = node.namedChild(i);
|
|
6246
|
+
if (child?.type === "string_fragment") return child.text;
|
|
6247
|
+
}
|
|
6248
|
+
return "";
|
|
6249
|
+
}
|
|
6250
|
+
if (node.type === "template_string") {
|
|
6251
|
+
let out = "";
|
|
6252
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
6253
|
+
const child = node.namedChild(i);
|
|
6254
|
+
if (!child) continue;
|
|
6255
|
+
if (child.type === "string_fragment") out += child.text;
|
|
6256
|
+
else if (child.type === "template_substitution") out += ":param";
|
|
6257
|
+
}
|
|
6258
|
+
if (out.length === 0) {
|
|
6259
|
+
const raw = node.text;
|
|
6260
|
+
return raw.length >= 2 ? raw.slice(1, -1) : "";
|
|
6261
|
+
}
|
|
6262
|
+
return out;
|
|
6263
|
+
}
|
|
6264
|
+
return null;
|
|
6265
|
+
}
|
|
6266
|
+
function methodFromOptions(objNode) {
|
|
6267
|
+
for (let i = 0; i < objNode.namedChildCount; i++) {
|
|
6268
|
+
const pair = objNode.namedChild(i);
|
|
6269
|
+
if (!pair || pair.type !== "pair") continue;
|
|
6270
|
+
const k = pair.childForFieldName("key");
|
|
6271
|
+
const kText = k ? k.type === "string" ? stringText(k) : k.text : null;
|
|
6272
|
+
if (kText !== "method") continue;
|
|
6273
|
+
const v = pair.childForFieldName("value");
|
|
6274
|
+
if (v && (v.type === "string" || v.type === "template_string")) {
|
|
6275
|
+
const s = stringText(v);
|
|
6276
|
+
return s ? s.toUpperCase() : void 0;
|
|
6277
|
+
}
|
|
6278
|
+
}
|
|
6279
|
+
return void 0;
|
|
6280
|
+
}
|
|
6281
|
+
function stringText(node) {
|
|
6282
|
+
if (node.type === "string") {
|
|
6283
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
6284
|
+
const child = node.namedChild(i);
|
|
6285
|
+
if (child?.type === "string_fragment") return child.text;
|
|
6286
|
+
}
|
|
6287
|
+
return "";
|
|
6288
|
+
}
|
|
6289
|
+
return null;
|
|
6290
|
+
}
|
|
6291
|
+
function urlNodeFromConfig(objNode) {
|
|
6292
|
+
for (let i = 0; i < objNode.namedChildCount; i++) {
|
|
6293
|
+
const pair = objNode.namedChild(i);
|
|
6294
|
+
if (!pair || pair.type !== "pair") continue;
|
|
6295
|
+
const k = pair.childForFieldName("key");
|
|
6296
|
+
const kText = k ? k.type === "string" ? stringText(k) : k.text : null;
|
|
6297
|
+
if (kText === "url") return pair.childForFieldName("value");
|
|
6298
|
+
}
|
|
6299
|
+
return null;
|
|
6300
|
+
}
|
|
6301
|
+
function pathOf(urlStr) {
|
|
6302
|
+
try {
|
|
6303
|
+
const candidate = urlStr.startsWith("//") ? `http:${urlStr}` : urlStr;
|
|
6304
|
+
const parsed = new URL(candidate);
|
|
6305
|
+
return parsed.pathname || "/";
|
|
6306
|
+
} catch {
|
|
6307
|
+
return null;
|
|
6308
|
+
}
|
|
6309
|
+
}
|
|
6310
|
+
function matchHost(urlStr, knownHosts) {
|
|
6311
|
+
for (const host of knownHosts) {
|
|
6312
|
+
if (urlMatchesHost(urlStr, host)) return host;
|
|
6313
|
+
}
|
|
6314
|
+
return null;
|
|
6315
|
+
}
|
|
6316
|
+
function clientCallSitesFromSource(source, parser, knownHosts) {
|
|
6317
|
+
const tree = parseSource4(parser, source);
|
|
6318
|
+
const out = [];
|
|
6319
|
+
const push = (urlNode, method, callNode) => {
|
|
6320
|
+
const urlStr = reconstructUrl(urlNode);
|
|
6321
|
+
if (!urlStr) return;
|
|
6322
|
+
const host = matchHost(urlStr, knownHosts);
|
|
6323
|
+
if (!host) return;
|
|
6324
|
+
const p = pathOf(urlStr);
|
|
6325
|
+
if (p === null) return;
|
|
6326
|
+
const line = callNode.startPosition.row + 1;
|
|
6327
|
+
out.push({
|
|
6328
|
+
host,
|
|
6329
|
+
method,
|
|
6330
|
+
pathTemplate: p,
|
|
6331
|
+
line,
|
|
6332
|
+
snippet: snippet(source, line)
|
|
6333
|
+
});
|
|
6334
|
+
};
|
|
6335
|
+
walk2(tree.rootNode, (node) => {
|
|
6336
|
+
if (node.type !== "call_expression") return;
|
|
6337
|
+
const fn = node.childForFieldName("function");
|
|
6338
|
+
if (!fn) return;
|
|
6339
|
+
const args = node.childForFieldName("arguments");
|
|
6340
|
+
const first = args?.namedChild(0);
|
|
6341
|
+
if (!first) return;
|
|
6342
|
+
const fnName = fn.type === "identifier" ? fn.text : fn.type === "member_expression" ? fn.childForFieldName("property")?.text ?? "" : "";
|
|
6343
|
+
if (fn.type === "identifier" && fnName === "fetch") {
|
|
6344
|
+
const opts = args?.namedChild(1);
|
|
6345
|
+
const method = opts && opts.type === "object" ? methodFromOptions(opts) ?? "GET" : "GET";
|
|
6346
|
+
push(first, method, node);
|
|
6347
|
+
return;
|
|
6348
|
+
}
|
|
6349
|
+
if (fn.type === "identifier" && fnName === "axios") {
|
|
6350
|
+
if (first.type === "object") {
|
|
6351
|
+
const urlNode = urlNodeFromConfig(first);
|
|
6352
|
+
if (urlNode) push(urlNode, methodFromOptions(first) ?? "GET", node);
|
|
6353
|
+
} else {
|
|
6354
|
+
const opts = args?.namedChild(1);
|
|
6355
|
+
const method = opts && opts.type === "object" ? methodFromOptions(opts) ?? "GET" : "GET";
|
|
6356
|
+
push(first, method, node);
|
|
6357
|
+
}
|
|
6358
|
+
return;
|
|
6359
|
+
}
|
|
6360
|
+
if (fn.type === "member_expression") {
|
|
6361
|
+
const obj = fn.childForFieldName("object");
|
|
6362
|
+
const objName = obj?.text ?? "";
|
|
6363
|
+
if (objName === "axios" && AXIOS_METHODS.has(fnName)) {
|
|
6364
|
+
if (fnName === "request" && first.type === "object") {
|
|
6365
|
+
const urlNode = urlNodeFromConfig(first);
|
|
6366
|
+
if (urlNode) push(urlNode, methodFromOptions(first) ?? "GET", node);
|
|
6367
|
+
} else {
|
|
6368
|
+
push(first, fnName.toUpperCase(), node);
|
|
6369
|
+
}
|
|
6370
|
+
return;
|
|
6371
|
+
}
|
|
6372
|
+
if ((objName === "http" || objName === "https") && (fnName === "request" || fnName === "get")) {
|
|
6373
|
+
const opts = args?.namedChild(1);
|
|
6374
|
+
const method = opts && opts.type === "object" ? methodFromOptions(opts) ?? (fnName === "get" ? "GET" : "GET") : "GET";
|
|
6375
|
+
push(first, method, node);
|
|
6376
|
+
return;
|
|
6377
|
+
}
|
|
6378
|
+
}
|
|
6379
|
+
});
|
|
6380
|
+
return out;
|
|
6381
|
+
}
|
|
6382
|
+
function buildRouteIndex(graph) {
|
|
6383
|
+
const index = /* @__PURE__ */ new Map();
|
|
6384
|
+
graph.forEachNode((_id, attrs) => {
|
|
6385
|
+
const node = attrs;
|
|
6386
|
+
if (node.type !== import_types15.NodeType.RouteNode) return;
|
|
6387
|
+
const route = attrs;
|
|
6388
|
+
const owner = (0, import_types15.serviceId)(route.service);
|
|
6389
|
+
const entry = {
|
|
6390
|
+
method: route.method.toUpperCase(),
|
|
6391
|
+
normalizedPath: normalizePathTemplate(route.pathTemplate),
|
|
6392
|
+
routeNodeId: route.id
|
|
6393
|
+
};
|
|
6394
|
+
const list = index.get(owner);
|
|
6395
|
+
if (list) list.push(entry);
|
|
6396
|
+
else index.set(owner, [entry]);
|
|
6397
|
+
});
|
|
6398
|
+
return index;
|
|
6399
|
+
}
|
|
6400
|
+
function findRoute(entries, method, normalizedPath) {
|
|
6401
|
+
return entries.find(
|
|
6402
|
+
(e) => e.normalizedPath === normalizedPath && (e.method === "ALL" || method === void 0 || e.method === method)
|
|
6403
|
+
);
|
|
6404
|
+
}
|
|
6405
|
+
async function addRouteCallEdges(graph, services) {
|
|
6406
|
+
const jsParser = makeJsParser4();
|
|
6407
|
+
const { knownHosts, hostToNodeId } = buildServiceHostIndex(services);
|
|
6408
|
+
const routeIndex = buildRouteIndex(graph);
|
|
6409
|
+
if (routeIndex.size === 0) return { nodesAdded: 0, edgesAdded: 0 };
|
|
6410
|
+
let nodesAdded = 0;
|
|
6411
|
+
let edgesAdded = 0;
|
|
6412
|
+
for (const service of services) {
|
|
6413
|
+
const files = await loadSourceFiles(service.dir);
|
|
6414
|
+
const seen = /* @__PURE__ */ new Set();
|
|
6415
|
+
for (const file of files) {
|
|
6416
|
+
if (isTestPath(file.path)) continue;
|
|
6417
|
+
if (!JS_CLIENT_EXTENSIONS.has(import_node_path27.default.extname(file.path))) continue;
|
|
6418
|
+
let sites;
|
|
6419
|
+
try {
|
|
6420
|
+
sites = clientCallSitesFromSource(file.content, jsParser, knownHosts);
|
|
6421
|
+
} catch (err) {
|
|
6422
|
+
recordExtractionError("route-match call extraction", file.path, err);
|
|
6423
|
+
continue;
|
|
6424
|
+
}
|
|
6425
|
+
if (sites.length === 0) continue;
|
|
6426
|
+
const relFile = toPosix2(import_node_path27.default.relative(service.dir, file.path));
|
|
6427
|
+
for (const site of sites) {
|
|
6428
|
+
const serverServiceId = hostToNodeId.get(site.host);
|
|
6429
|
+
if (!serverServiceId || serverServiceId === service.node.id) continue;
|
|
6430
|
+
const entries = routeIndex.get(serverServiceId);
|
|
6431
|
+
if (!entries) continue;
|
|
6432
|
+
const normalizedPath = normalizePathTemplate(site.pathTemplate);
|
|
6433
|
+
const match = findRoute(entries, site.method, normalizedPath);
|
|
6434
|
+
if (!match) continue;
|
|
6435
|
+
const dedupKey = `${relFile}|${match.routeNodeId}`;
|
|
6436
|
+
if (seen.has(dedupKey)) continue;
|
|
6437
|
+
seen.add(dedupKey);
|
|
6438
|
+
const { fileNodeId, nodesAdded: n, edgesAdded: e } = ensureFileNode(
|
|
6439
|
+
graph,
|
|
6440
|
+
service.pkg.name,
|
|
6441
|
+
service.node.id,
|
|
6442
|
+
relFile
|
|
6443
|
+
);
|
|
6444
|
+
nodesAdded += n;
|
|
6445
|
+
edgesAdded += e;
|
|
6446
|
+
const confidence = (0, import_types15.confidenceForExtracted)("verified-call-site");
|
|
6447
|
+
const ev = {
|
|
6448
|
+
file: relFile,
|
|
6449
|
+
line: site.line,
|
|
6450
|
+
snippet: site.snippet,
|
|
6451
|
+
method: site.method ?? match.method,
|
|
6452
|
+
pathTemplate: site.pathTemplate
|
|
6453
|
+
};
|
|
6454
|
+
if (!(0, import_types15.passesExtractedFloor)(confidence)) {
|
|
6455
|
+
noteExtractedDropped({
|
|
6456
|
+
source: fileNodeId,
|
|
6457
|
+
target: match.routeNodeId,
|
|
6458
|
+
type: import_types15.EdgeType.CALLS,
|
|
6459
|
+
confidence,
|
|
6460
|
+
confidenceKind: "verified-call-site",
|
|
6461
|
+
evidence: ev
|
|
6462
|
+
});
|
|
6463
|
+
continue;
|
|
6464
|
+
}
|
|
6465
|
+
const edgeId = (0, import_types5.extractedEdgeId)(fileNodeId, match.routeNodeId, import_types15.EdgeType.CALLS);
|
|
6466
|
+
if (!graph.hasEdge(edgeId)) {
|
|
6467
|
+
const edge = {
|
|
6468
|
+
id: edgeId,
|
|
6469
|
+
source: fileNodeId,
|
|
6470
|
+
target: match.routeNodeId,
|
|
6471
|
+
type: import_types15.EdgeType.CALLS,
|
|
6472
|
+
provenance: import_types15.Provenance.EXTRACTED,
|
|
6473
|
+
confidence,
|
|
6474
|
+
evidence: ev
|
|
6475
|
+
};
|
|
6476
|
+
graph.addEdgeWithKey(edgeId, fileNodeId, match.routeNodeId, edge);
|
|
6477
|
+
edgesAdded++;
|
|
6478
|
+
}
|
|
6479
|
+
}
|
|
6480
|
+
}
|
|
6481
|
+
}
|
|
6482
|
+
return { nodesAdded, edgesAdded };
|
|
6483
|
+
}
|
|
6484
|
+
|
|
5541
6485
|
// src/extract/calls/kafka.ts
|
|
5542
6486
|
init_cjs_shims();
|
|
5543
|
-
var
|
|
5544
|
-
var
|
|
6487
|
+
var import_node_path28 = __toESM(require("path"), 1);
|
|
6488
|
+
var import_types16 = require("@neat.is/types");
|
|
5545
6489
|
var PRODUCER_TOPIC_RE = /(?:producer|kafkaProducer)[\s\S]{0,40}?\.send\s*\(\s*\{[\s\S]{0,200}?topic\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
5546
6490
|
var CONSUMER_TOPIC_RE = /(?:consumer|kafkaConsumer)[\s\S]{0,40}?\.(?:subscribe|run)\s*\(\s*\{[\s\S]{0,200}?topic[s]?\s*:\s*(?:\[\s*)?['"`]([^'"`]+)['"`]/g;
|
|
5547
6491
|
function findAll(re, text) {
|
|
@@ -5562,7 +6506,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
|
|
|
5562
6506
|
seen.add(key);
|
|
5563
6507
|
const line = lineOf(file.content, topic);
|
|
5564
6508
|
out.push({
|
|
5565
|
-
infraId: (0,
|
|
6509
|
+
infraId: (0, import_types16.infraId)("kafka-topic", topic),
|
|
5566
6510
|
name: topic,
|
|
5567
6511
|
kind: "kafka-topic",
|
|
5568
6512
|
edgeType,
|
|
@@ -5571,7 +6515,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
|
|
|
5571
6515
|
// tier (ADR-066).
|
|
5572
6516
|
confidenceKind: "verified-call-site",
|
|
5573
6517
|
evidence: {
|
|
5574
|
-
file:
|
|
6518
|
+
file: import_node_path28.default.relative(serviceDir, file.path),
|
|
5575
6519
|
line,
|
|
5576
6520
|
snippet: snippet(file.content, line)
|
|
5577
6521
|
}
|
|
@@ -5584,8 +6528,8 @@ function kafkaEndpointsFromFile(file, serviceDir) {
|
|
|
5584
6528
|
|
|
5585
6529
|
// src/extract/calls/redis.ts
|
|
5586
6530
|
init_cjs_shims();
|
|
5587
|
-
var
|
|
5588
|
-
var
|
|
6531
|
+
var import_node_path29 = __toESM(require("path"), 1);
|
|
6532
|
+
var import_types17 = require("@neat.is/types");
|
|
5589
6533
|
var REDIS_URL_RE = /redis(?:s)?:\/\/(?:[^@'"`\s]+@)?([^:/'"`\s]+)(?::(\d+))?/g;
|
|
5590
6534
|
function redisEndpointsFromFile(file, serviceDir) {
|
|
5591
6535
|
const out = [];
|
|
@@ -5598,7 +6542,7 @@ function redisEndpointsFromFile(file, serviceDir) {
|
|
|
5598
6542
|
seen.add(host);
|
|
5599
6543
|
const line = lineOf(file.content, host);
|
|
5600
6544
|
out.push({
|
|
5601
|
-
infraId: (0,
|
|
6545
|
+
infraId: (0, import_types17.infraId)("redis", host),
|
|
5602
6546
|
name: host,
|
|
5603
6547
|
kind: "redis",
|
|
5604
6548
|
edgeType: "CALLS",
|
|
@@ -5607,7 +6551,7 @@ function redisEndpointsFromFile(file, serviceDir) {
|
|
|
5607
6551
|
// support tier (ADR-066).
|
|
5608
6552
|
confidenceKind: "url-with-structural-support",
|
|
5609
6553
|
evidence: {
|
|
5610
|
-
file:
|
|
6554
|
+
file: import_node_path29.default.relative(serviceDir, file.path),
|
|
5611
6555
|
line,
|
|
5612
6556
|
snippet: snippet(file.content, line)
|
|
5613
6557
|
}
|
|
@@ -5618,8 +6562,8 @@ function redisEndpointsFromFile(file, serviceDir) {
|
|
|
5618
6562
|
|
|
5619
6563
|
// src/extract/calls/aws.ts
|
|
5620
6564
|
init_cjs_shims();
|
|
5621
|
-
var
|
|
5622
|
-
var
|
|
6565
|
+
var import_node_path30 = __toESM(require("path"), 1);
|
|
6566
|
+
var import_types18 = require("@neat.is/types");
|
|
5623
6567
|
var S3_BUCKET_RE = /Bucket\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
5624
6568
|
var DYNAMO_TABLE_RE = /TableName\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
5625
6569
|
function hasMarker(text, markers) {
|
|
@@ -5643,7 +6587,7 @@ function awsEndpointsFromFile(file, serviceDir) {
|
|
|
5643
6587
|
seen.add(key);
|
|
5644
6588
|
const line = lineOf(file.content, name);
|
|
5645
6589
|
out.push({
|
|
5646
|
-
infraId: (0,
|
|
6590
|
+
infraId: (0, import_types18.infraId)(kind, name),
|
|
5647
6591
|
name,
|
|
5648
6592
|
kind,
|
|
5649
6593
|
edgeType: "CALLS",
|
|
@@ -5652,7 +6596,7 @@ function awsEndpointsFromFile(file, serviceDir) {
|
|
|
5652
6596
|
// (ADR-066).
|
|
5653
6597
|
confidenceKind: "verified-call-site",
|
|
5654
6598
|
evidence: {
|
|
5655
|
-
file:
|
|
6599
|
+
file: import_node_path30.default.relative(serviceDir, file.path),
|
|
5656
6600
|
line,
|
|
5657
6601
|
snippet: snippet(file.content, line)
|
|
5658
6602
|
}
|
|
@@ -5677,8 +6621,8 @@ function awsEndpointsFromFile(file, serviceDir) {
|
|
|
5677
6621
|
|
|
5678
6622
|
// src/extract/calls/grpc.ts
|
|
5679
6623
|
init_cjs_shims();
|
|
5680
|
-
var
|
|
5681
|
-
var
|
|
6624
|
+
var import_node_path31 = __toESM(require("path"), 1);
|
|
6625
|
+
var import_types19 = require("@neat.is/types");
|
|
5682
6626
|
var GRPC_CLIENT_RE = /new\s+([A-Z][A-Za-z0-9_]*)Client\s*\(\s*['"`]?([^,'"`)]+)?/g;
|
|
5683
6627
|
var AWS_SDK_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@aws-sdk\/client-([a-z0-9-]+)['"`]/g;
|
|
5684
6628
|
var GRPC_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@grpc\/grpc-js['"`]|_grpc_pb['"`]/;
|
|
@@ -5727,7 +6671,7 @@ function grpcEndpointsFromFile(file, serviceDir) {
|
|
|
5727
6671
|
const { kind } = classified;
|
|
5728
6672
|
const line = lineOf(file.content, m[0]);
|
|
5729
6673
|
out.push({
|
|
5730
|
-
infraId: (0,
|
|
6674
|
+
infraId: (0, import_types19.infraId)(kind, name),
|
|
5731
6675
|
name,
|
|
5732
6676
|
kind,
|
|
5733
6677
|
edgeType: "CALLS",
|
|
@@ -5736,7 +6680,7 @@ function grpcEndpointsFromFile(file, serviceDir) {
|
|
|
5736
6680
|
// tier (ADR-066).
|
|
5737
6681
|
confidenceKind: "verified-call-site",
|
|
5738
6682
|
evidence: {
|
|
5739
|
-
file:
|
|
6683
|
+
file: import_node_path31.default.relative(serviceDir, file.path),
|
|
5740
6684
|
line,
|
|
5741
6685
|
snippet: snippet(file.content, line)
|
|
5742
6686
|
}
|
|
@@ -5747,8 +6691,8 @@ function grpcEndpointsFromFile(file, serviceDir) {
|
|
|
5747
6691
|
|
|
5748
6692
|
// src/extract/calls/supabase.ts
|
|
5749
6693
|
init_cjs_shims();
|
|
5750
|
-
var
|
|
5751
|
-
var
|
|
6694
|
+
var import_node_path32 = __toESM(require("path"), 1);
|
|
6695
|
+
var import_types20 = require("@neat.is/types");
|
|
5752
6696
|
var SUPABASE_JS_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@supabase\/supabase-js['"`]/;
|
|
5753
6697
|
var SUPABASE_SSR_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@supabase\/ssr['"`]/;
|
|
5754
6698
|
var SUPABASE_CLIENT_RE = /\b(createClient|createServerClient|createBrowserClient)\s*\(\s*(?:['"`]([^'"`]*)['"`])?/g;
|
|
@@ -5786,7 +6730,7 @@ function supabaseEndpointsFromFile(file, serviceDir) {
|
|
|
5786
6730
|
seen.add(name);
|
|
5787
6731
|
const line = lineOf(file.content, m[0]);
|
|
5788
6732
|
out.push({
|
|
5789
|
-
infraId: (0,
|
|
6733
|
+
infraId: (0, import_types20.infraId)("supabase", name),
|
|
5790
6734
|
name,
|
|
5791
6735
|
kind: "supabase",
|
|
5792
6736
|
edgeType: "CALLS",
|
|
@@ -5796,7 +6740,7 @@ function supabaseEndpointsFromFile(file, serviceDir) {
|
|
|
5796
6740
|
// tier (ADR-066), the same grade aws.ts / grpc.ts emit at.
|
|
5797
6741
|
confidenceKind: "verified-call-site",
|
|
5798
6742
|
evidence: {
|
|
5799
|
-
file:
|
|
6743
|
+
file: import_node_path32.default.relative(serviceDir, file.path),
|
|
5800
6744
|
line,
|
|
5801
6745
|
snippet: snippet(file.content, line)
|
|
5802
6746
|
}
|
|
@@ -5809,11 +6753,11 @@ function supabaseEndpointsFromFile(file, serviceDir) {
|
|
|
5809
6753
|
function edgeTypeFromEndpoint(ep) {
|
|
5810
6754
|
switch (ep.edgeType) {
|
|
5811
6755
|
case "PUBLISHES_TO":
|
|
5812
|
-
return
|
|
6756
|
+
return import_types21.EdgeType.PUBLISHES_TO;
|
|
5813
6757
|
case "CONSUMES_FROM":
|
|
5814
|
-
return
|
|
6758
|
+
return import_types21.EdgeType.CONSUMES_FROM;
|
|
5815
6759
|
default:
|
|
5816
|
-
return
|
|
6760
|
+
return import_types21.EdgeType.CALLS;
|
|
5817
6761
|
}
|
|
5818
6762
|
}
|
|
5819
6763
|
function isAwsKind(kind) {
|
|
@@ -5841,7 +6785,7 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
5841
6785
|
if (!graph.hasNode(ep.infraId)) {
|
|
5842
6786
|
const node = {
|
|
5843
6787
|
id: ep.infraId,
|
|
5844
|
-
type:
|
|
6788
|
+
type: import_types21.NodeType.InfraNode,
|
|
5845
6789
|
name: ep.name,
|
|
5846
6790
|
// #238 — `aws-*` covers AWS-SDK client kinds (aws-s3, aws-dynamodb,
|
|
5847
6791
|
// aws-cognito-identity-provider, …); `s3-` / `dynamodb-` cover the
|
|
@@ -5853,7 +6797,7 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
5853
6797
|
nodesAdded++;
|
|
5854
6798
|
}
|
|
5855
6799
|
const edgeType = edgeTypeFromEndpoint(ep);
|
|
5856
|
-
const confidence = (0,
|
|
6800
|
+
const confidence = (0, import_types21.confidenceForExtracted)(ep.confidenceKind);
|
|
5857
6801
|
const relFile = toPosix2(ep.evidence.file);
|
|
5858
6802
|
const { fileNodeId, nodesAdded: n, edgesAdded: e } = ensureFileNode(
|
|
5859
6803
|
graph,
|
|
@@ -5863,7 +6807,7 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
5863
6807
|
);
|
|
5864
6808
|
nodesAdded += n;
|
|
5865
6809
|
edgesAdded += e;
|
|
5866
|
-
if (!(0,
|
|
6810
|
+
if (!(0, import_types21.passesExtractedFloor)(confidence)) {
|
|
5867
6811
|
noteExtractedDropped({
|
|
5868
6812
|
source: fileNodeId,
|
|
5869
6813
|
target: ep.infraId,
|
|
@@ -5883,7 +6827,7 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
5883
6827
|
source: fileNodeId,
|
|
5884
6828
|
target: ep.infraId,
|
|
5885
6829
|
type: edgeType,
|
|
5886
|
-
provenance:
|
|
6830
|
+
provenance: import_types21.Provenance.EXTRACTED,
|
|
5887
6831
|
confidence,
|
|
5888
6832
|
evidence: ep.evidence
|
|
5889
6833
|
};
|
|
@@ -5897,9 +6841,10 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
5897
6841
|
async function addCallEdges(graph, services) {
|
|
5898
6842
|
const http = await addHttpCallEdges(graph, services);
|
|
5899
6843
|
const ext = await addExternalEndpointEdges(graph, services);
|
|
6844
|
+
const routes = await addRouteCallEdges(graph, services);
|
|
5900
6845
|
return {
|
|
5901
|
-
nodesAdded: http.nodesAdded + ext.nodesAdded,
|
|
5902
|
-
edgesAdded: http.edgesAdded + ext.edgesAdded
|
|
6846
|
+
nodesAdded: http.nodesAdded + ext.nodesAdded + routes.nodesAdded,
|
|
6847
|
+
edgesAdded: http.edgesAdded + ext.edgesAdded + routes.edgesAdded
|
|
5903
6848
|
};
|
|
5904
6849
|
}
|
|
5905
6850
|
|
|
@@ -5908,16 +6853,16 @@ init_cjs_shims();
|
|
|
5908
6853
|
|
|
5909
6854
|
// src/extract/infra/docker-compose.ts
|
|
5910
6855
|
init_cjs_shims();
|
|
5911
|
-
var
|
|
5912
|
-
var
|
|
6856
|
+
var import_node_path33 = __toESM(require("path"), 1);
|
|
6857
|
+
var import_types23 = require("@neat.is/types");
|
|
5913
6858
|
|
|
5914
6859
|
// src/extract/infra/shared.ts
|
|
5915
6860
|
init_cjs_shims();
|
|
5916
|
-
var
|
|
6861
|
+
var import_types22 = require("@neat.is/types");
|
|
5917
6862
|
function makeInfraNode(kind, name, provider = "self", extras) {
|
|
5918
6863
|
return {
|
|
5919
|
-
id: (0,
|
|
5920
|
-
type:
|
|
6864
|
+
id: (0, import_types22.infraId)(kind, name),
|
|
6865
|
+
type: import_types22.NodeType.InfraNode,
|
|
5921
6866
|
name,
|
|
5922
6867
|
provider,
|
|
5923
6868
|
kind,
|
|
@@ -5946,7 +6891,7 @@ function dependsOnList(value) {
|
|
|
5946
6891
|
}
|
|
5947
6892
|
function serviceNameToServiceNode(name, services) {
|
|
5948
6893
|
for (const s of services) {
|
|
5949
|
-
if (s.node.name === name ||
|
|
6894
|
+
if (s.node.name === name || import_node_path33.default.basename(s.dir) === name) return s.node.id;
|
|
5950
6895
|
}
|
|
5951
6896
|
return null;
|
|
5952
6897
|
}
|
|
@@ -5955,7 +6900,7 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
5955
6900
|
let edgesAdded = 0;
|
|
5956
6901
|
let composePath = null;
|
|
5957
6902
|
for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
|
|
5958
|
-
const abs =
|
|
6903
|
+
const abs = import_node_path33.default.join(scanPath, name);
|
|
5959
6904
|
if (await exists2(abs)) {
|
|
5960
6905
|
composePath = abs;
|
|
5961
6906
|
break;
|
|
@@ -5968,13 +6913,13 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
5968
6913
|
} catch (err) {
|
|
5969
6914
|
recordExtractionError(
|
|
5970
6915
|
"infra docker-compose",
|
|
5971
|
-
|
|
6916
|
+
import_node_path33.default.relative(scanPath, composePath),
|
|
5972
6917
|
err
|
|
5973
6918
|
);
|
|
5974
6919
|
return { nodesAdded, edgesAdded };
|
|
5975
6920
|
}
|
|
5976
6921
|
if (!compose?.services) return { nodesAdded, edgesAdded };
|
|
5977
|
-
const evidenceFile =
|
|
6922
|
+
const evidenceFile = import_node_path33.default.relative(scanPath, composePath).split(import_node_path33.default.sep).join("/");
|
|
5978
6923
|
const composeNameToNodeId = /* @__PURE__ */ new Map();
|
|
5979
6924
|
for (const [composeName, svc] of Object.entries(compose.services)) {
|
|
5980
6925
|
const matchedServiceId = serviceNameToServiceNode(composeName, services);
|
|
@@ -5996,15 +6941,15 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
5996
6941
|
for (const dep of dependsOnList(svc.depends_on)) {
|
|
5997
6942
|
const targetId = composeNameToNodeId.get(dep);
|
|
5998
6943
|
if (!targetId) continue;
|
|
5999
|
-
const edgeId = (0, import_types5.extractedEdgeId)(sourceId, targetId,
|
|
6944
|
+
const edgeId = (0, import_types5.extractedEdgeId)(sourceId, targetId, import_types23.EdgeType.DEPENDS_ON);
|
|
6000
6945
|
if (graph.hasEdge(edgeId)) continue;
|
|
6001
6946
|
const edge = {
|
|
6002
6947
|
id: edgeId,
|
|
6003
6948
|
source: sourceId,
|
|
6004
6949
|
target: targetId,
|
|
6005
|
-
type:
|
|
6006
|
-
provenance:
|
|
6007
|
-
confidence: (0,
|
|
6950
|
+
type: import_types23.EdgeType.DEPENDS_ON,
|
|
6951
|
+
provenance: import_types23.Provenance.EXTRACTED,
|
|
6952
|
+
confidence: (0, import_types23.confidenceForExtracted)("structural"),
|
|
6008
6953
|
evidence: { file: evidenceFile }
|
|
6009
6954
|
};
|
|
6010
6955
|
graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
|
|
@@ -6016,9 +6961,9 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
6016
6961
|
|
|
6017
6962
|
// src/extract/infra/dockerfile.ts
|
|
6018
6963
|
init_cjs_shims();
|
|
6019
|
-
var
|
|
6020
|
-
var
|
|
6021
|
-
var
|
|
6964
|
+
var import_node_path34 = __toESM(require("path"), 1);
|
|
6965
|
+
var import_node_fs18 = require("fs");
|
|
6966
|
+
var import_types24 = require("@neat.is/types");
|
|
6022
6967
|
function readDockerfile(content) {
|
|
6023
6968
|
let image = null;
|
|
6024
6969
|
const ports = [];
|
|
@@ -6047,15 +6992,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
6047
6992
|
let nodesAdded = 0;
|
|
6048
6993
|
let edgesAdded = 0;
|
|
6049
6994
|
for (const service of services) {
|
|
6050
|
-
const dockerfilePath =
|
|
6995
|
+
const dockerfilePath = import_node_path34.default.join(service.dir, "Dockerfile");
|
|
6051
6996
|
if (!await exists2(dockerfilePath)) continue;
|
|
6052
6997
|
let content;
|
|
6053
6998
|
try {
|
|
6054
|
-
content = await
|
|
6999
|
+
content = await import_node_fs18.promises.readFile(dockerfilePath, "utf8");
|
|
6055
7000
|
} catch (err) {
|
|
6056
7001
|
recordExtractionError(
|
|
6057
7002
|
"infra dockerfile",
|
|
6058
|
-
|
|
7003
|
+
import_node_path34.default.relative(scanPath, dockerfilePath),
|
|
6059
7004
|
err
|
|
6060
7005
|
);
|
|
6061
7006
|
continue;
|
|
@@ -6067,8 +7012,8 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
6067
7012
|
graph.addNode(node.id, node);
|
|
6068
7013
|
nodesAdded++;
|
|
6069
7014
|
}
|
|
6070
|
-
const relDockerfile = toPosix2(
|
|
6071
|
-
const evidenceFile = toPosix2(
|
|
7015
|
+
const relDockerfile = toPosix2(import_node_path34.default.relative(service.dir, dockerfilePath));
|
|
7016
|
+
const evidenceFile = toPosix2(import_node_path34.default.relative(scanPath, dockerfilePath));
|
|
6072
7017
|
const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
|
|
6073
7018
|
graph,
|
|
6074
7019
|
service.pkg.name,
|
|
@@ -6077,15 +7022,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
6077
7022
|
);
|
|
6078
7023
|
nodesAdded += fn;
|
|
6079
7024
|
edgesAdded += fe;
|
|
6080
|
-
const edgeId = (0, import_types5.extractedEdgeId)(fileNodeId, node.id,
|
|
7025
|
+
const edgeId = (0, import_types5.extractedEdgeId)(fileNodeId, node.id, import_types24.EdgeType.RUNS_ON);
|
|
6081
7026
|
if (!graph.hasEdge(edgeId)) {
|
|
6082
7027
|
const edge = {
|
|
6083
7028
|
id: edgeId,
|
|
6084
7029
|
source: fileNodeId,
|
|
6085
7030
|
target: node.id,
|
|
6086
|
-
type:
|
|
6087
|
-
provenance:
|
|
6088
|
-
confidence: (0,
|
|
7031
|
+
type: import_types24.EdgeType.RUNS_ON,
|
|
7032
|
+
provenance: import_types24.Provenance.EXTRACTED,
|
|
7033
|
+
confidence: (0, import_types24.confidenceForExtracted)("structural"),
|
|
6089
7034
|
evidence: {
|
|
6090
7035
|
file: evidenceFile,
|
|
6091
7036
|
...facts.entrypoint ? { snippet: facts.entrypoint.slice(0, 120) } : {}
|
|
@@ -6100,15 +7045,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
6100
7045
|
graph.addNode(portNode.id, portNode);
|
|
6101
7046
|
nodesAdded++;
|
|
6102
7047
|
}
|
|
6103
|
-
const portEdgeId = (0, import_types5.extractedEdgeId)(fileNodeId, portNode.id,
|
|
7048
|
+
const portEdgeId = (0, import_types5.extractedEdgeId)(fileNodeId, portNode.id, import_types24.EdgeType.CONNECTS_TO);
|
|
6104
7049
|
if (graph.hasEdge(portEdgeId)) continue;
|
|
6105
7050
|
const portEdge = {
|
|
6106
7051
|
id: portEdgeId,
|
|
6107
7052
|
source: fileNodeId,
|
|
6108
7053
|
target: portNode.id,
|
|
6109
|
-
type:
|
|
6110
|
-
provenance:
|
|
6111
|
-
confidence: (0,
|
|
7054
|
+
type: import_types24.EdgeType.CONNECTS_TO,
|
|
7055
|
+
provenance: import_types24.Provenance.EXTRACTED,
|
|
7056
|
+
confidence: (0, import_types24.confidenceForExtracted)("structural"),
|
|
6112
7057
|
evidence: { file: evidenceFile, snippet: `EXPOSE ${port}` }
|
|
6113
7058
|
};
|
|
6114
7059
|
graph.addEdgeWithKey(portEdgeId, portEdge.source, portEdge.target, portEdge);
|
|
@@ -6120,23 +7065,23 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
6120
7065
|
|
|
6121
7066
|
// src/extract/infra/terraform.ts
|
|
6122
7067
|
init_cjs_shims();
|
|
6123
|
-
var
|
|
6124
|
-
var
|
|
6125
|
-
var
|
|
7068
|
+
var import_node_fs19 = require("fs");
|
|
7069
|
+
var import_node_path35 = __toESM(require("path"), 1);
|
|
7070
|
+
var import_types25 = require("@neat.is/types");
|
|
6126
7071
|
var RESOURCE_RE = /resource\s+"(aws_[A-Za-z0-9_]+)"\s+"([A-Za-z0-9_-]+)"/g;
|
|
6127
7072
|
var REFERENCE_RE = /(?<![\w.])(aws_[A-Za-z0-9_]+)\.([A-Za-z0-9_-]+)/g;
|
|
6128
7073
|
async function walkTfFiles(start, depth = 0, max = 5) {
|
|
6129
7074
|
if (depth > max) return [];
|
|
6130
7075
|
const out = [];
|
|
6131
|
-
const entries = await
|
|
7076
|
+
const entries = await import_node_fs19.promises.readdir(start, { withFileTypes: true }).catch(() => []);
|
|
6132
7077
|
for (const entry of entries) {
|
|
6133
7078
|
if (entry.isDirectory()) {
|
|
6134
7079
|
if (IGNORED_DIRS.has(entry.name) || entry.name === ".terraform") continue;
|
|
6135
|
-
const child =
|
|
7080
|
+
const child = import_node_path35.default.join(start, entry.name);
|
|
6136
7081
|
if (await isPythonVenvDir(child)) continue;
|
|
6137
7082
|
out.push(...await walkTfFiles(child, depth + 1, max));
|
|
6138
7083
|
} else if (entry.isFile() && entry.name.endsWith(".tf")) {
|
|
6139
|
-
out.push(
|
|
7084
|
+
out.push(import_node_path35.default.join(start, entry.name));
|
|
6140
7085
|
}
|
|
6141
7086
|
}
|
|
6142
7087
|
return out;
|
|
@@ -6155,7 +7100,7 @@ function blockBody(content, from) {
|
|
|
6155
7100
|
}
|
|
6156
7101
|
return null;
|
|
6157
7102
|
}
|
|
6158
|
-
function
|
|
7103
|
+
function lineAt2(content, index) {
|
|
6159
7104
|
let line = 1;
|
|
6160
7105
|
for (let i = 0; i < index && i < content.length; i++) {
|
|
6161
7106
|
if (content[i] === "\n") line++;
|
|
@@ -6167,8 +7112,8 @@ async function addTerraformResources(graph, scanPath) {
|
|
|
6167
7112
|
let edgesAdded = 0;
|
|
6168
7113
|
const files = await walkTfFiles(scanPath);
|
|
6169
7114
|
for (const file of files) {
|
|
6170
|
-
const content = await
|
|
6171
|
-
const evidenceFile = toPosix2(
|
|
7115
|
+
const content = await import_node_fs19.promises.readFile(file, "utf8");
|
|
7116
|
+
const evidenceFile = toPosix2(import_node_path35.default.relative(scanPath, file));
|
|
6172
7117
|
const resources = [];
|
|
6173
7118
|
const byKey = /* @__PURE__ */ new Map();
|
|
6174
7119
|
RESOURCE_RE.lastIndex = 0;
|
|
@@ -6203,16 +7148,16 @@ async function addTerraformResources(graph, scanPath) {
|
|
|
6203
7148
|
if (!target) continue;
|
|
6204
7149
|
if (seen.has(target.nodeId)) continue;
|
|
6205
7150
|
seen.add(target.nodeId);
|
|
6206
|
-
const edgeId = (0, import_types5.extractedEdgeId)(resource.nodeId, target.nodeId,
|
|
7151
|
+
const edgeId = (0, import_types5.extractedEdgeId)(resource.nodeId, target.nodeId, import_types25.EdgeType.DEPENDS_ON);
|
|
6207
7152
|
if (graph.hasEdge(edgeId)) continue;
|
|
6208
|
-
const line =
|
|
7153
|
+
const line = lineAt2(content, resource.bodyOffset + ref.index);
|
|
6209
7154
|
const edge = {
|
|
6210
7155
|
id: edgeId,
|
|
6211
7156
|
source: resource.nodeId,
|
|
6212
7157
|
target: target.nodeId,
|
|
6213
|
-
type:
|
|
6214
|
-
provenance:
|
|
6215
|
-
confidence: (0,
|
|
7158
|
+
type: import_types25.EdgeType.DEPENDS_ON,
|
|
7159
|
+
provenance: import_types25.Provenance.EXTRACTED,
|
|
7160
|
+
confidence: (0, import_types25.confidenceForExtracted)("structural"),
|
|
6216
7161
|
evidence: { file: evidenceFile, line, snippet: key }
|
|
6217
7162
|
};
|
|
6218
7163
|
graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
|
|
@@ -6225,8 +7170,8 @@ async function addTerraformResources(graph, scanPath) {
|
|
|
6225
7170
|
|
|
6226
7171
|
// src/extract/infra/k8s.ts
|
|
6227
7172
|
init_cjs_shims();
|
|
6228
|
-
var
|
|
6229
|
-
var
|
|
7173
|
+
var import_node_fs20 = require("fs");
|
|
7174
|
+
var import_node_path36 = __toESM(require("path"), 1);
|
|
6230
7175
|
var import_yaml3 = require("yaml");
|
|
6231
7176
|
var K8S_KIND_TO_INFRA_KIND = {
|
|
6232
7177
|
Service: "k8s-service",
|
|
@@ -6240,15 +7185,15 @@ var K8S_KIND_TO_INFRA_KIND = {
|
|
|
6240
7185
|
async function walkYamlFiles2(start, depth = 0, max = 5) {
|
|
6241
7186
|
if (depth > max) return [];
|
|
6242
7187
|
const out = [];
|
|
6243
|
-
const entries = await
|
|
7188
|
+
const entries = await import_node_fs20.promises.readdir(start, { withFileTypes: true }).catch(() => []);
|
|
6244
7189
|
for (const entry of entries) {
|
|
6245
7190
|
if (entry.isDirectory()) {
|
|
6246
7191
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
6247
|
-
const child =
|
|
7192
|
+
const child = import_node_path36.default.join(start, entry.name);
|
|
6248
7193
|
if (await isPythonVenvDir(child)) continue;
|
|
6249
7194
|
out.push(...await walkYamlFiles2(child, depth + 1, max));
|
|
6250
|
-
} else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(
|
|
6251
|
-
out.push(
|
|
7195
|
+
} else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path36.default.extname(entry.name))) {
|
|
7196
|
+
out.push(import_node_path36.default.join(start, entry.name));
|
|
6252
7197
|
}
|
|
6253
7198
|
}
|
|
6254
7199
|
return out;
|
|
@@ -6257,7 +7202,7 @@ async function addK8sResources(graph, scanPath) {
|
|
|
6257
7202
|
let nodesAdded = 0;
|
|
6258
7203
|
const files = await walkYamlFiles2(scanPath);
|
|
6259
7204
|
for (const file of files) {
|
|
6260
|
-
const content = await
|
|
7205
|
+
const content = await import_node_fs20.promises.readFile(file, "utf8");
|
|
6261
7206
|
let docs;
|
|
6262
7207
|
try {
|
|
6263
7208
|
docs = (0, import_yaml3.parseAllDocuments)(content).map((d) => d.toJSON());
|
|
@@ -6292,17 +7237,17 @@ async function addInfra(graph, scanPath, services) {
|
|
|
6292
7237
|
}
|
|
6293
7238
|
|
|
6294
7239
|
// src/extract/index.ts
|
|
6295
|
-
var
|
|
7240
|
+
var import_node_path38 = __toESM(require("path"), 1);
|
|
6296
7241
|
|
|
6297
7242
|
// src/extract/retire.ts
|
|
6298
7243
|
init_cjs_shims();
|
|
6299
|
-
var
|
|
6300
|
-
var
|
|
6301
|
-
var
|
|
7244
|
+
var import_node_fs21 = require("fs");
|
|
7245
|
+
var import_node_path37 = __toESM(require("path"), 1);
|
|
7246
|
+
var import_types26 = require("@neat.is/types");
|
|
6302
7247
|
function dropOrphanedFileNodes(graph) {
|
|
6303
7248
|
const orphans = [];
|
|
6304
7249
|
graph.forEachNode((id, attrs) => {
|
|
6305
|
-
if (attrs.type !==
|
|
7250
|
+
if (attrs.type !== import_types26.NodeType.FileNode) return;
|
|
6306
7251
|
if (graph.inboundEdges(id).length === 0 && graph.outboundEdges(id).length === 0) {
|
|
6307
7252
|
orphans.push(id);
|
|
6308
7253
|
}
|
|
@@ -6315,14 +7260,14 @@ function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = []) {
|
|
|
6315
7260
|
const bases = [scanPath, ...serviceDirs];
|
|
6316
7261
|
graph.forEachEdge((id, attrs) => {
|
|
6317
7262
|
const edge = attrs;
|
|
6318
|
-
if (edge.provenance !==
|
|
7263
|
+
if (edge.provenance !== import_types26.Provenance.EXTRACTED) return;
|
|
6319
7264
|
const evidenceFile = edge.evidence?.file;
|
|
6320
7265
|
if (!evidenceFile) return;
|
|
6321
|
-
if (
|
|
6322
|
-
if (!(0,
|
|
7266
|
+
if (import_node_path37.default.isAbsolute(evidenceFile)) {
|
|
7267
|
+
if (!(0, import_node_fs21.existsSync)(evidenceFile)) toDrop.push(id);
|
|
6323
7268
|
return;
|
|
6324
7269
|
}
|
|
6325
|
-
const found = bases.some((base) => (0,
|
|
7270
|
+
const found = bases.some((base) => (0, import_node_fs21.existsSync)(import_node_path37.default.join(base, evidenceFile)));
|
|
6326
7271
|
if (!found) toDrop.push(id);
|
|
6327
7272
|
});
|
|
6328
7273
|
for (const id of toDrop) graph.dropEdge(id);
|
|
@@ -6341,6 +7286,8 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
6341
7286
|
const importGraph = await addImports(graph, services);
|
|
6342
7287
|
const phase2 = await addDatabasesAndCompat(graph, services, scanPath);
|
|
6343
7288
|
const phase3 = await addConfigNodes(graph, services, scanPath);
|
|
7289
|
+
const routePhase = await addRoutes(graph, services);
|
|
7290
|
+
const grpcPhase = await addGrpcMethods(graph, services);
|
|
6344
7291
|
const phase4 = await addCallEdges(graph, services);
|
|
6345
7292
|
const phase5 = await addInfra(graph, scanPath, services);
|
|
6346
7293
|
const ghostsRetired = retireExtractedEdgesByMissingFile(
|
|
@@ -6362,7 +7309,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
6362
7309
|
}
|
|
6363
7310
|
const droppedEntries = drainDroppedExtracted();
|
|
6364
7311
|
if (isRejectedLogEnabled() && opts.errorsPath && droppedEntries.length > 0) {
|
|
6365
|
-
const rejectedPath =
|
|
7312
|
+
const rejectedPath = import_node_path38.default.join(import_node_path38.default.dirname(opts.errorsPath), "rejected.ndjson");
|
|
6366
7313
|
try {
|
|
6367
7314
|
await writeRejectedExtracted(droppedEntries, rejectedPath);
|
|
6368
7315
|
} catch (err) {
|
|
@@ -6372,8 +7319,8 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
6372
7319
|
}
|
|
6373
7320
|
}
|
|
6374
7321
|
const result = {
|
|
6375
|
-
nodesAdded: phase1Nodes + fileEnum.nodesAdded + importGraph.nodesAdded + phase2.nodesAdded + phase3.nodesAdded + phase4.nodesAdded + phase5.nodesAdded,
|
|
6376
|
-
edgesAdded: fileEnum.edgesAdded + importGraph.edgesAdded + phase2.edgesAdded + phase3.edgesAdded + phase4.edgesAdded + phase5.edgesAdded,
|
|
7322
|
+
nodesAdded: phase1Nodes + fileEnum.nodesAdded + importGraph.nodesAdded + phase2.nodesAdded + phase3.nodesAdded + routePhase.nodesAdded + grpcPhase.nodesAdded + phase4.nodesAdded + phase5.nodesAdded,
|
|
7323
|
+
edgesAdded: fileEnum.edgesAdded + importGraph.edgesAdded + phase2.edgesAdded + phase3.edgesAdded + routePhase.edgesAdded + grpcPhase.edgesAdded + phase4.edgesAdded + phase5.edgesAdded,
|
|
6377
7324
|
frontiersPromoted,
|
|
6378
7325
|
extractionErrors: errorEntries.length,
|
|
6379
7326
|
errorEntries,
|
|
@@ -6396,7 +7343,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
6396
7343
|
|
|
6397
7344
|
// src/diff.ts
|
|
6398
7345
|
init_cjs_shims();
|
|
6399
|
-
var
|
|
7346
|
+
var import_node_fs22 = require("fs");
|
|
6400
7347
|
async function loadSnapshotForDiff(target) {
|
|
6401
7348
|
if (/^https?:\/\//i.test(target)) {
|
|
6402
7349
|
const res = await fetch(target);
|
|
@@ -6405,7 +7352,7 @@ async function loadSnapshotForDiff(target) {
|
|
|
6405
7352
|
}
|
|
6406
7353
|
return await res.json();
|
|
6407
7354
|
}
|
|
6408
|
-
const raw = await
|
|
7355
|
+
const raw = await import_node_fs22.promises.readFile(target, "utf8");
|
|
6409
7356
|
return JSON.parse(raw);
|
|
6410
7357
|
}
|
|
6411
7358
|
function indexEntries(entries) {
|
|
@@ -6473,9 +7420,9 @@ function canonicalJson(value) {
|
|
|
6473
7420
|
|
|
6474
7421
|
// src/persist.ts
|
|
6475
7422
|
init_cjs_shims();
|
|
6476
|
-
var
|
|
6477
|
-
var
|
|
6478
|
-
var
|
|
7423
|
+
var import_node_fs23 = require("fs");
|
|
7424
|
+
var import_node_path39 = __toESM(require("path"), 1);
|
|
7425
|
+
var import_types27 = require("@neat.is/types");
|
|
6479
7426
|
var SCHEMA_VERSION = 4;
|
|
6480
7427
|
function migrateV1ToV2(payload) {
|
|
6481
7428
|
const nodes = payload.graph.nodes;
|
|
@@ -6497,12 +7444,12 @@ function migrateV2ToV3(payload) {
|
|
|
6497
7444
|
for (const edge of edges) {
|
|
6498
7445
|
const attrs = edge.attributes;
|
|
6499
7446
|
if (!attrs || attrs.provenance !== "FRONTIER") continue;
|
|
6500
|
-
attrs.provenance =
|
|
7447
|
+
attrs.provenance = import_types27.Provenance.OBSERVED;
|
|
6501
7448
|
const type = typeof attrs.type === "string" ? attrs.type : void 0;
|
|
6502
7449
|
const source = typeof attrs.source === "string" ? attrs.source : void 0;
|
|
6503
7450
|
const target = typeof attrs.target === "string" ? attrs.target : void 0;
|
|
6504
7451
|
if (type && source && target) {
|
|
6505
|
-
const newId = (0,
|
|
7452
|
+
const newId = (0, import_types27.observedEdgeId)(source, target, type);
|
|
6506
7453
|
attrs.id = newId;
|
|
6507
7454
|
if (edge.key) edge.key = newId;
|
|
6508
7455
|
}
|
|
@@ -6511,7 +7458,7 @@ function migrateV2ToV3(payload) {
|
|
|
6511
7458
|
return { ...payload, schemaVersion: 3 };
|
|
6512
7459
|
}
|
|
6513
7460
|
async function ensureDir(filePath) {
|
|
6514
|
-
await
|
|
7461
|
+
await import_node_fs23.promises.mkdir(import_node_path39.default.dirname(filePath), { recursive: true });
|
|
6515
7462
|
}
|
|
6516
7463
|
async function saveGraphToDisk(graph, outPath) {
|
|
6517
7464
|
await ensureDir(outPath);
|
|
@@ -6521,13 +7468,13 @@ async function saveGraphToDisk(graph, outPath) {
|
|
|
6521
7468
|
graph: graph.export()
|
|
6522
7469
|
};
|
|
6523
7470
|
const tmp = `${outPath}.tmp`;
|
|
6524
|
-
await
|
|
6525
|
-
await
|
|
7471
|
+
await import_node_fs23.promises.writeFile(tmp, JSON.stringify(payload), "utf8");
|
|
7472
|
+
await import_node_fs23.promises.rename(tmp, outPath);
|
|
6526
7473
|
}
|
|
6527
7474
|
async function loadGraphFromDisk(graph, outPath) {
|
|
6528
7475
|
let raw;
|
|
6529
7476
|
try {
|
|
6530
|
-
raw = await
|
|
7477
|
+
raw = await import_node_fs23.promises.readFile(outPath, "utf8");
|
|
6531
7478
|
} catch (err) {
|
|
6532
7479
|
if (err.code === "ENOENT") return;
|
|
6533
7480
|
throw err;
|
|
@@ -6592,23 +7539,23 @@ function startPersistLoop(graph, outPath, opts = {}) {
|
|
|
6592
7539
|
|
|
6593
7540
|
// src/projects.ts
|
|
6594
7541
|
init_cjs_shims();
|
|
6595
|
-
var
|
|
7542
|
+
var import_node_path40 = __toESM(require("path"), 1);
|
|
6596
7543
|
function pathsForProject(project, baseDir) {
|
|
6597
7544
|
if (project === DEFAULT_PROJECT) {
|
|
6598
7545
|
return {
|
|
6599
|
-
snapshotPath:
|
|
6600
|
-
errorsPath:
|
|
6601
|
-
staleEventsPath:
|
|
6602
|
-
embeddingsCachePath:
|
|
6603
|
-
policyViolationsPath:
|
|
7546
|
+
snapshotPath: import_node_path40.default.join(baseDir, "graph.json"),
|
|
7547
|
+
errorsPath: import_node_path40.default.join(baseDir, "errors.ndjson"),
|
|
7548
|
+
staleEventsPath: import_node_path40.default.join(baseDir, "stale-events.ndjson"),
|
|
7549
|
+
embeddingsCachePath: import_node_path40.default.join(baseDir, "embeddings.json"),
|
|
7550
|
+
policyViolationsPath: import_node_path40.default.join(baseDir, "policy-violations.ndjson")
|
|
6604
7551
|
};
|
|
6605
7552
|
}
|
|
6606
7553
|
return {
|
|
6607
|
-
snapshotPath:
|
|
6608
|
-
errorsPath:
|
|
6609
|
-
staleEventsPath:
|
|
6610
|
-
embeddingsCachePath:
|
|
6611
|
-
policyViolationsPath:
|
|
7554
|
+
snapshotPath: import_node_path40.default.join(baseDir, `${project}.json`),
|
|
7555
|
+
errorsPath: import_node_path40.default.join(baseDir, `errors.${project}.ndjson`),
|
|
7556
|
+
staleEventsPath: import_node_path40.default.join(baseDir, `stale-events.${project}.ndjson`),
|
|
7557
|
+
embeddingsCachePath: import_node_path40.default.join(baseDir, `embeddings.${project}.json`),
|
|
7558
|
+
policyViolationsPath: import_node_path40.default.join(baseDir, `policy-violations.${project}.ndjson`)
|
|
6612
7559
|
};
|
|
6613
7560
|
}
|
|
6614
7561
|
var Projects = class {
|
|
@@ -6648,23 +7595,23 @@ function parseExtraProjects(raw) {
|
|
|
6648
7595
|
|
|
6649
7596
|
// src/registry.ts
|
|
6650
7597
|
init_cjs_shims();
|
|
6651
|
-
var
|
|
7598
|
+
var import_node_fs24 = require("fs");
|
|
6652
7599
|
var import_node_os3 = __toESM(require("os"), 1);
|
|
6653
|
-
var
|
|
6654
|
-
var
|
|
7600
|
+
var import_node_path41 = __toESM(require("path"), 1);
|
|
7601
|
+
var import_types28 = require("@neat.is/types");
|
|
6655
7602
|
function neatHome() {
|
|
6656
7603
|
const override = process.env.NEAT_HOME;
|
|
6657
|
-
if (override && override.length > 0) return
|
|
6658
|
-
return
|
|
7604
|
+
if (override && override.length > 0) return import_node_path41.default.resolve(override);
|
|
7605
|
+
return import_node_path41.default.join(import_node_os3.default.homedir(), ".neat");
|
|
6659
7606
|
}
|
|
6660
7607
|
function registryPath() {
|
|
6661
|
-
return
|
|
7608
|
+
return import_node_path41.default.join(neatHome(), "projects.json");
|
|
6662
7609
|
}
|
|
6663
7610
|
async function readRegistry() {
|
|
6664
7611
|
const file = registryPath();
|
|
6665
7612
|
let raw;
|
|
6666
7613
|
try {
|
|
6667
|
-
raw = await
|
|
7614
|
+
raw = await import_node_fs24.promises.readFile(file, "utf8");
|
|
6668
7615
|
} catch (err) {
|
|
6669
7616
|
if (err.code === "ENOENT") {
|
|
6670
7617
|
return { version: 1, projects: [] };
|
|
@@ -6672,7 +7619,7 @@ async function readRegistry() {
|
|
|
6672
7619
|
throw err;
|
|
6673
7620
|
}
|
|
6674
7621
|
const parsed = JSON.parse(raw);
|
|
6675
|
-
return
|
|
7622
|
+
return import_types28.RegistryFileSchema.parse(parsed);
|
|
6676
7623
|
}
|
|
6677
7624
|
async function getProject(name) {
|
|
6678
7625
|
const reg = await readRegistry();
|
|
@@ -6893,11 +7840,11 @@ function registerRoutes(scope, ctx) {
|
|
|
6893
7840
|
const candidates = req.query.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
6894
7841
|
const parsed = [];
|
|
6895
7842
|
for (const c of candidates) {
|
|
6896
|
-
const r =
|
|
7843
|
+
const r = import_types29.DivergenceTypeSchema.safeParse(c);
|
|
6897
7844
|
if (!r.success) {
|
|
6898
7845
|
return reply.code(400).send({
|
|
6899
7846
|
error: `unknown divergence type "${c}"`,
|
|
6900
|
-
allowed:
|
|
7847
|
+
allowed: import_types29.DivergenceTypeSchema.options
|
|
6901
7848
|
});
|
|
6902
7849
|
}
|
|
6903
7850
|
parsed.push(r.data);
|
|
@@ -7116,7 +8063,7 @@ function registerRoutes(scope, ctx) {
|
|
|
7116
8063
|
const log = new PolicyViolationsLog(proj.paths.policyViolationsPath);
|
|
7117
8064
|
let violations = await log.readAll();
|
|
7118
8065
|
if (req.query.severity) {
|
|
7119
|
-
const sev =
|
|
8066
|
+
const sev = import_types29.PolicySeveritySchema.safeParse(req.query.severity);
|
|
7120
8067
|
if (!sev.success) {
|
|
7121
8068
|
return reply.code(400).send({
|
|
7122
8069
|
error: "invalid severity",
|
|
@@ -7155,7 +8102,7 @@ function registerRoutes(scope, ctx) {
|
|
|
7155
8102
|
scope.post("/policies/check", async (req, reply) => {
|
|
7156
8103
|
const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
|
|
7157
8104
|
if (!proj) return;
|
|
7158
|
-
const parsed =
|
|
8105
|
+
const parsed = import_types29.PoliciesCheckBodySchema.safeParse(req.body ?? {});
|
|
7159
8106
|
if (!parsed.success) {
|
|
7160
8107
|
return reply.code(400).send({
|
|
7161
8108
|
error: "invalid /policies/check body",
|
|
@@ -7417,8 +8364,8 @@ init_otel_grpc();
|
|
|
7417
8364
|
|
|
7418
8365
|
// src/search.ts
|
|
7419
8366
|
init_cjs_shims();
|
|
7420
|
-
var
|
|
7421
|
-
var
|
|
8367
|
+
var import_node_fs25 = require("fs");
|
|
8368
|
+
var import_node_path44 = __toESM(require("path"), 1);
|
|
7422
8369
|
var import_node_crypto3 = require("crypto");
|
|
7423
8370
|
var DEFAULT_LIMIT = 10;
|
|
7424
8371
|
var NOMIC_DIM = 768;
|
|
@@ -7453,6 +8400,30 @@ function embedText(node) {
|
|
|
7453
8400
|
if (filePath) parts.push(`path=${filePath}`);
|
|
7454
8401
|
break;
|
|
7455
8402
|
}
|
|
8403
|
+
case "RouteNode": {
|
|
8404
|
+
const method = node.method;
|
|
8405
|
+
const tmpl = node.pathTemplate;
|
|
8406
|
+
if (method) parts.push(`method=${method}`);
|
|
8407
|
+
if (tmpl) parts.push(`path=${tmpl}`);
|
|
8408
|
+
break;
|
|
8409
|
+
}
|
|
8410
|
+
case "GraphQLOperationNode": {
|
|
8411
|
+
const opType = node.operationType;
|
|
8412
|
+
if (opType) parts.push(`operationType=${opType}`);
|
|
8413
|
+
break;
|
|
8414
|
+
}
|
|
8415
|
+
case "GrpcMethodNode": {
|
|
8416
|
+
const rpcService = node.rpcService;
|
|
8417
|
+
const rpcMethod = node.rpcMethod;
|
|
8418
|
+
if (rpcService) parts.push(`rpcService=${rpcService}`);
|
|
8419
|
+
if (rpcMethod) parts.push(`rpcMethod=${rpcMethod}`);
|
|
8420
|
+
break;
|
|
8421
|
+
}
|
|
8422
|
+
case "WebSocketChannelNode": {
|
|
8423
|
+
const channel = node.channel;
|
|
8424
|
+
if (channel) parts.push(`channel=${channel}`);
|
|
8425
|
+
break;
|
|
8426
|
+
}
|
|
7456
8427
|
default:
|
|
7457
8428
|
break;
|
|
7458
8429
|
}
|
|
@@ -7548,7 +8519,7 @@ async function pickEmbedder() {
|
|
|
7548
8519
|
}
|
|
7549
8520
|
async function readCache(cachePath) {
|
|
7550
8521
|
try {
|
|
7551
|
-
const raw = await
|
|
8522
|
+
const raw = await import_node_fs25.promises.readFile(cachePath, "utf8");
|
|
7552
8523
|
const parsed = JSON.parse(raw);
|
|
7553
8524
|
if (parsed.version !== 1) return null;
|
|
7554
8525
|
return parsed;
|
|
@@ -7557,8 +8528,8 @@ async function readCache(cachePath) {
|
|
|
7557
8528
|
}
|
|
7558
8529
|
}
|
|
7559
8530
|
async function writeCache(cachePath, cache) {
|
|
7560
|
-
await
|
|
7561
|
-
await
|
|
8531
|
+
await import_node_fs25.promises.mkdir(import_node_path44.default.dirname(cachePath), { recursive: true });
|
|
8532
|
+
await import_node_fs25.promises.writeFile(cachePath, JSON.stringify(cache));
|
|
7562
8533
|
}
|
|
7563
8534
|
var VectorIndex = class {
|
|
7564
8535
|
constructor(embedder, cachePath) {
|
|
@@ -7738,14 +8709,14 @@ async function bootProject(registry, name, scanPath, baseDir) {
|
|
|
7738
8709
|
async function main() {
|
|
7739
8710
|
const baseDirEnv = process.env.NEAT_OUT_DIR;
|
|
7740
8711
|
const legacyOutPath = process.env.NEAT_OUT_PATH;
|
|
7741
|
-
const baseDir = baseDirEnv ?
|
|
7742
|
-
const defaultScanPath =
|
|
8712
|
+
const baseDir = baseDirEnv ? import_node_path45.default.resolve(baseDirEnv) : legacyOutPath ? import_node_path45.default.resolve(import_node_path45.default.dirname(legacyOutPath)) : import_node_path45.default.resolve("./neat-out");
|
|
8713
|
+
const defaultScanPath = import_node_path45.default.resolve(process.env.NEAT_SCAN_PATH ?? "./demo");
|
|
7743
8714
|
const registry = new Projects();
|
|
7744
8715
|
await bootProject(registry, DEFAULT_PROJECT, defaultScanPath, baseDir);
|
|
7745
8716
|
for (const name of parseExtraProjects(process.env.NEAT_PROJECTS)) {
|
|
7746
8717
|
const envKey = `NEAT_PROJECT_SCAN_PATH_${name.toUpperCase().replace(/[^A-Z0-9]/g, "_")}`;
|
|
7747
8718
|
const projectScan = process.env[envKey];
|
|
7748
|
-
await bootProject(registry, name, projectScan ?
|
|
8719
|
+
await bootProject(registry, name, projectScan ? import_node_path45.default.resolve(projectScan) : void 0, baseDir);
|
|
7749
8720
|
}
|
|
7750
8721
|
const host = process.env.HOST ?? "0.0.0.0";
|
|
7751
8722
|
const port = Number(process.env.PORT ?? 8080);
|