@neat.is/core 0.4.26-dev.20260702 → 0.4.26-dev.20260703
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-ZX7PCMGZ.js → chunk-2LNICOVU.js} +30 -3
- package/dist/chunk-2LNICOVU.js.map +1 -0
- package/dist/{chunk-XOOCA5T7.js → chunk-4OR4RQEO.js} +13 -1
- package/dist/chunk-4OR4RQEO.js.map +1 -0
- package/dist/{chunk-GAFTW2OX.js → chunk-C5NCCKPZ.js} +2 -2
- package/dist/{chunk-IABNGQT2.js → chunk-S7TDPQDD.js} +15 -3
- package/dist/chunk-S7TDPQDD.js.map +1 -0
- package/dist/{chunk-O25KZNZK.js → chunk-WZYH5DVG.js} +957 -238
- package/dist/chunk-WZYH5DVG.js.map +1 -0
- package/dist/cli.cjs +1149 -406
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +5 -5
- package/dist/index.cjs +1011 -269
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +4 -4
- package/dist/neatd.cjs +1112 -278
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +3 -3
- package/dist/{otel-grpc-QYHUJ4OY.js → otel-grpc-LFYZDSK3.js} +3 -3
- package/dist/server.cjs +954 -211
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +4 -4
- package/package.json +2 -2
- package/dist/chunk-IABNGQT2.js.map +0 -1
- package/dist/chunk-O25KZNZK.js.map +0 -1
- package/dist/chunk-XOOCA5T7.js.map +0 -1
- package/dist/chunk-ZX7PCMGZ.js.map +0 -1
- /package/dist/{chunk-GAFTW2OX.js.map → chunk-C5NCCKPZ.js.map} +0 -0
- /package/dist/{otel-grpc-QYHUJ4OY.js.map → otel-grpc-LFYZDSK3.js.map} +0 -0
package/dist/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 path45 = (req.url.split("?")[0] ?? "").replace(/\/+$/, "");
|
|
60
60
|
for (const suffix of suffixes) {
|
|
61
|
-
if (
|
|
61
|
+
if (path45 === suffix || path45.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_path41.default.dirname((0, import_node_url.fileURLToPath)(importMetaUrl));
|
|
192
|
+
return import_node_path41.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_path41, 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_path41 = __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,13 @@ 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
|
+
}
|
|
337
346
|
function parseOtlpRequest(body) {
|
|
338
347
|
const out = [];
|
|
339
348
|
for (const rs of body.resourceSpans ?? []) {
|
|
@@ -360,6 +369,10 @@ function parseOtlpRequest(body) {
|
|
|
360
369
|
attributes: attrs,
|
|
361
370
|
dbSystem: typeof attrs["db.system"] === "string" ? attrs["db.system"] : void 0,
|
|
362
371
|
dbName: typeof attrs["db.name"] === "string" ? attrs["db.name"] : void 0,
|
|
372
|
+
messagingSystem: typeof attrs["messaging.system"] === "string" ? attrs["messaging.system"] : void 0,
|
|
373
|
+
messagingDestination: messagingDestinationOf(attrs),
|
|
374
|
+
graphqlOperationName: typeof attrs["graphql.operation.name"] === "string" && attrs["graphql.operation.name"].length > 0 ? attrs["graphql.operation.name"] : void 0,
|
|
375
|
+
graphqlOperationType: typeof attrs["graphql.operation.type"] === "string" && attrs["graphql.operation.type"].length > 0 ? attrs["graphql.operation.type"] : void 0,
|
|
363
376
|
statusCode: span.status?.code,
|
|
364
377
|
errorMessage: span.status?.message,
|
|
365
378
|
exception: extractExceptionFromEvents(span.events)
|
|
@@ -371,10 +384,10 @@ function parseOtlpRequest(body) {
|
|
|
371
384
|
return out;
|
|
372
385
|
}
|
|
373
386
|
function loadProtoRoot() {
|
|
374
|
-
const here =
|
|
375
|
-
const protoRoot =
|
|
387
|
+
const here = import_node_path42.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
|
|
388
|
+
const protoRoot = import_node_path42.default.resolve(here, "..", "proto");
|
|
376
389
|
const root = new import_protobufjs.default.Root();
|
|
377
|
-
root.resolvePath = (_origin, target) =>
|
|
390
|
+
root.resolvePath = (_origin, target) => import_node_path42.default.resolve(protoRoot, target);
|
|
378
391
|
root.loadSync(
|
|
379
392
|
"opentelemetry/proto/collector/trace/v1/trace_service.proto",
|
|
380
393
|
{ keepCase: true }
|
|
@@ -419,7 +432,21 @@ async function buildOtelReceiver(opts) {
|
|
|
419
432
|
logger: false,
|
|
420
433
|
bodyLimit: opts.bodyLimit ?? 16 * 1024 * 1024
|
|
421
434
|
});
|
|
422
|
-
|
|
435
|
+
const REJECT_WARN_INTERVAL_MS = 6e4;
|
|
436
|
+
let lastRejectWarnAt = 0;
|
|
437
|
+
const warnRejectedOtlp = () => {
|
|
438
|
+
const now = Date.now();
|
|
439
|
+
if (now - lastRejectWarnAt < REJECT_WARN_INTERVAL_MS) return;
|
|
440
|
+
lastRejectWarnAt = now;
|
|
441
|
+
console.warn(
|
|
442
|
+
"[neatd] rejecting OTLP spans on /v1/traces \u2014 missing or invalid bearer token (set NEAT_OTEL_TOKEN on the instrumented app)"
|
|
443
|
+
);
|
|
444
|
+
};
|
|
445
|
+
mountBearerAuth(app, {
|
|
446
|
+
token: opts.authToken,
|
|
447
|
+
trustProxy: opts.trustProxy,
|
|
448
|
+
onReject: warnRejectedOtlp
|
|
449
|
+
});
|
|
423
450
|
const queue = [];
|
|
424
451
|
let draining = false;
|
|
425
452
|
let drainPromise = Promise.resolve();
|
|
@@ -572,12 +599,12 @@ async function buildOtelReceiver(opts) {
|
|
|
572
599
|
};
|
|
573
600
|
return decorated;
|
|
574
601
|
}
|
|
575
|
-
var
|
|
602
|
+
var import_node_path42, import_node_url2, import_fastify2, import_protobufjs, ENV_ATTR_CANONICAL, ENV_ATTR_COMPAT, ENV_FALLBACK, exportTraceServiceRequestType, exportTraceServiceResponseType, cachedProtobufResponseBody;
|
|
576
603
|
var init_otel = __esm({
|
|
577
604
|
"src/otel.ts"() {
|
|
578
605
|
"use strict";
|
|
579
606
|
init_cjs_shims();
|
|
580
|
-
|
|
607
|
+
import_node_path42 = __toESM(require("path"), 1);
|
|
581
608
|
import_node_url2 = require("url");
|
|
582
609
|
import_fastify2 = __toESM(require("fastify"), 1);
|
|
583
610
|
import_protobufjs = __toESM(require("protobufjs"), 1);
|
|
@@ -593,7 +620,7 @@ var init_otel = __esm({
|
|
|
593
620
|
|
|
594
621
|
// src/server.ts
|
|
595
622
|
init_cjs_shims();
|
|
596
|
-
var
|
|
623
|
+
var import_node_path44 = __toESM(require("path"), 1);
|
|
597
624
|
|
|
598
625
|
// src/graph.ts
|
|
599
626
|
init_cjs_shims();
|
|
@@ -617,7 +644,7 @@ function getGraph(project = DEFAULT_PROJECT) {
|
|
|
617
644
|
init_cjs_shims();
|
|
618
645
|
var import_fastify = __toESM(require("fastify"), 1);
|
|
619
646
|
var import_cors = __toESM(require("@fastify/cors"), 1);
|
|
620
|
-
var
|
|
647
|
+
var import_types28 = require("@neat.is/types");
|
|
621
648
|
|
|
622
649
|
// src/extend/index.ts
|
|
623
650
|
init_cjs_shims();
|
|
@@ -1360,19 +1387,19 @@ function confidenceFromMix(edges, now = Date.now()) {
|
|
|
1360
1387
|
function longestIncomingWalk(graph, start, maxDepth) {
|
|
1361
1388
|
let best = { path: [start], edges: [] };
|
|
1362
1389
|
const visited = /* @__PURE__ */ new Set([start]);
|
|
1363
|
-
function step(node,
|
|
1364
|
-
if (
|
|
1365
|
-
best = { path: [...
|
|
1390
|
+
function step(node, path45, edges) {
|
|
1391
|
+
if (path45.length > best.path.length) {
|
|
1392
|
+
best = { path: [...path45], edges: [...edges] };
|
|
1366
1393
|
}
|
|
1367
|
-
if (
|
|
1394
|
+
if (path45.length - 1 >= maxDepth) return;
|
|
1368
1395
|
const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
|
|
1369
1396
|
for (const [srcId, edge] of incoming) {
|
|
1370
1397
|
if (visited.has(srcId)) continue;
|
|
1371
1398
|
visited.add(srcId);
|
|
1372
|
-
|
|
1399
|
+
path45.push(srcId);
|
|
1373
1400
|
edges.push(edge);
|
|
1374
|
-
step(srcId,
|
|
1375
|
-
|
|
1401
|
+
step(srcId, path45, edges);
|
|
1402
|
+
path45.pop();
|
|
1376
1403
|
edges.pop();
|
|
1377
1404
|
visited.delete(srcId);
|
|
1378
1405
|
}
|
|
@@ -1380,14 +1407,14 @@ function longestIncomingWalk(graph, start, maxDepth) {
|
|
|
1380
1407
|
step(start, [start], []);
|
|
1381
1408
|
return best;
|
|
1382
1409
|
}
|
|
1383
|
-
function databaseRootCauseShape(graph, origin,
|
|
1410
|
+
function databaseRootCauseShape(graph, origin, walk3) {
|
|
1384
1411
|
const targetDb = origin;
|
|
1385
1412
|
const candidatePairs = compatPairs().filter((p) => p.engine === targetDb.engine);
|
|
1386
1413
|
if (candidatePairs.length === 0) return null;
|
|
1387
|
-
for (const id of
|
|
1414
|
+
for (const id of walk3.path) {
|
|
1388
1415
|
const owner = resolveOwningService(graph, id);
|
|
1389
1416
|
if (!owner) continue;
|
|
1390
|
-
const { id:
|
|
1417
|
+
const { id: serviceId4, svc } = owner;
|
|
1391
1418
|
const deps = svc.dependencies ?? {};
|
|
1392
1419
|
for (const pair of candidatePairs) {
|
|
1393
1420
|
const declared = deps[pair.driver];
|
|
@@ -1400,7 +1427,7 @@ function databaseRootCauseShape(graph, origin, walk) {
|
|
|
1400
1427
|
);
|
|
1401
1428
|
if (!result.compatible) {
|
|
1402
1429
|
return {
|
|
1403
|
-
rootCauseNode:
|
|
1430
|
+
rootCauseNode: serviceId4,
|
|
1404
1431
|
rootCauseReason: result.reason ?? "incompatible driver",
|
|
1405
1432
|
...result.minDriverVersion ? {
|
|
1406
1433
|
fixRecommendation: `Upgrade ${svc.name} ${pair.driver} driver to >= ${result.minDriverVersion}`
|
|
@@ -1411,11 +1438,11 @@ function databaseRootCauseShape(graph, origin, walk) {
|
|
|
1411
1438
|
}
|
|
1412
1439
|
return null;
|
|
1413
1440
|
}
|
|
1414
|
-
function serviceRootCauseShape(graph, _origin,
|
|
1415
|
-
for (const id of
|
|
1441
|
+
function serviceRootCauseShape(graph, _origin, walk3) {
|
|
1442
|
+
for (const id of walk3.path) {
|
|
1416
1443
|
const owner = resolveOwningService(graph, id);
|
|
1417
1444
|
if (!owner) continue;
|
|
1418
|
-
const { id:
|
|
1445
|
+
const { id: serviceId4, svc } = owner;
|
|
1419
1446
|
const deps = svc.dependencies ?? {};
|
|
1420
1447
|
const serviceNodeEngine = svc.nodeEngine;
|
|
1421
1448
|
for (const constraint of nodeEngineConstraints()) {
|
|
@@ -1424,7 +1451,7 @@ function serviceRootCauseShape(graph, _origin, walk) {
|
|
|
1424
1451
|
const result = checkNodeEngineConstraint(constraint, declared, serviceNodeEngine);
|
|
1425
1452
|
if (!result.compatible && result.reason) {
|
|
1426
1453
|
return {
|
|
1427
|
-
rootCauseNode:
|
|
1454
|
+
rootCauseNode: serviceId4,
|
|
1428
1455
|
rootCauseReason: result.reason,
|
|
1429
1456
|
...result.requiredNodeVersion ? {
|
|
1430
1457
|
fixRecommendation: `Bump ${svc.name}'s engines.node to >= ${result.requiredNodeVersion}`
|
|
@@ -1439,7 +1466,7 @@ function serviceRootCauseShape(graph, _origin, walk) {
|
|
|
1439
1466
|
const result = checkPackageConflict(conflict, declared, requiredDeclared);
|
|
1440
1467
|
if (!result.compatible && result.reason) {
|
|
1441
1468
|
return {
|
|
1442
|
-
rootCauseNode:
|
|
1469
|
+
rootCauseNode: serviceId4,
|
|
1443
1470
|
rootCauseReason: result.reason,
|
|
1444
1471
|
fixRecommendation: `Upgrade ${svc.name}'s ${conflict.requires.name} to >= ${conflict.requires.minVersion}`
|
|
1445
1472
|
};
|
|
@@ -1448,10 +1475,10 @@ function serviceRootCauseShape(graph, _origin, walk) {
|
|
|
1448
1475
|
}
|
|
1449
1476
|
return null;
|
|
1450
1477
|
}
|
|
1451
|
-
function fileRootCauseShape(graph, origin,
|
|
1478
|
+
function fileRootCauseShape(graph, origin, walk3) {
|
|
1452
1479
|
const owner = resolveOwningService(graph, origin.id);
|
|
1453
1480
|
if (!owner) return null;
|
|
1454
|
-
return serviceRootCauseShape(graph, owner.svc,
|
|
1481
|
+
return serviceRootCauseShape(graph, owner.svc, walk3);
|
|
1455
1482
|
}
|
|
1456
1483
|
var rootCauseShapes = {
|
|
1457
1484
|
[import_types.NodeType.DatabaseNode]: databaseRootCauseShape,
|
|
@@ -1463,16 +1490,16 @@ function getRootCause(graph, errorNodeId, errorEvent, incidents) {
|
|
|
1463
1490
|
const origin = graph.getNodeAttributes(errorNodeId);
|
|
1464
1491
|
const shape = rootCauseShapes[origin.type];
|
|
1465
1492
|
if (shape) {
|
|
1466
|
-
const
|
|
1467
|
-
const match = shape(graph, origin,
|
|
1493
|
+
const walk3 = longestIncomingWalk(graph, errorNodeId, ROOT_CAUSE_MAX_DEPTH);
|
|
1494
|
+
const match = shape(graph, origin, walk3);
|
|
1468
1495
|
if (match) {
|
|
1469
1496
|
const reason = errorEvent ? `${match.rootCauseReason} (observed error: ${errorEvent.errorMessage})` : match.rootCauseReason;
|
|
1470
1497
|
return import_types.RootCauseResultSchema.parse({
|
|
1471
1498
|
rootCauseNode: match.rootCauseNode,
|
|
1472
1499
|
rootCauseReason: reason,
|
|
1473
|
-
traversalPath:
|
|
1474
|
-
edgeProvenances:
|
|
1475
|
-
confidence: confidenceFromMix(
|
|
1500
|
+
traversalPath: walk3.path,
|
|
1501
|
+
edgeProvenances: walk3.edges.map((e) => e.provenance),
|
|
1502
|
+
confidence: confidenceFromMix(walk3.edges),
|
|
1476
1503
|
fixRecommendation: match.fixRecommendation
|
|
1477
1504
|
});
|
|
1478
1505
|
}
|
|
@@ -1530,9 +1557,9 @@ function rootCauseFromIncidents(nodeId, incidents, errorEvent) {
|
|
|
1530
1557
|
function isFailingCallEdge(e) {
|
|
1531
1558
|
return e.type === import_types.EdgeType.CALLS && (e.signal?.errorCount ?? 0) > 0;
|
|
1532
1559
|
}
|
|
1533
|
-
function callSourcesForService(graph,
|
|
1534
|
-
const ids = [
|
|
1535
|
-
for (const edgeId of graph.outboundEdges(
|
|
1560
|
+
function callSourcesForService(graph, serviceId4) {
|
|
1561
|
+
const ids = [serviceId4];
|
|
1562
|
+
for (const edgeId of graph.outboundEdges(serviceId4)) {
|
|
1536
1563
|
const e = graph.getEdgeAttributes(edgeId);
|
|
1537
1564
|
if (e.type !== import_types.EdgeType.CONTAINS) continue;
|
|
1538
1565
|
const tgt = graph.getNodeAttributes(e.target);
|
|
@@ -1549,9 +1576,9 @@ function failingCallDominates(e, id, curEdge, curId) {
|
|
|
1549
1576
|
}
|
|
1550
1577
|
return id < curId;
|
|
1551
1578
|
}
|
|
1552
|
-
function dominantFailingCall(graph,
|
|
1579
|
+
function dominantFailingCall(graph, serviceId4, visited) {
|
|
1553
1580
|
let best = null;
|
|
1554
|
-
for (const src of callSourcesForService(graph,
|
|
1581
|
+
for (const src of callSourcesForService(graph, serviceId4)) {
|
|
1555
1582
|
for (const edgeId of graph.outboundEdges(src)) {
|
|
1556
1583
|
const e = graph.getEdgeAttributes(edgeId);
|
|
1557
1584
|
if (!isFailingCallEdge(e)) continue;
|
|
@@ -1566,26 +1593,26 @@ function dominantFailingCall(graph, serviceId3, visited) {
|
|
|
1566
1593
|
return best;
|
|
1567
1594
|
}
|
|
1568
1595
|
function followFailingCallChain(graph, originServiceId, maxDepth) {
|
|
1569
|
-
const
|
|
1596
|
+
const path45 = [originServiceId];
|
|
1570
1597
|
const edges = [];
|
|
1571
1598
|
const visited = /* @__PURE__ */ new Set([originServiceId]);
|
|
1572
1599
|
let current = originServiceId;
|
|
1573
1600
|
for (let depth = 0; depth < maxDepth; depth++) {
|
|
1574
1601
|
const hop = dominantFailingCall(graph, current, visited);
|
|
1575
1602
|
if (!hop) break;
|
|
1576
|
-
|
|
1603
|
+
path45.push(hop.nextService);
|
|
1577
1604
|
edges.push(hop.edge);
|
|
1578
1605
|
visited.add(hop.nextService);
|
|
1579
1606
|
current = hop.nextService;
|
|
1580
1607
|
}
|
|
1581
1608
|
if (edges.length === 0) return null;
|
|
1582
|
-
return { path:
|
|
1609
|
+
return { path: path45, edges, culprit: current };
|
|
1583
1610
|
}
|
|
1584
1611
|
function crossServiceRootCause(graph, originId, incidents, errorEvent) {
|
|
1585
1612
|
const chain = followFailingCallChain(graph, originId, ROOT_CAUSE_MAX_DEPTH);
|
|
1586
1613
|
if (!chain) return null;
|
|
1587
1614
|
const culprit = chain.culprit;
|
|
1588
|
-
const
|
|
1615
|
+
const path45 = [...chain.path];
|
|
1589
1616
|
const edgeProvenances = chain.edges.map((e) => e.provenance);
|
|
1590
1617
|
const baseConfidence = confidenceFromMix(chain.edges);
|
|
1591
1618
|
const confidence = Math.max(0, Math.min(1, baseConfidence * INCIDENT_ROOT_CAUSE_CONFIDENCE));
|
|
@@ -1593,14 +1620,14 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
|
|
|
1593
1620
|
if (loc) {
|
|
1594
1621
|
let rootCauseNode = culprit;
|
|
1595
1622
|
if (loc.fileNode) {
|
|
1596
|
-
|
|
1623
|
+
path45.push(loc.fileNode);
|
|
1597
1624
|
edgeProvenances.push(import_types.Provenance.OBSERVED);
|
|
1598
1625
|
rootCauseNode = loc.fileNode;
|
|
1599
1626
|
}
|
|
1600
1627
|
return import_types.RootCauseResultSchema.parse({
|
|
1601
1628
|
rootCauseNode,
|
|
1602
1629
|
rootCauseReason: loc.rootCauseReason,
|
|
1603
|
-
traversalPath:
|
|
1630
|
+
traversalPath: path45,
|
|
1604
1631
|
edgeProvenances,
|
|
1605
1632
|
confidence,
|
|
1606
1633
|
...loc.fixRecommendation ? { fixRecommendation: loc.fixRecommendation } : {}
|
|
@@ -1612,7 +1639,7 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
|
|
|
1612
1639
|
return import_types.RootCauseResultSchema.parse({
|
|
1613
1640
|
rootCauseNode: culprit,
|
|
1614
1641
|
rootCauseReason: `${culpritName} is failing downstream calls (${errs} observed error${errs === 1 ? "" : "s"})`,
|
|
1615
|
-
traversalPath:
|
|
1642
|
+
traversalPath: path45,
|
|
1616
1643
|
edgeProvenances,
|
|
1617
1644
|
confidence,
|
|
1618
1645
|
fixRecommendation: `Inspect ${culpritName}'s failing handler`
|
|
@@ -2824,10 +2851,48 @@ var STITCH_EDGE_TYPES = /* @__PURE__ */ new Set([
|
|
|
2824
2851
|
]);
|
|
2825
2852
|
var WIRE_SPAN_KIND_CLIENT = 3;
|
|
2826
2853
|
var WIRE_SPAN_KIND_PRODUCER = 4;
|
|
2854
|
+
var WIRE_SPAN_KIND_CONSUMER = 5;
|
|
2827
2855
|
function spanMintsObservedEdge(kind) {
|
|
2828
2856
|
if (kind === void 0 || kind === 0) return true;
|
|
2829
2857
|
return kind === WIRE_SPAN_KIND_CLIENT || kind === WIRE_SPAN_KIND_PRODUCER;
|
|
2830
2858
|
}
|
|
2859
|
+
function spanMintsMessagingEdge(kind) {
|
|
2860
|
+
return kind === WIRE_SPAN_KIND_PRODUCER || kind === WIRE_SPAN_KIND_CONSUMER;
|
|
2861
|
+
}
|
|
2862
|
+
function spanServesGraphqlOperation(kind) {
|
|
2863
|
+
return kind !== WIRE_SPAN_KIND_CLIENT && kind !== WIRE_SPAN_KIND_PRODUCER && kind !== WIRE_SPAN_KIND_CONSUMER;
|
|
2864
|
+
}
|
|
2865
|
+
function ensureGraphqlOperationNode(graph, serviceName, operationType, operationName) {
|
|
2866
|
+
const id = (0, import_types4.graphqlOperationId)(serviceName, operationType, operationName);
|
|
2867
|
+
if (graph.hasNode(id)) return id;
|
|
2868
|
+
const node = {
|
|
2869
|
+
id,
|
|
2870
|
+
type: import_types4.NodeType.GraphQLOperationNode,
|
|
2871
|
+
name: operationName,
|
|
2872
|
+
service: serviceName,
|
|
2873
|
+
operationType: operationType.toLowerCase(),
|
|
2874
|
+
operationName,
|
|
2875
|
+
discoveredVia: "otel"
|
|
2876
|
+
};
|
|
2877
|
+
graph.addNode(id, node);
|
|
2878
|
+
return id;
|
|
2879
|
+
}
|
|
2880
|
+
function messagingDestinationKind(system) {
|
|
2881
|
+
return `${system}-topic`;
|
|
2882
|
+
}
|
|
2883
|
+
function ensureMessagingDestinationNode(graph, system, destination) {
|
|
2884
|
+
const id = (0, import_types4.infraId)(messagingDestinationKind(system), destination);
|
|
2885
|
+
if (graph.hasNode(id)) return id;
|
|
2886
|
+
const node = {
|
|
2887
|
+
id,
|
|
2888
|
+
type: import_types4.NodeType.InfraNode,
|
|
2889
|
+
name: destination,
|
|
2890
|
+
provider: "self",
|
|
2891
|
+
kind: messagingDestinationKind(system)
|
|
2892
|
+
};
|
|
2893
|
+
graph.addNode(id, node);
|
|
2894
|
+
return id;
|
|
2895
|
+
}
|
|
2831
2896
|
var PARENT_SPAN_CACHE_SIZE = 1e4;
|
|
2832
2897
|
var PARENT_SPAN_CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
2833
2898
|
var parentSpanCache = /* @__PURE__ */ new Map();
|
|
@@ -2921,6 +2986,21 @@ function ensureDatabaseNode(graph, host, engine) {
|
|
|
2921
2986
|
graph.addNode(id, node);
|
|
2922
2987
|
return id;
|
|
2923
2988
|
}
|
|
2989
|
+
function ensureLocalDatabaseNode(graph, serviceName, name, engine) {
|
|
2990
|
+
const id = (0, import_types4.localDatabaseId)(serviceName, name);
|
|
2991
|
+
if (graph.hasNode(id)) return id;
|
|
2992
|
+
const node = {
|
|
2993
|
+
id,
|
|
2994
|
+
type: import_types4.NodeType.DatabaseNode,
|
|
2995
|
+
name,
|
|
2996
|
+
engine,
|
|
2997
|
+
engineVersion: "unknown",
|
|
2998
|
+
compatibleDrivers: [],
|
|
2999
|
+
discoveredVia: "otel"
|
|
3000
|
+
};
|
|
3001
|
+
graph.addNode(id, node);
|
|
3002
|
+
return id;
|
|
3003
|
+
}
|
|
2924
3004
|
function ensureFrontierNode(graph, host, ts) {
|
|
2925
3005
|
const id = frontierIdFor(host);
|
|
2926
3006
|
if (graph.hasNode(id)) {
|
|
@@ -3147,10 +3227,21 @@ async function handleSpan(ctx, span) {
|
|
|
3147
3227
|
let affectedNode = sourceId;
|
|
3148
3228
|
const mintsFromCallerSide = spanMintsObservedEdge(span.kind);
|
|
3149
3229
|
if (span.dbSystem) {
|
|
3150
|
-
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
|
|
3230
|
+
if (mintsFromCallerSide) {
|
|
3231
|
+
const host = pickAddress(span);
|
|
3232
|
+
let targetId;
|
|
3233
|
+
if (host) {
|
|
3234
|
+
ensureDatabaseNode(ctx.graph, host, span.dbSystem);
|
|
3235
|
+
targetId = (0, import_types4.databaseId)(host);
|
|
3236
|
+
} else {
|
|
3237
|
+
const localName = span.dbName ?? span.dbSystem;
|
|
3238
|
+
targetId = ensureLocalDatabaseNode(
|
|
3239
|
+
ctx.graph,
|
|
3240
|
+
span.service,
|
|
3241
|
+
localName,
|
|
3242
|
+
span.dbSystem
|
|
3243
|
+
);
|
|
3244
|
+
}
|
|
3154
3245
|
const result = upsertObservedEdge(
|
|
3155
3246
|
ctx.graph,
|
|
3156
3247
|
import_types4.EdgeType.CONNECTS_TO,
|
|
@@ -3162,6 +3253,40 @@ async function handleSpan(ctx, span) {
|
|
|
3162
3253
|
);
|
|
3163
3254
|
if (result) affectedNode = targetId;
|
|
3164
3255
|
}
|
|
3256
|
+
} else if (span.messagingSystem && span.messagingDestination && spanMintsMessagingEdge(span.kind)) {
|
|
3257
|
+
const targetId = ensureMessagingDestinationNode(
|
|
3258
|
+
ctx.graph,
|
|
3259
|
+
span.messagingSystem,
|
|
3260
|
+
span.messagingDestination
|
|
3261
|
+
);
|
|
3262
|
+
const edgeType = span.kind === WIRE_SPAN_KIND_CONSUMER ? import_types4.EdgeType.CONSUMES_FROM : import_types4.EdgeType.PUBLISHES_TO;
|
|
3263
|
+
const result = upsertObservedEdge(
|
|
3264
|
+
ctx.graph,
|
|
3265
|
+
edgeType,
|
|
3266
|
+
observedSource(),
|
|
3267
|
+
targetId,
|
|
3268
|
+
ts,
|
|
3269
|
+
isError,
|
|
3270
|
+
callSiteEvidence
|
|
3271
|
+
);
|
|
3272
|
+
if (result) affectedNode = targetId;
|
|
3273
|
+
} else if (span.graphqlOperationName && span.graphqlOperationType && spanServesGraphqlOperation(span.kind)) {
|
|
3274
|
+
const targetId = ensureGraphqlOperationNode(
|
|
3275
|
+
ctx.graph,
|
|
3276
|
+
span.service,
|
|
3277
|
+
span.graphqlOperationType,
|
|
3278
|
+
span.graphqlOperationName
|
|
3279
|
+
);
|
|
3280
|
+
const result = upsertObservedEdge(
|
|
3281
|
+
ctx.graph,
|
|
3282
|
+
import_types4.EdgeType.CONTAINS,
|
|
3283
|
+
observedSource(),
|
|
3284
|
+
targetId,
|
|
3285
|
+
ts,
|
|
3286
|
+
isError,
|
|
3287
|
+
callSiteEvidence
|
|
3288
|
+
);
|
|
3289
|
+
if (result) affectedNode = targetId;
|
|
3165
3290
|
} else {
|
|
3166
3291
|
const host = pickAddress(span);
|
|
3167
3292
|
let resolvedViaAddress = false;
|
|
@@ -3271,29 +3396,29 @@ function promoteFrontierNodes(graph, opts = {}) {
|
|
|
3271
3396
|
toPromote.push({ frontierId: id, serviceId: target });
|
|
3272
3397
|
});
|
|
3273
3398
|
let promoted = 0;
|
|
3274
|
-
for (const { frontierId: frontierId2, serviceId:
|
|
3399
|
+
for (const { frontierId: frontierId2, serviceId: serviceId4 } of toPromote) {
|
|
3275
3400
|
if (opts.policies && opts.policies.length > 0 && opts.policyCtx) {
|
|
3276
3401
|
const gate = canPromoteFrontier(graph, frontierId2, opts.policies, opts.policyCtx);
|
|
3277
3402
|
if (!gate.allowed) {
|
|
3278
3403
|
continue;
|
|
3279
3404
|
}
|
|
3280
3405
|
}
|
|
3281
|
-
rewireFrontierEdges(graph, frontierId2,
|
|
3406
|
+
rewireFrontierEdges(graph, frontierId2, serviceId4);
|
|
3282
3407
|
graph.dropNode(frontierId2);
|
|
3283
3408
|
promoted++;
|
|
3284
3409
|
}
|
|
3285
3410
|
return promoted;
|
|
3286
3411
|
}
|
|
3287
|
-
function rewireFrontierEdges(graph, frontierId2,
|
|
3412
|
+
function rewireFrontierEdges(graph, frontierId2, serviceId4) {
|
|
3288
3413
|
const inbound = [...graph.inboundEdges(frontierId2)];
|
|
3289
3414
|
const outbound = [...graph.outboundEdges(frontierId2)];
|
|
3290
3415
|
for (const edgeId of inbound) {
|
|
3291
3416
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
3292
|
-
rebuildEdge(graph, edge, edge.source,
|
|
3417
|
+
rebuildEdge(graph, edge, edge.source, serviceId4, edgeId);
|
|
3293
3418
|
}
|
|
3294
3419
|
for (const edgeId of outbound) {
|
|
3295
3420
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
3296
|
-
rebuildEdge(graph, edge,
|
|
3421
|
+
rebuildEdge(graph, edge, serviceId4, edge.target, edgeId);
|
|
3297
3422
|
}
|
|
3298
3423
|
}
|
|
3299
3424
|
function rebuildEdge(graph, edge, newSource, newTarget, oldEdgeId) {
|
|
@@ -4055,9 +4180,9 @@ var K8S_KINDS_WITH_HOSTNAMES = /* @__PURE__ */ new Set([
|
|
|
4055
4180
|
"StatefulSet",
|
|
4056
4181
|
"DaemonSet"
|
|
4057
4182
|
]);
|
|
4058
|
-
function addAliases(graph,
|
|
4059
|
-
if (!graph.hasNode(
|
|
4060
|
-
const node = graph.getNodeAttributes(
|
|
4183
|
+
function addAliases(graph, serviceId4, candidates) {
|
|
4184
|
+
if (!graph.hasNode(serviceId4)) return;
|
|
4185
|
+
const node = graph.getNodeAttributes(serviceId4);
|
|
4061
4186
|
if (node.type !== import_types7.NodeType.ServiceNode) return;
|
|
4062
4187
|
const set = new Set(node.aliases ?? []);
|
|
4063
4188
|
for (const c of candidates) {
|
|
@@ -4067,7 +4192,7 @@ function addAliases(graph, serviceId3, candidates) {
|
|
|
4067
4192
|
}
|
|
4068
4193
|
if (set.size === 0) return;
|
|
4069
4194
|
const updated = { ...node, aliases: [...set].sort() };
|
|
4070
|
-
graph.replaceNodeAttributes(
|
|
4195
|
+
graph.replaceNodeAttributes(serviceId4, updated);
|
|
4071
4196
|
}
|
|
4072
4197
|
function indexServicesByName(services) {
|
|
4073
4198
|
const map = /* @__PURE__ */ new Map();
|
|
@@ -4100,12 +4225,12 @@ async function collectComposeAliases(graph, scanPath, serviceIndex) {
|
|
|
4100
4225
|
}
|
|
4101
4226
|
if (!compose?.services) return;
|
|
4102
4227
|
for (const [composeName, svc] of Object.entries(compose.services)) {
|
|
4103
|
-
const
|
|
4104
|
-
if (!
|
|
4228
|
+
const serviceId4 = serviceIndex.get(composeName);
|
|
4229
|
+
if (!serviceId4) continue;
|
|
4105
4230
|
const aliases = /* @__PURE__ */ new Set([composeName]);
|
|
4106
4231
|
if (svc.container_name) aliases.add(svc.container_name);
|
|
4107
4232
|
if (svc.hostname) aliases.add(svc.hostname);
|
|
4108
|
-
addAliases(graph,
|
|
4233
|
+
addAliases(graph, serviceId4, aliases);
|
|
4109
4234
|
}
|
|
4110
4235
|
}
|
|
4111
4236
|
var LABEL_KEYS = /* @__PURE__ */ new Set([
|
|
@@ -4218,16 +4343,28 @@ init_cjs_shims();
|
|
|
4218
4343
|
var import_node_fs12 = require("fs");
|
|
4219
4344
|
var import_node_path12 = __toESM(require("path"), 1);
|
|
4220
4345
|
var import_types8 = require("@neat.is/types");
|
|
4346
|
+
function buildServiceHostIndex(services) {
|
|
4347
|
+
const knownHosts = /* @__PURE__ */ new Set();
|
|
4348
|
+
const hostToNodeId = /* @__PURE__ */ new Map();
|
|
4349
|
+
for (const service of services) {
|
|
4350
|
+
const base = import_node_path12.default.basename(service.dir);
|
|
4351
|
+
knownHosts.add(base);
|
|
4352
|
+
knownHosts.add(service.pkg.name);
|
|
4353
|
+
hostToNodeId.set(base, service.node.id);
|
|
4354
|
+
hostToNodeId.set(service.pkg.name, service.node.id);
|
|
4355
|
+
}
|
|
4356
|
+
return { knownHosts, hostToNodeId };
|
|
4357
|
+
}
|
|
4221
4358
|
async function walkSourceFiles(dir) {
|
|
4222
4359
|
const out = [];
|
|
4223
|
-
async function
|
|
4360
|
+
async function walk3(current) {
|
|
4224
4361
|
const entries = await import_node_fs12.promises.readdir(current, { withFileTypes: true }).catch(() => []);
|
|
4225
4362
|
for (const entry of entries) {
|
|
4226
4363
|
const full = import_node_path12.default.join(current, entry.name);
|
|
4227
4364
|
if (entry.isDirectory()) {
|
|
4228
4365
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
4229
4366
|
if (await isPythonVenvDir(full)) continue;
|
|
4230
|
-
await
|
|
4367
|
+
await walk3(full);
|
|
4231
4368
|
} 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
4369
|
// would attribute our instrumentation imports to the user's service.
|
|
4233
4370
|
!isNeatAuthoredSourceFile(entry.name)) {
|
|
@@ -4235,7 +4372,7 @@ async function walkSourceFiles(dir) {
|
|
|
4235
4372
|
}
|
|
4236
4373
|
}
|
|
4237
4374
|
}
|
|
4238
|
-
await
|
|
4375
|
+
await walk3(dir);
|
|
4239
4376
|
return out;
|
|
4240
4377
|
}
|
|
4241
4378
|
async function loadSourceFiles(dir) {
|
|
@@ -5329,20 +5466,20 @@ var import_node_path23 = __toESM(require("path"), 1);
|
|
|
5329
5466
|
var import_types11 = require("@neat.is/types");
|
|
5330
5467
|
async function walkConfigFiles(dir) {
|
|
5331
5468
|
const out = [];
|
|
5332
|
-
async function
|
|
5469
|
+
async function walk3(current) {
|
|
5333
5470
|
const entries = await import_node_fs16.promises.readdir(current, { withFileTypes: true });
|
|
5334
5471
|
for (const entry of entries) {
|
|
5335
5472
|
const full = import_node_path23.default.join(current, entry.name);
|
|
5336
5473
|
if (entry.isDirectory()) {
|
|
5337
5474
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
5338
5475
|
if (await isPythonVenvDir(full)) continue;
|
|
5339
|
-
await
|
|
5476
|
+
await walk3(full);
|
|
5340
5477
|
} else if (entry.isFile() && isConfigFile(entry.name).match) {
|
|
5341
5478
|
out.push(full);
|
|
5342
5479
|
}
|
|
5343
5480
|
}
|
|
5344
5481
|
}
|
|
5345
|
-
await
|
|
5482
|
+
await walk3(dir);
|
|
5346
5483
|
return out;
|
|
5347
5484
|
}
|
|
5348
5485
|
async function addConfigNodes(graph, services, scanPath) {
|
|
@@ -5390,17 +5527,347 @@ async function addConfigNodes(graph, services, scanPath) {
|
|
|
5390
5527
|
return { nodesAdded, edgesAdded };
|
|
5391
5528
|
}
|
|
5392
5529
|
|
|
5530
|
+
// src/extract/routes.ts
|
|
5531
|
+
init_cjs_shims();
|
|
5532
|
+
var import_node_path24 = __toESM(require("path"), 1);
|
|
5533
|
+
var import_tree_sitter2 = __toESM(require("tree-sitter"), 1);
|
|
5534
|
+
var import_tree_sitter_javascript2 = __toESM(require("tree-sitter-javascript"), 1);
|
|
5535
|
+
var import_types12 = require("@neat.is/types");
|
|
5536
|
+
var PARSE_CHUNK2 = 16384;
|
|
5537
|
+
function parseSource2(parser, source) {
|
|
5538
|
+
return parser.parse(
|
|
5539
|
+
(index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK2)
|
|
5540
|
+
);
|
|
5541
|
+
}
|
|
5542
|
+
function makeJsParser2() {
|
|
5543
|
+
const p = new import_tree_sitter2.default();
|
|
5544
|
+
p.setLanguage(import_tree_sitter_javascript2.default);
|
|
5545
|
+
return p;
|
|
5546
|
+
}
|
|
5547
|
+
var ROUTER_METHODS = /* @__PURE__ */ new Set([
|
|
5548
|
+
"get",
|
|
5549
|
+
"post",
|
|
5550
|
+
"put",
|
|
5551
|
+
"patch",
|
|
5552
|
+
"delete",
|
|
5553
|
+
"options",
|
|
5554
|
+
"head",
|
|
5555
|
+
"all"
|
|
5556
|
+
]);
|
|
5557
|
+
var NEXT_APP_METHODS = /* @__PURE__ */ new Set(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"]);
|
|
5558
|
+
var JS_ROUTE_EXTENSIONS = /* @__PURE__ */ new Set([".js", ".jsx", ".mjs", ".cjs", ".ts", ".tsx"]);
|
|
5559
|
+
function canonicalizeTemplate(raw) {
|
|
5560
|
+
let p = raw.split("?")[0].split("#")[0];
|
|
5561
|
+
if (!p.startsWith("/")) p = "/" + p;
|
|
5562
|
+
if (p.length > 1 && p.endsWith("/")) p = p.slice(0, -1);
|
|
5563
|
+
return p;
|
|
5564
|
+
}
|
|
5565
|
+
function isDynamicSegment(seg) {
|
|
5566
|
+
if (seg.length === 0) return false;
|
|
5567
|
+
if (seg.includes(":")) return true;
|
|
5568
|
+
if (seg.startsWith("{") || seg.startsWith("[")) return true;
|
|
5569
|
+
if (/^\d+$/.test(seg)) return true;
|
|
5570
|
+
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;
|
|
5571
|
+
if (/^[0-9a-f]{24,}$/i.test(seg)) return true;
|
|
5572
|
+
return false;
|
|
5573
|
+
}
|
|
5574
|
+
function normalizePathTemplate(raw) {
|
|
5575
|
+
const canonical = canonicalizeTemplate(raw);
|
|
5576
|
+
const segments = canonical.split("/").filter((s) => s.length > 0);
|
|
5577
|
+
const normalised = segments.map((seg) => isDynamicSegment(seg) ? ":param" : seg.toLowerCase());
|
|
5578
|
+
return "/" + normalised.join("/");
|
|
5579
|
+
}
|
|
5580
|
+
function walk(node, visit) {
|
|
5581
|
+
visit(node);
|
|
5582
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
5583
|
+
const child = node.namedChild(i);
|
|
5584
|
+
if (child) walk(child, visit);
|
|
5585
|
+
}
|
|
5586
|
+
}
|
|
5587
|
+
function staticStringText(node) {
|
|
5588
|
+
if (node.type === "string") {
|
|
5589
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
5590
|
+
const child = node.namedChild(i);
|
|
5591
|
+
if (child?.type === "string_fragment") return child.text;
|
|
5592
|
+
}
|
|
5593
|
+
return "";
|
|
5594
|
+
}
|
|
5595
|
+
if (node.type === "template_string") {
|
|
5596
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
5597
|
+
if (node.namedChild(i)?.type === "template_substitution") return null;
|
|
5598
|
+
}
|
|
5599
|
+
const raw = node.text;
|
|
5600
|
+
return raw.length >= 2 ? raw.slice(1, -1) : "";
|
|
5601
|
+
}
|
|
5602
|
+
return null;
|
|
5603
|
+
}
|
|
5604
|
+
function objectStringProp(objNode, key) {
|
|
5605
|
+
for (let i = 0; i < objNode.namedChildCount; i++) {
|
|
5606
|
+
const pair = objNode.namedChild(i);
|
|
5607
|
+
if (!pair || pair.type !== "pair") continue;
|
|
5608
|
+
const k = pair.childForFieldName("key");
|
|
5609
|
+
if (!k) continue;
|
|
5610
|
+
const kText = k.type === "string" ? staticStringText(k) : k.text;
|
|
5611
|
+
if (kText !== key) continue;
|
|
5612
|
+
const v = pair.childForFieldName("value");
|
|
5613
|
+
if (v) return staticStringText(v);
|
|
5614
|
+
}
|
|
5615
|
+
return null;
|
|
5616
|
+
}
|
|
5617
|
+
function fastifyRouteMethods(objNode) {
|
|
5618
|
+
for (let i = 0; i < objNode.namedChildCount; i++) {
|
|
5619
|
+
const pair = objNode.namedChild(i);
|
|
5620
|
+
if (!pair || pair.type !== "pair") continue;
|
|
5621
|
+
const k = pair.childForFieldName("key");
|
|
5622
|
+
const kText = k ? k.type === "string" ? staticStringText(k) : k.text : null;
|
|
5623
|
+
if (kText !== "method") continue;
|
|
5624
|
+
const v = pair.childForFieldName("value");
|
|
5625
|
+
if (!v) return [];
|
|
5626
|
+
if (v.type === "string" || v.type === "template_string") {
|
|
5627
|
+
const s = staticStringText(v);
|
|
5628
|
+
return s ? [s.toUpperCase()] : [];
|
|
5629
|
+
}
|
|
5630
|
+
if (v.type === "array") {
|
|
5631
|
+
const out = [];
|
|
5632
|
+
for (let j = 0; j < v.namedChildCount; j++) {
|
|
5633
|
+
const el = v.namedChild(j);
|
|
5634
|
+
if (el && (el.type === "string" || el.type === "template_string")) {
|
|
5635
|
+
const s = staticStringText(el);
|
|
5636
|
+
if (s) out.push(s.toUpperCase());
|
|
5637
|
+
}
|
|
5638
|
+
}
|
|
5639
|
+
return out;
|
|
5640
|
+
}
|
|
5641
|
+
}
|
|
5642
|
+
return [];
|
|
5643
|
+
}
|
|
5644
|
+
function serverRoutesFromSource(source, parser, hasExpress, hasFastify) {
|
|
5645
|
+
const tree = parseSource2(parser, source);
|
|
5646
|
+
const out = [];
|
|
5647
|
+
const framework = hasExpress ? "express" : "fastify";
|
|
5648
|
+
walk(tree.rootNode, (node) => {
|
|
5649
|
+
if (node.type !== "call_expression") return;
|
|
5650
|
+
const fn = node.childForFieldName("function");
|
|
5651
|
+
if (!fn || fn.type !== "member_expression") return;
|
|
5652
|
+
const prop = fn.childForFieldName("property");
|
|
5653
|
+
if (!prop) return;
|
|
5654
|
+
const method = prop.text.toLowerCase();
|
|
5655
|
+
const args = node.childForFieldName("arguments");
|
|
5656
|
+
const first = args?.namedChild(0);
|
|
5657
|
+
if (!first) return;
|
|
5658
|
+
const line = node.startPosition.row + 1;
|
|
5659
|
+
if (ROUTER_METHODS.has(method)) {
|
|
5660
|
+
const p = staticStringText(first);
|
|
5661
|
+
if (p && p.startsWith("/")) {
|
|
5662
|
+
out.push({
|
|
5663
|
+
method: method === "all" ? "ALL" : method.toUpperCase(),
|
|
5664
|
+
pathTemplate: canonicalizeTemplate(p),
|
|
5665
|
+
line,
|
|
5666
|
+
framework
|
|
5667
|
+
});
|
|
5668
|
+
}
|
|
5669
|
+
return;
|
|
5670
|
+
}
|
|
5671
|
+
if (method === "route" && hasFastify && first.type === "object") {
|
|
5672
|
+
const url = objectStringProp(first, "url");
|
|
5673
|
+
if (!url || !url.startsWith("/")) return;
|
|
5674
|
+
const methods = fastifyRouteMethods(first);
|
|
5675
|
+
const list = methods.length > 0 ? methods : ["ALL"];
|
|
5676
|
+
for (const m of list) {
|
|
5677
|
+
out.push({
|
|
5678
|
+
method: m === "ALL" ? "ALL" : m.toUpperCase(),
|
|
5679
|
+
pathTemplate: canonicalizeTemplate(url),
|
|
5680
|
+
line,
|
|
5681
|
+
framework: "fastify"
|
|
5682
|
+
});
|
|
5683
|
+
}
|
|
5684
|
+
}
|
|
5685
|
+
});
|
|
5686
|
+
return out;
|
|
5687
|
+
}
|
|
5688
|
+
function segmentsOf(relFile) {
|
|
5689
|
+
return toPosix2(relFile).split("/").filter((s) => s.length > 0);
|
|
5690
|
+
}
|
|
5691
|
+
function isNextAppRouteFile(relFile) {
|
|
5692
|
+
const segs = segmentsOf(relFile);
|
|
5693
|
+
if (!segs.includes("app")) return false;
|
|
5694
|
+
const base = segs[segs.length - 1] ?? "";
|
|
5695
|
+
return /^route\.(?:js|jsx|mjs|cjs|ts|tsx)$/.test(base);
|
|
5696
|
+
}
|
|
5697
|
+
function isNextPagesApiFile(relFile) {
|
|
5698
|
+
const segs = segmentsOf(relFile);
|
|
5699
|
+
const pagesIdx = segs.indexOf("pages");
|
|
5700
|
+
if (pagesIdx === -1 || segs[pagesIdx + 1] !== "api") return false;
|
|
5701
|
+
const base = segs[segs.length - 1] ?? "";
|
|
5702
|
+
if (/^_(app|document|middleware)\./.test(base)) return false;
|
|
5703
|
+
return JS_ROUTE_EXTENSIONS.has(import_node_path24.default.extname(base));
|
|
5704
|
+
}
|
|
5705
|
+
function nextSegment(seg) {
|
|
5706
|
+
if (seg.startsWith("(") && seg.endsWith(")")) return null;
|
|
5707
|
+
const catchAll = seg.match(/^\[\[?\.\.\.(.+?)\]?\]$/);
|
|
5708
|
+
if (catchAll) return ":" + catchAll[1];
|
|
5709
|
+
const dynamic = seg.match(/^\[(.+?)\]$/);
|
|
5710
|
+
if (dynamic) return ":" + dynamic[1];
|
|
5711
|
+
return seg;
|
|
5712
|
+
}
|
|
5713
|
+
function nextAppPathTemplate(relFile) {
|
|
5714
|
+
const segs = segmentsOf(relFile);
|
|
5715
|
+
const appIdx = segs.lastIndexOf("app");
|
|
5716
|
+
const between = segs.slice(appIdx + 1, segs.length - 1);
|
|
5717
|
+
const parts = [];
|
|
5718
|
+
for (const seg of between) {
|
|
5719
|
+
const mapped = nextSegment(seg);
|
|
5720
|
+
if (mapped !== null) parts.push(mapped);
|
|
5721
|
+
}
|
|
5722
|
+
return "/" + parts.join("/");
|
|
5723
|
+
}
|
|
5724
|
+
function nextPagesApiPathTemplate(relFile) {
|
|
5725
|
+
const segs = segmentsOf(relFile);
|
|
5726
|
+
const pagesIdx = segs.indexOf("pages");
|
|
5727
|
+
const rest = segs.slice(pagesIdx + 1);
|
|
5728
|
+
const parts = [];
|
|
5729
|
+
for (let i = 0; i < rest.length; i++) {
|
|
5730
|
+
let seg = rest[i];
|
|
5731
|
+
if (i === rest.length - 1) {
|
|
5732
|
+
seg = seg.replace(/\.(?:js|jsx|mjs|cjs|ts|tsx)$/, "");
|
|
5733
|
+
if (seg === "index") continue;
|
|
5734
|
+
}
|
|
5735
|
+
const mapped = nextSegment(seg);
|
|
5736
|
+
if (mapped !== null) parts.push(mapped);
|
|
5737
|
+
}
|
|
5738
|
+
return "/" + parts.join("/");
|
|
5739
|
+
}
|
|
5740
|
+
function nextAppMethods(root) {
|
|
5741
|
+
const out = [];
|
|
5742
|
+
walk(root, (node) => {
|
|
5743
|
+
if (node.type !== "export_statement") return;
|
|
5744
|
+
const decl = node.childForFieldName("declaration");
|
|
5745
|
+
if (!decl) return;
|
|
5746
|
+
const line = node.startPosition.row + 1;
|
|
5747
|
+
if (decl.type === "function_declaration") {
|
|
5748
|
+
const name = decl.childForFieldName("name")?.text;
|
|
5749
|
+
if (name && NEXT_APP_METHODS.has(name)) out.push({ method: name, line });
|
|
5750
|
+
return;
|
|
5751
|
+
}
|
|
5752
|
+
if (decl.type === "lexical_declaration" || decl.type === "variable_declaration") {
|
|
5753
|
+
for (let i = 0; i < decl.namedChildCount; i++) {
|
|
5754
|
+
const d = decl.namedChild(i);
|
|
5755
|
+
if (d?.type !== "variable_declarator") continue;
|
|
5756
|
+
const name = d.childForFieldName("name")?.text;
|
|
5757
|
+
if (name && NEXT_APP_METHODS.has(name)) out.push({ method: name, line });
|
|
5758
|
+
}
|
|
5759
|
+
}
|
|
5760
|
+
});
|
|
5761
|
+
return out;
|
|
5762
|
+
}
|
|
5763
|
+
function nextRoutesFromFile(source, relFile, parser) {
|
|
5764
|
+
if (isNextAppRouteFile(relFile)) {
|
|
5765
|
+
const tree = parseSource2(parser, source);
|
|
5766
|
+
const template = nextAppPathTemplate(relFile);
|
|
5767
|
+
return nextAppMethods(tree.rootNode).map(({ method, line }) => ({
|
|
5768
|
+
method,
|
|
5769
|
+
pathTemplate: canonicalizeTemplate(template),
|
|
5770
|
+
line,
|
|
5771
|
+
framework: "next"
|
|
5772
|
+
}));
|
|
5773
|
+
}
|
|
5774
|
+
if (isNextPagesApiFile(relFile)) {
|
|
5775
|
+
return [
|
|
5776
|
+
{
|
|
5777
|
+
method: "ALL",
|
|
5778
|
+
pathTemplate: canonicalizeTemplate(nextPagesApiPathTemplate(relFile)),
|
|
5779
|
+
line: 1,
|
|
5780
|
+
framework: "next"
|
|
5781
|
+
}
|
|
5782
|
+
];
|
|
5783
|
+
}
|
|
5784
|
+
return [];
|
|
5785
|
+
}
|
|
5786
|
+
async function addRoutes(graph, services) {
|
|
5787
|
+
const jsParser = makeJsParser2();
|
|
5788
|
+
let nodesAdded = 0;
|
|
5789
|
+
let edgesAdded = 0;
|
|
5790
|
+
for (const service of services) {
|
|
5791
|
+
const deps = {
|
|
5792
|
+
...service.pkg.dependencies ?? {},
|
|
5793
|
+
...service.pkg.devDependencies ?? {}
|
|
5794
|
+
};
|
|
5795
|
+
const hasExpress = deps["express"] !== void 0;
|
|
5796
|
+
const hasFastify = deps["fastify"] !== void 0;
|
|
5797
|
+
const hasNext = deps["next"] !== void 0;
|
|
5798
|
+
if (!hasExpress && !hasFastify && !hasNext) continue;
|
|
5799
|
+
const files = await loadSourceFiles(service.dir);
|
|
5800
|
+
for (const file of files) {
|
|
5801
|
+
if (isTestPath(file.path)) continue;
|
|
5802
|
+
if (!JS_ROUTE_EXTENSIONS.has(import_node_path24.default.extname(file.path))) continue;
|
|
5803
|
+
const relFile = toPosix2(import_node_path24.default.relative(service.dir, file.path));
|
|
5804
|
+
let routes;
|
|
5805
|
+
try {
|
|
5806
|
+
if (hasNext && (isNextAppRouteFile(relFile) || isNextPagesApiFile(relFile))) {
|
|
5807
|
+
routes = nextRoutesFromFile(file.content, relFile, jsParser);
|
|
5808
|
+
} else if (hasExpress || hasFastify) {
|
|
5809
|
+
routes = serverRoutesFromSource(file.content, jsParser, hasExpress, hasFastify);
|
|
5810
|
+
} else {
|
|
5811
|
+
routes = [];
|
|
5812
|
+
}
|
|
5813
|
+
} catch (err) {
|
|
5814
|
+
recordExtractionError("route extraction", file.path, err);
|
|
5815
|
+
continue;
|
|
5816
|
+
}
|
|
5817
|
+
if (routes.length === 0) continue;
|
|
5818
|
+
for (const route of routes) {
|
|
5819
|
+
const rid = (0, import_types12.routeId)(service.pkg.name, route.method, route.pathTemplate);
|
|
5820
|
+
if (!graph.hasNode(rid)) {
|
|
5821
|
+
const node = {
|
|
5822
|
+
id: rid,
|
|
5823
|
+
type: import_types12.NodeType.RouteNode,
|
|
5824
|
+
name: `${route.method} ${route.pathTemplate}`,
|
|
5825
|
+
service: service.pkg.name,
|
|
5826
|
+
method: route.method,
|
|
5827
|
+
pathTemplate: route.pathTemplate,
|
|
5828
|
+
path: relFile,
|
|
5829
|
+
line: route.line,
|
|
5830
|
+
framework: route.framework,
|
|
5831
|
+
discoveredVia: "static"
|
|
5832
|
+
};
|
|
5833
|
+
graph.addNode(rid, node);
|
|
5834
|
+
nodesAdded++;
|
|
5835
|
+
}
|
|
5836
|
+
const containsId = (0, import_types12.extractedEdgeId)(service.node.id, rid, import_types12.EdgeType.CONTAINS);
|
|
5837
|
+
if (!graph.hasEdge(containsId)) {
|
|
5838
|
+
const edge = {
|
|
5839
|
+
id: containsId,
|
|
5840
|
+
source: service.node.id,
|
|
5841
|
+
target: rid,
|
|
5842
|
+
type: import_types12.EdgeType.CONTAINS,
|
|
5843
|
+
provenance: import_types12.Provenance.EXTRACTED,
|
|
5844
|
+
confidence: (0, import_types12.confidenceForExtracted)("structural"),
|
|
5845
|
+
evidence: {
|
|
5846
|
+
file: relFile,
|
|
5847
|
+
line: route.line,
|
|
5848
|
+
snippet: snippet(file.content, route.line)
|
|
5849
|
+
}
|
|
5850
|
+
};
|
|
5851
|
+
graph.addEdgeWithKey(containsId, service.node.id, rid, edge);
|
|
5852
|
+
edgesAdded++;
|
|
5853
|
+
}
|
|
5854
|
+
}
|
|
5855
|
+
}
|
|
5856
|
+
}
|
|
5857
|
+
return { nodesAdded, edgesAdded };
|
|
5858
|
+
}
|
|
5859
|
+
|
|
5393
5860
|
// src/extract/calls/index.ts
|
|
5394
5861
|
init_cjs_shims();
|
|
5395
|
-
var
|
|
5862
|
+
var import_types20 = require("@neat.is/types");
|
|
5396
5863
|
|
|
5397
5864
|
// src/extract/calls/http.ts
|
|
5398
5865
|
init_cjs_shims();
|
|
5399
|
-
var
|
|
5400
|
-
var
|
|
5401
|
-
var
|
|
5866
|
+
var import_node_path25 = __toESM(require("path"), 1);
|
|
5867
|
+
var import_tree_sitter3 = __toESM(require("tree-sitter"), 1);
|
|
5868
|
+
var import_tree_sitter_javascript3 = __toESM(require("tree-sitter-javascript"), 1);
|
|
5402
5869
|
var import_tree_sitter_python2 = __toESM(require("tree-sitter-python"), 1);
|
|
5403
|
-
var
|
|
5870
|
+
var import_types13 = require("@neat.is/types");
|
|
5404
5871
|
var STRING_LITERAL_NODE_TYPES = /* @__PURE__ */ new Set(["string_fragment", "string_content"]);
|
|
5405
5872
|
var JSX_EXTERNAL_LINK_TAGS = /* @__PURE__ */ new Set(["a", "Link", "NavLink", "ExternalLink", "Anchor"]);
|
|
5406
5873
|
function isInsideJsxExternalLink(node) {
|
|
@@ -5428,14 +5895,14 @@ function collectStringLiterals(node, out) {
|
|
|
5428
5895
|
if (child) collectStringLiterals(child, out);
|
|
5429
5896
|
}
|
|
5430
5897
|
}
|
|
5431
|
-
var
|
|
5432
|
-
function
|
|
5898
|
+
var PARSE_CHUNK3 = 16384;
|
|
5899
|
+
function parseSource3(parser, source) {
|
|
5433
5900
|
return parser.parse(
|
|
5434
|
-
(index) => index >= source.length ? "" : source.slice(index, index +
|
|
5901
|
+
(index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK3)
|
|
5435
5902
|
);
|
|
5436
5903
|
}
|
|
5437
5904
|
function callsFromSource(source, parser, knownHosts) {
|
|
5438
|
-
const tree =
|
|
5905
|
+
const tree = parseSource3(parser, source);
|
|
5439
5906
|
const literals = [];
|
|
5440
5907
|
collectStringLiterals(tree.rootNode, literals);
|
|
5441
5908
|
const out = [];
|
|
@@ -5449,27 +5916,20 @@ function callsFromSource(source, parser, knownHosts) {
|
|
|
5449
5916
|
}
|
|
5450
5917
|
return out;
|
|
5451
5918
|
}
|
|
5452
|
-
function
|
|
5453
|
-
const p = new
|
|
5454
|
-
p.setLanguage(
|
|
5919
|
+
function makeJsParser3() {
|
|
5920
|
+
const p = new import_tree_sitter3.default();
|
|
5921
|
+
p.setLanguage(import_tree_sitter_javascript3.default);
|
|
5455
5922
|
return p;
|
|
5456
5923
|
}
|
|
5457
5924
|
function makePyParser2() {
|
|
5458
|
-
const p = new
|
|
5925
|
+
const p = new import_tree_sitter3.default();
|
|
5459
5926
|
p.setLanguage(import_tree_sitter_python2.default);
|
|
5460
5927
|
return p;
|
|
5461
5928
|
}
|
|
5462
5929
|
async function addHttpCallEdges(graph, services) {
|
|
5463
|
-
const jsParser =
|
|
5930
|
+
const jsParser = makeJsParser3();
|
|
5464
5931
|
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
|
-
}
|
|
5932
|
+
const { knownHosts, hostToNodeId } = buildServiceHostIndex(services);
|
|
5473
5933
|
let nodesAdded = 0;
|
|
5474
5934
|
let edgesAdded = 0;
|
|
5475
5935
|
for (const service of services) {
|
|
@@ -5477,7 +5937,7 @@ async function addHttpCallEdges(graph, services) {
|
|
|
5477
5937
|
const seen = /* @__PURE__ */ new Set();
|
|
5478
5938
|
for (const file of files) {
|
|
5479
5939
|
if (isTestPath(file.path)) continue;
|
|
5480
|
-
const parser =
|
|
5940
|
+
const parser = import_node_path25.default.extname(file.path) === ".py" ? pyParser : jsParser;
|
|
5481
5941
|
let sites;
|
|
5482
5942
|
try {
|
|
5483
5943
|
sites = callsFromSource(file.content, parser, knownHosts);
|
|
@@ -5486,14 +5946,14 @@ async function addHttpCallEdges(graph, services) {
|
|
|
5486
5946
|
continue;
|
|
5487
5947
|
}
|
|
5488
5948
|
if (sites.length === 0) continue;
|
|
5489
|
-
const relFile = toPosix2(
|
|
5949
|
+
const relFile = toPosix2(import_node_path25.default.relative(service.dir, file.path));
|
|
5490
5950
|
for (const site of sites) {
|
|
5491
5951
|
const targetId = hostToNodeId.get(site.host);
|
|
5492
5952
|
if (!targetId || targetId === service.node.id) continue;
|
|
5493
5953
|
const dedupKey = `${relFile}|${targetId}`;
|
|
5494
5954
|
if (seen.has(dedupKey)) continue;
|
|
5495
5955
|
seen.add(dedupKey);
|
|
5496
|
-
const confidence = (0,
|
|
5956
|
+
const confidence = (0, import_types13.confidenceForExtracted)("url-literal-service-target");
|
|
5497
5957
|
const ev = {
|
|
5498
5958
|
file: relFile,
|
|
5499
5959
|
line: site.line,
|
|
@@ -5507,25 +5967,25 @@ async function addHttpCallEdges(graph, services) {
|
|
|
5507
5967
|
);
|
|
5508
5968
|
nodesAdded += n;
|
|
5509
5969
|
edgesAdded += e;
|
|
5510
|
-
if (!(0,
|
|
5970
|
+
if (!(0, import_types13.passesExtractedFloor)(confidence)) {
|
|
5511
5971
|
noteExtractedDropped({
|
|
5512
5972
|
source: fileNodeId,
|
|
5513
5973
|
target: targetId,
|
|
5514
|
-
type:
|
|
5974
|
+
type: import_types13.EdgeType.CALLS,
|
|
5515
5975
|
confidence,
|
|
5516
5976
|
confidenceKind: "url-literal-service-target",
|
|
5517
5977
|
evidence: ev
|
|
5518
5978
|
});
|
|
5519
5979
|
continue;
|
|
5520
5980
|
}
|
|
5521
|
-
const edgeId = (0, import_types5.extractedEdgeId)(fileNodeId, targetId,
|
|
5981
|
+
const edgeId = (0, import_types5.extractedEdgeId)(fileNodeId, targetId, import_types13.EdgeType.CALLS);
|
|
5522
5982
|
if (!graph.hasEdge(edgeId)) {
|
|
5523
5983
|
const edge = {
|
|
5524
5984
|
id: edgeId,
|
|
5525
5985
|
source: fileNodeId,
|
|
5526
5986
|
target: targetId,
|
|
5527
|
-
type:
|
|
5528
|
-
provenance:
|
|
5987
|
+
type: import_types13.EdgeType.CALLS,
|
|
5988
|
+
provenance: import_types13.Provenance.EXTRACTED,
|
|
5529
5989
|
confidence,
|
|
5530
5990
|
evidence: ev
|
|
5531
5991
|
};
|
|
@@ -5538,10 +5998,279 @@ async function addHttpCallEdges(graph, services) {
|
|
|
5538
5998
|
return { nodesAdded, edgesAdded };
|
|
5539
5999
|
}
|
|
5540
6000
|
|
|
6001
|
+
// src/extract/calls/route-match.ts
|
|
6002
|
+
init_cjs_shims();
|
|
6003
|
+
var import_node_path26 = __toESM(require("path"), 1);
|
|
6004
|
+
var import_tree_sitter4 = __toESM(require("tree-sitter"), 1);
|
|
6005
|
+
var import_tree_sitter_javascript4 = __toESM(require("tree-sitter-javascript"), 1);
|
|
6006
|
+
var import_types14 = require("@neat.is/types");
|
|
6007
|
+
var PARSE_CHUNK4 = 16384;
|
|
6008
|
+
function parseSource4(parser, source) {
|
|
6009
|
+
return parser.parse(
|
|
6010
|
+
(index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK4)
|
|
6011
|
+
);
|
|
6012
|
+
}
|
|
6013
|
+
function makeJsParser4() {
|
|
6014
|
+
const p = new import_tree_sitter4.default();
|
|
6015
|
+
p.setLanguage(import_tree_sitter_javascript4.default);
|
|
6016
|
+
return p;
|
|
6017
|
+
}
|
|
6018
|
+
var JS_CLIENT_EXTENSIONS = /* @__PURE__ */ new Set([".js", ".jsx", ".mjs", ".cjs", ".ts", ".tsx"]);
|
|
6019
|
+
var AXIOS_METHODS = /* @__PURE__ */ new Set(["get", "post", "put", "patch", "delete", "head", "options", "request"]);
|
|
6020
|
+
function walk2(node, visit) {
|
|
6021
|
+
visit(node);
|
|
6022
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
6023
|
+
const child = node.namedChild(i);
|
|
6024
|
+
if (child) walk2(child, visit);
|
|
6025
|
+
}
|
|
6026
|
+
}
|
|
6027
|
+
function reconstructUrl(node) {
|
|
6028
|
+
if (node.type === "string") {
|
|
6029
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
6030
|
+
const child = node.namedChild(i);
|
|
6031
|
+
if (child?.type === "string_fragment") return child.text;
|
|
6032
|
+
}
|
|
6033
|
+
return "";
|
|
6034
|
+
}
|
|
6035
|
+
if (node.type === "template_string") {
|
|
6036
|
+
let out = "";
|
|
6037
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
6038
|
+
const child = node.namedChild(i);
|
|
6039
|
+
if (!child) continue;
|
|
6040
|
+
if (child.type === "string_fragment") out += child.text;
|
|
6041
|
+
else if (child.type === "template_substitution") out += ":param";
|
|
6042
|
+
}
|
|
6043
|
+
if (out.length === 0) {
|
|
6044
|
+
const raw = node.text;
|
|
6045
|
+
return raw.length >= 2 ? raw.slice(1, -1) : "";
|
|
6046
|
+
}
|
|
6047
|
+
return out;
|
|
6048
|
+
}
|
|
6049
|
+
return null;
|
|
6050
|
+
}
|
|
6051
|
+
function methodFromOptions(objNode) {
|
|
6052
|
+
for (let i = 0; i < objNode.namedChildCount; i++) {
|
|
6053
|
+
const pair = objNode.namedChild(i);
|
|
6054
|
+
if (!pair || pair.type !== "pair") continue;
|
|
6055
|
+
const k = pair.childForFieldName("key");
|
|
6056
|
+
const kText = k ? k.type === "string" ? stringText(k) : k.text : null;
|
|
6057
|
+
if (kText !== "method") continue;
|
|
6058
|
+
const v = pair.childForFieldName("value");
|
|
6059
|
+
if (v && (v.type === "string" || v.type === "template_string")) {
|
|
6060
|
+
const s = stringText(v);
|
|
6061
|
+
return s ? s.toUpperCase() : void 0;
|
|
6062
|
+
}
|
|
6063
|
+
}
|
|
6064
|
+
return void 0;
|
|
6065
|
+
}
|
|
6066
|
+
function stringText(node) {
|
|
6067
|
+
if (node.type === "string") {
|
|
6068
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
6069
|
+
const child = node.namedChild(i);
|
|
6070
|
+
if (child?.type === "string_fragment") return child.text;
|
|
6071
|
+
}
|
|
6072
|
+
return "";
|
|
6073
|
+
}
|
|
6074
|
+
return null;
|
|
6075
|
+
}
|
|
6076
|
+
function urlNodeFromConfig(objNode) {
|
|
6077
|
+
for (let i = 0; i < objNode.namedChildCount; i++) {
|
|
6078
|
+
const pair = objNode.namedChild(i);
|
|
6079
|
+
if (!pair || pair.type !== "pair") continue;
|
|
6080
|
+
const k = pair.childForFieldName("key");
|
|
6081
|
+
const kText = k ? k.type === "string" ? stringText(k) : k.text : null;
|
|
6082
|
+
if (kText === "url") return pair.childForFieldName("value");
|
|
6083
|
+
}
|
|
6084
|
+
return null;
|
|
6085
|
+
}
|
|
6086
|
+
function pathOf(urlStr) {
|
|
6087
|
+
try {
|
|
6088
|
+
const candidate = urlStr.startsWith("//") ? `http:${urlStr}` : urlStr;
|
|
6089
|
+
const parsed = new URL(candidate);
|
|
6090
|
+
return parsed.pathname || "/";
|
|
6091
|
+
} catch {
|
|
6092
|
+
return null;
|
|
6093
|
+
}
|
|
6094
|
+
}
|
|
6095
|
+
function matchHost(urlStr, knownHosts) {
|
|
6096
|
+
for (const host of knownHosts) {
|
|
6097
|
+
if (urlMatchesHost(urlStr, host)) return host;
|
|
6098
|
+
}
|
|
6099
|
+
return null;
|
|
6100
|
+
}
|
|
6101
|
+
function clientCallSitesFromSource(source, parser, knownHosts) {
|
|
6102
|
+
const tree = parseSource4(parser, source);
|
|
6103
|
+
const out = [];
|
|
6104
|
+
const push = (urlNode, method, callNode) => {
|
|
6105
|
+
const urlStr = reconstructUrl(urlNode);
|
|
6106
|
+
if (!urlStr) return;
|
|
6107
|
+
const host = matchHost(urlStr, knownHosts);
|
|
6108
|
+
if (!host) return;
|
|
6109
|
+
const p = pathOf(urlStr);
|
|
6110
|
+
if (p === null) return;
|
|
6111
|
+
const line = callNode.startPosition.row + 1;
|
|
6112
|
+
out.push({
|
|
6113
|
+
host,
|
|
6114
|
+
method,
|
|
6115
|
+
pathTemplate: p,
|
|
6116
|
+
line,
|
|
6117
|
+
snippet: snippet(source, line)
|
|
6118
|
+
});
|
|
6119
|
+
};
|
|
6120
|
+
walk2(tree.rootNode, (node) => {
|
|
6121
|
+
if (node.type !== "call_expression") return;
|
|
6122
|
+
const fn = node.childForFieldName("function");
|
|
6123
|
+
if (!fn) return;
|
|
6124
|
+
const args = node.childForFieldName("arguments");
|
|
6125
|
+
const first = args?.namedChild(0);
|
|
6126
|
+
if (!first) return;
|
|
6127
|
+
const fnName = fn.type === "identifier" ? fn.text : fn.type === "member_expression" ? fn.childForFieldName("property")?.text ?? "" : "";
|
|
6128
|
+
if (fn.type === "identifier" && fnName === "fetch") {
|
|
6129
|
+
const opts = args?.namedChild(1);
|
|
6130
|
+
const method = opts && opts.type === "object" ? methodFromOptions(opts) ?? "GET" : "GET";
|
|
6131
|
+
push(first, method, node);
|
|
6132
|
+
return;
|
|
6133
|
+
}
|
|
6134
|
+
if (fn.type === "identifier" && fnName === "axios") {
|
|
6135
|
+
if (first.type === "object") {
|
|
6136
|
+
const urlNode = urlNodeFromConfig(first);
|
|
6137
|
+
if (urlNode) push(urlNode, methodFromOptions(first) ?? "GET", node);
|
|
6138
|
+
} else {
|
|
6139
|
+
const opts = args?.namedChild(1);
|
|
6140
|
+
const method = opts && opts.type === "object" ? methodFromOptions(opts) ?? "GET" : "GET";
|
|
6141
|
+
push(first, method, node);
|
|
6142
|
+
}
|
|
6143
|
+
return;
|
|
6144
|
+
}
|
|
6145
|
+
if (fn.type === "member_expression") {
|
|
6146
|
+
const obj = fn.childForFieldName("object");
|
|
6147
|
+
const objName = obj?.text ?? "";
|
|
6148
|
+
if (objName === "axios" && AXIOS_METHODS.has(fnName)) {
|
|
6149
|
+
if (fnName === "request" && first.type === "object") {
|
|
6150
|
+
const urlNode = urlNodeFromConfig(first);
|
|
6151
|
+
if (urlNode) push(urlNode, methodFromOptions(first) ?? "GET", node);
|
|
6152
|
+
} else {
|
|
6153
|
+
push(first, fnName.toUpperCase(), node);
|
|
6154
|
+
}
|
|
6155
|
+
return;
|
|
6156
|
+
}
|
|
6157
|
+
if ((objName === "http" || objName === "https") && (fnName === "request" || fnName === "get")) {
|
|
6158
|
+
const opts = args?.namedChild(1);
|
|
6159
|
+
const method = opts && opts.type === "object" ? methodFromOptions(opts) ?? (fnName === "get" ? "GET" : "GET") : "GET";
|
|
6160
|
+
push(first, method, node);
|
|
6161
|
+
return;
|
|
6162
|
+
}
|
|
6163
|
+
}
|
|
6164
|
+
});
|
|
6165
|
+
return out;
|
|
6166
|
+
}
|
|
6167
|
+
function buildRouteIndex(graph) {
|
|
6168
|
+
const index = /* @__PURE__ */ new Map();
|
|
6169
|
+
graph.forEachNode((_id, attrs) => {
|
|
6170
|
+
const node = attrs;
|
|
6171
|
+
if (node.type !== import_types14.NodeType.RouteNode) return;
|
|
6172
|
+
const route = attrs;
|
|
6173
|
+
const owner = (0, import_types14.serviceId)(route.service);
|
|
6174
|
+
const entry = {
|
|
6175
|
+
method: route.method.toUpperCase(),
|
|
6176
|
+
normalizedPath: normalizePathTemplate(route.pathTemplate),
|
|
6177
|
+
routeNodeId: route.id
|
|
6178
|
+
};
|
|
6179
|
+
const list = index.get(owner);
|
|
6180
|
+
if (list) list.push(entry);
|
|
6181
|
+
else index.set(owner, [entry]);
|
|
6182
|
+
});
|
|
6183
|
+
return index;
|
|
6184
|
+
}
|
|
6185
|
+
function findRoute(entries, method, normalizedPath) {
|
|
6186
|
+
return entries.find(
|
|
6187
|
+
(e) => e.normalizedPath === normalizedPath && (e.method === "ALL" || method === void 0 || e.method === method)
|
|
6188
|
+
);
|
|
6189
|
+
}
|
|
6190
|
+
async function addRouteCallEdges(graph, services) {
|
|
6191
|
+
const jsParser = makeJsParser4();
|
|
6192
|
+
const { knownHosts, hostToNodeId } = buildServiceHostIndex(services);
|
|
6193
|
+
const routeIndex = buildRouteIndex(graph);
|
|
6194
|
+
if (routeIndex.size === 0) return { nodesAdded: 0, edgesAdded: 0 };
|
|
6195
|
+
let nodesAdded = 0;
|
|
6196
|
+
let edgesAdded = 0;
|
|
6197
|
+
for (const service of services) {
|
|
6198
|
+
const files = await loadSourceFiles(service.dir);
|
|
6199
|
+
const seen = /* @__PURE__ */ new Set();
|
|
6200
|
+
for (const file of files) {
|
|
6201
|
+
if (isTestPath(file.path)) continue;
|
|
6202
|
+
if (!JS_CLIENT_EXTENSIONS.has(import_node_path26.default.extname(file.path))) continue;
|
|
6203
|
+
let sites;
|
|
6204
|
+
try {
|
|
6205
|
+
sites = clientCallSitesFromSource(file.content, jsParser, knownHosts);
|
|
6206
|
+
} catch (err) {
|
|
6207
|
+
recordExtractionError("route-match call extraction", file.path, err);
|
|
6208
|
+
continue;
|
|
6209
|
+
}
|
|
6210
|
+
if (sites.length === 0) continue;
|
|
6211
|
+
const relFile = toPosix2(import_node_path26.default.relative(service.dir, file.path));
|
|
6212
|
+
for (const site of sites) {
|
|
6213
|
+
const serverServiceId = hostToNodeId.get(site.host);
|
|
6214
|
+
if (!serverServiceId || serverServiceId === service.node.id) continue;
|
|
6215
|
+
const entries = routeIndex.get(serverServiceId);
|
|
6216
|
+
if (!entries) continue;
|
|
6217
|
+
const normalizedPath = normalizePathTemplate(site.pathTemplate);
|
|
6218
|
+
const match = findRoute(entries, site.method, normalizedPath);
|
|
6219
|
+
if (!match) continue;
|
|
6220
|
+
const dedupKey = `${relFile}|${match.routeNodeId}`;
|
|
6221
|
+
if (seen.has(dedupKey)) continue;
|
|
6222
|
+
seen.add(dedupKey);
|
|
6223
|
+
const { fileNodeId, nodesAdded: n, edgesAdded: e } = ensureFileNode(
|
|
6224
|
+
graph,
|
|
6225
|
+
service.pkg.name,
|
|
6226
|
+
service.node.id,
|
|
6227
|
+
relFile
|
|
6228
|
+
);
|
|
6229
|
+
nodesAdded += n;
|
|
6230
|
+
edgesAdded += e;
|
|
6231
|
+
const confidence = (0, import_types14.confidenceForExtracted)("verified-call-site");
|
|
6232
|
+
const ev = {
|
|
6233
|
+
file: relFile,
|
|
6234
|
+
line: site.line,
|
|
6235
|
+
snippet: site.snippet,
|
|
6236
|
+
method: site.method ?? match.method,
|
|
6237
|
+
pathTemplate: site.pathTemplate
|
|
6238
|
+
};
|
|
6239
|
+
if (!(0, import_types14.passesExtractedFloor)(confidence)) {
|
|
6240
|
+
noteExtractedDropped({
|
|
6241
|
+
source: fileNodeId,
|
|
6242
|
+
target: match.routeNodeId,
|
|
6243
|
+
type: import_types14.EdgeType.CALLS,
|
|
6244
|
+
confidence,
|
|
6245
|
+
confidenceKind: "verified-call-site",
|
|
6246
|
+
evidence: ev
|
|
6247
|
+
});
|
|
6248
|
+
continue;
|
|
6249
|
+
}
|
|
6250
|
+
const edgeId = (0, import_types5.extractedEdgeId)(fileNodeId, match.routeNodeId, import_types14.EdgeType.CALLS);
|
|
6251
|
+
if (!graph.hasEdge(edgeId)) {
|
|
6252
|
+
const edge = {
|
|
6253
|
+
id: edgeId,
|
|
6254
|
+
source: fileNodeId,
|
|
6255
|
+
target: match.routeNodeId,
|
|
6256
|
+
type: import_types14.EdgeType.CALLS,
|
|
6257
|
+
provenance: import_types14.Provenance.EXTRACTED,
|
|
6258
|
+
confidence,
|
|
6259
|
+
evidence: ev
|
|
6260
|
+
};
|
|
6261
|
+
graph.addEdgeWithKey(edgeId, fileNodeId, match.routeNodeId, edge);
|
|
6262
|
+
edgesAdded++;
|
|
6263
|
+
}
|
|
6264
|
+
}
|
|
6265
|
+
}
|
|
6266
|
+
}
|
|
6267
|
+
return { nodesAdded, edgesAdded };
|
|
6268
|
+
}
|
|
6269
|
+
|
|
5541
6270
|
// src/extract/calls/kafka.ts
|
|
5542
6271
|
init_cjs_shims();
|
|
5543
|
-
var
|
|
5544
|
-
var
|
|
6272
|
+
var import_node_path27 = __toESM(require("path"), 1);
|
|
6273
|
+
var import_types15 = require("@neat.is/types");
|
|
5545
6274
|
var PRODUCER_TOPIC_RE = /(?:producer|kafkaProducer)[\s\S]{0,40}?\.send\s*\(\s*\{[\s\S]{0,200}?topic\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
5546
6275
|
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
6276
|
function findAll(re, text) {
|
|
@@ -5562,7 +6291,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
|
|
|
5562
6291
|
seen.add(key);
|
|
5563
6292
|
const line = lineOf(file.content, topic);
|
|
5564
6293
|
out.push({
|
|
5565
|
-
infraId: (0,
|
|
6294
|
+
infraId: (0, import_types15.infraId)("kafka-topic", topic),
|
|
5566
6295
|
name: topic,
|
|
5567
6296
|
kind: "kafka-topic",
|
|
5568
6297
|
edgeType,
|
|
@@ -5571,7 +6300,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
|
|
|
5571
6300
|
// tier (ADR-066).
|
|
5572
6301
|
confidenceKind: "verified-call-site",
|
|
5573
6302
|
evidence: {
|
|
5574
|
-
file:
|
|
6303
|
+
file: import_node_path27.default.relative(serviceDir, file.path),
|
|
5575
6304
|
line,
|
|
5576
6305
|
snippet: snippet(file.content, line)
|
|
5577
6306
|
}
|
|
@@ -5584,8 +6313,8 @@ function kafkaEndpointsFromFile(file, serviceDir) {
|
|
|
5584
6313
|
|
|
5585
6314
|
// src/extract/calls/redis.ts
|
|
5586
6315
|
init_cjs_shims();
|
|
5587
|
-
var
|
|
5588
|
-
var
|
|
6316
|
+
var import_node_path28 = __toESM(require("path"), 1);
|
|
6317
|
+
var import_types16 = require("@neat.is/types");
|
|
5589
6318
|
var REDIS_URL_RE = /redis(?:s)?:\/\/(?:[^@'"`\s]+@)?([^:/'"`\s]+)(?::(\d+))?/g;
|
|
5590
6319
|
function redisEndpointsFromFile(file, serviceDir) {
|
|
5591
6320
|
const out = [];
|
|
@@ -5598,7 +6327,7 @@ function redisEndpointsFromFile(file, serviceDir) {
|
|
|
5598
6327
|
seen.add(host);
|
|
5599
6328
|
const line = lineOf(file.content, host);
|
|
5600
6329
|
out.push({
|
|
5601
|
-
infraId: (0,
|
|
6330
|
+
infraId: (0, import_types16.infraId)("redis", host),
|
|
5602
6331
|
name: host,
|
|
5603
6332
|
kind: "redis",
|
|
5604
6333
|
edgeType: "CALLS",
|
|
@@ -5607,7 +6336,7 @@ function redisEndpointsFromFile(file, serviceDir) {
|
|
|
5607
6336
|
// support tier (ADR-066).
|
|
5608
6337
|
confidenceKind: "url-with-structural-support",
|
|
5609
6338
|
evidence: {
|
|
5610
|
-
file:
|
|
6339
|
+
file: import_node_path28.default.relative(serviceDir, file.path),
|
|
5611
6340
|
line,
|
|
5612
6341
|
snippet: snippet(file.content, line)
|
|
5613
6342
|
}
|
|
@@ -5618,8 +6347,8 @@ function redisEndpointsFromFile(file, serviceDir) {
|
|
|
5618
6347
|
|
|
5619
6348
|
// src/extract/calls/aws.ts
|
|
5620
6349
|
init_cjs_shims();
|
|
5621
|
-
var
|
|
5622
|
-
var
|
|
6350
|
+
var import_node_path29 = __toESM(require("path"), 1);
|
|
6351
|
+
var import_types17 = require("@neat.is/types");
|
|
5623
6352
|
var S3_BUCKET_RE = /Bucket\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
5624
6353
|
var DYNAMO_TABLE_RE = /TableName\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
5625
6354
|
function hasMarker(text, markers) {
|
|
@@ -5643,7 +6372,7 @@ function awsEndpointsFromFile(file, serviceDir) {
|
|
|
5643
6372
|
seen.add(key);
|
|
5644
6373
|
const line = lineOf(file.content, name);
|
|
5645
6374
|
out.push({
|
|
5646
|
-
infraId: (0,
|
|
6375
|
+
infraId: (0, import_types17.infraId)(kind, name),
|
|
5647
6376
|
name,
|
|
5648
6377
|
kind,
|
|
5649
6378
|
edgeType: "CALLS",
|
|
@@ -5652,7 +6381,7 @@ function awsEndpointsFromFile(file, serviceDir) {
|
|
|
5652
6381
|
// (ADR-066).
|
|
5653
6382
|
confidenceKind: "verified-call-site",
|
|
5654
6383
|
evidence: {
|
|
5655
|
-
file:
|
|
6384
|
+
file: import_node_path29.default.relative(serviceDir, file.path),
|
|
5656
6385
|
line,
|
|
5657
6386
|
snippet: snippet(file.content, line)
|
|
5658
6387
|
}
|
|
@@ -5677,8 +6406,8 @@ function awsEndpointsFromFile(file, serviceDir) {
|
|
|
5677
6406
|
|
|
5678
6407
|
// src/extract/calls/grpc.ts
|
|
5679
6408
|
init_cjs_shims();
|
|
5680
|
-
var
|
|
5681
|
-
var
|
|
6409
|
+
var import_node_path30 = __toESM(require("path"), 1);
|
|
6410
|
+
var import_types18 = require("@neat.is/types");
|
|
5682
6411
|
var GRPC_CLIENT_RE = /new\s+([A-Z][A-Za-z0-9_]*)Client\s*\(\s*['"`]?([^,'"`)]+)?/g;
|
|
5683
6412
|
var AWS_SDK_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@aws-sdk\/client-([a-z0-9-]+)['"`]/g;
|
|
5684
6413
|
var GRPC_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@grpc\/grpc-js['"`]|_grpc_pb['"`]/;
|
|
@@ -5727,7 +6456,7 @@ function grpcEndpointsFromFile(file, serviceDir) {
|
|
|
5727
6456
|
const { kind } = classified;
|
|
5728
6457
|
const line = lineOf(file.content, m[0]);
|
|
5729
6458
|
out.push({
|
|
5730
|
-
infraId: (0,
|
|
6459
|
+
infraId: (0, import_types18.infraId)(kind, name),
|
|
5731
6460
|
name,
|
|
5732
6461
|
kind,
|
|
5733
6462
|
edgeType: "CALLS",
|
|
@@ -5736,7 +6465,7 @@ function grpcEndpointsFromFile(file, serviceDir) {
|
|
|
5736
6465
|
// tier (ADR-066).
|
|
5737
6466
|
confidenceKind: "verified-call-site",
|
|
5738
6467
|
evidence: {
|
|
5739
|
-
file:
|
|
6468
|
+
file: import_node_path30.default.relative(serviceDir, file.path),
|
|
5740
6469
|
line,
|
|
5741
6470
|
snippet: snippet(file.content, line)
|
|
5742
6471
|
}
|
|
@@ -5747,8 +6476,8 @@ function grpcEndpointsFromFile(file, serviceDir) {
|
|
|
5747
6476
|
|
|
5748
6477
|
// src/extract/calls/supabase.ts
|
|
5749
6478
|
init_cjs_shims();
|
|
5750
|
-
var
|
|
5751
|
-
var
|
|
6479
|
+
var import_node_path31 = __toESM(require("path"), 1);
|
|
6480
|
+
var import_types19 = require("@neat.is/types");
|
|
5752
6481
|
var SUPABASE_JS_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@supabase\/supabase-js['"`]/;
|
|
5753
6482
|
var SUPABASE_SSR_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@supabase\/ssr['"`]/;
|
|
5754
6483
|
var SUPABASE_CLIENT_RE = /\b(createClient|createServerClient|createBrowserClient)\s*\(\s*(?:['"`]([^'"`]*)['"`])?/g;
|
|
@@ -5786,7 +6515,7 @@ function supabaseEndpointsFromFile(file, serviceDir) {
|
|
|
5786
6515
|
seen.add(name);
|
|
5787
6516
|
const line = lineOf(file.content, m[0]);
|
|
5788
6517
|
out.push({
|
|
5789
|
-
infraId: (0,
|
|
6518
|
+
infraId: (0, import_types19.infraId)("supabase", name),
|
|
5790
6519
|
name,
|
|
5791
6520
|
kind: "supabase",
|
|
5792
6521
|
edgeType: "CALLS",
|
|
@@ -5796,7 +6525,7 @@ function supabaseEndpointsFromFile(file, serviceDir) {
|
|
|
5796
6525
|
// tier (ADR-066), the same grade aws.ts / grpc.ts emit at.
|
|
5797
6526
|
confidenceKind: "verified-call-site",
|
|
5798
6527
|
evidence: {
|
|
5799
|
-
file:
|
|
6528
|
+
file: import_node_path31.default.relative(serviceDir, file.path),
|
|
5800
6529
|
line,
|
|
5801
6530
|
snippet: snippet(file.content, line)
|
|
5802
6531
|
}
|
|
@@ -5809,11 +6538,11 @@ function supabaseEndpointsFromFile(file, serviceDir) {
|
|
|
5809
6538
|
function edgeTypeFromEndpoint(ep) {
|
|
5810
6539
|
switch (ep.edgeType) {
|
|
5811
6540
|
case "PUBLISHES_TO":
|
|
5812
|
-
return
|
|
6541
|
+
return import_types20.EdgeType.PUBLISHES_TO;
|
|
5813
6542
|
case "CONSUMES_FROM":
|
|
5814
|
-
return
|
|
6543
|
+
return import_types20.EdgeType.CONSUMES_FROM;
|
|
5815
6544
|
default:
|
|
5816
|
-
return
|
|
6545
|
+
return import_types20.EdgeType.CALLS;
|
|
5817
6546
|
}
|
|
5818
6547
|
}
|
|
5819
6548
|
function isAwsKind(kind) {
|
|
@@ -5841,7 +6570,7 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
5841
6570
|
if (!graph.hasNode(ep.infraId)) {
|
|
5842
6571
|
const node = {
|
|
5843
6572
|
id: ep.infraId,
|
|
5844
|
-
type:
|
|
6573
|
+
type: import_types20.NodeType.InfraNode,
|
|
5845
6574
|
name: ep.name,
|
|
5846
6575
|
// #238 — `aws-*` covers AWS-SDK client kinds (aws-s3, aws-dynamodb,
|
|
5847
6576
|
// aws-cognito-identity-provider, …); `s3-` / `dynamodb-` cover the
|
|
@@ -5853,7 +6582,7 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
5853
6582
|
nodesAdded++;
|
|
5854
6583
|
}
|
|
5855
6584
|
const edgeType = edgeTypeFromEndpoint(ep);
|
|
5856
|
-
const confidence = (0,
|
|
6585
|
+
const confidence = (0, import_types20.confidenceForExtracted)(ep.confidenceKind);
|
|
5857
6586
|
const relFile = toPosix2(ep.evidence.file);
|
|
5858
6587
|
const { fileNodeId, nodesAdded: n, edgesAdded: e } = ensureFileNode(
|
|
5859
6588
|
graph,
|
|
@@ -5863,7 +6592,7 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
5863
6592
|
);
|
|
5864
6593
|
nodesAdded += n;
|
|
5865
6594
|
edgesAdded += e;
|
|
5866
|
-
if (!(0,
|
|
6595
|
+
if (!(0, import_types20.passesExtractedFloor)(confidence)) {
|
|
5867
6596
|
noteExtractedDropped({
|
|
5868
6597
|
source: fileNodeId,
|
|
5869
6598
|
target: ep.infraId,
|
|
@@ -5883,7 +6612,7 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
5883
6612
|
source: fileNodeId,
|
|
5884
6613
|
target: ep.infraId,
|
|
5885
6614
|
type: edgeType,
|
|
5886
|
-
provenance:
|
|
6615
|
+
provenance: import_types20.Provenance.EXTRACTED,
|
|
5887
6616
|
confidence,
|
|
5888
6617
|
evidence: ep.evidence
|
|
5889
6618
|
};
|
|
@@ -5897,9 +6626,10 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
5897
6626
|
async function addCallEdges(graph, services) {
|
|
5898
6627
|
const http = await addHttpCallEdges(graph, services);
|
|
5899
6628
|
const ext = await addExternalEndpointEdges(graph, services);
|
|
6629
|
+
const routes = await addRouteCallEdges(graph, services);
|
|
5900
6630
|
return {
|
|
5901
|
-
nodesAdded: http.nodesAdded + ext.nodesAdded,
|
|
5902
|
-
edgesAdded: http.edgesAdded + ext.edgesAdded
|
|
6631
|
+
nodesAdded: http.nodesAdded + ext.nodesAdded + routes.nodesAdded,
|
|
6632
|
+
edgesAdded: http.edgesAdded + ext.edgesAdded + routes.edgesAdded
|
|
5903
6633
|
};
|
|
5904
6634
|
}
|
|
5905
6635
|
|
|
@@ -5908,16 +6638,16 @@ init_cjs_shims();
|
|
|
5908
6638
|
|
|
5909
6639
|
// src/extract/infra/docker-compose.ts
|
|
5910
6640
|
init_cjs_shims();
|
|
5911
|
-
var
|
|
5912
|
-
var
|
|
6641
|
+
var import_node_path32 = __toESM(require("path"), 1);
|
|
6642
|
+
var import_types22 = require("@neat.is/types");
|
|
5913
6643
|
|
|
5914
6644
|
// src/extract/infra/shared.ts
|
|
5915
6645
|
init_cjs_shims();
|
|
5916
|
-
var
|
|
6646
|
+
var import_types21 = require("@neat.is/types");
|
|
5917
6647
|
function makeInfraNode(kind, name, provider = "self", extras) {
|
|
5918
6648
|
return {
|
|
5919
|
-
id: (0,
|
|
5920
|
-
type:
|
|
6649
|
+
id: (0, import_types21.infraId)(kind, name),
|
|
6650
|
+
type: import_types21.NodeType.InfraNode,
|
|
5921
6651
|
name,
|
|
5922
6652
|
provider,
|
|
5923
6653
|
kind,
|
|
@@ -5946,7 +6676,7 @@ function dependsOnList(value) {
|
|
|
5946
6676
|
}
|
|
5947
6677
|
function serviceNameToServiceNode(name, services) {
|
|
5948
6678
|
for (const s of services) {
|
|
5949
|
-
if (s.node.name === name ||
|
|
6679
|
+
if (s.node.name === name || import_node_path32.default.basename(s.dir) === name) return s.node.id;
|
|
5950
6680
|
}
|
|
5951
6681
|
return null;
|
|
5952
6682
|
}
|
|
@@ -5955,7 +6685,7 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
5955
6685
|
let edgesAdded = 0;
|
|
5956
6686
|
let composePath = null;
|
|
5957
6687
|
for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
|
|
5958
|
-
const abs =
|
|
6688
|
+
const abs = import_node_path32.default.join(scanPath, name);
|
|
5959
6689
|
if (await exists2(abs)) {
|
|
5960
6690
|
composePath = abs;
|
|
5961
6691
|
break;
|
|
@@ -5968,13 +6698,13 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
5968
6698
|
} catch (err) {
|
|
5969
6699
|
recordExtractionError(
|
|
5970
6700
|
"infra docker-compose",
|
|
5971
|
-
|
|
6701
|
+
import_node_path32.default.relative(scanPath, composePath),
|
|
5972
6702
|
err
|
|
5973
6703
|
);
|
|
5974
6704
|
return { nodesAdded, edgesAdded };
|
|
5975
6705
|
}
|
|
5976
6706
|
if (!compose?.services) return { nodesAdded, edgesAdded };
|
|
5977
|
-
const evidenceFile =
|
|
6707
|
+
const evidenceFile = import_node_path32.default.relative(scanPath, composePath).split(import_node_path32.default.sep).join("/");
|
|
5978
6708
|
const composeNameToNodeId = /* @__PURE__ */ new Map();
|
|
5979
6709
|
for (const [composeName, svc] of Object.entries(compose.services)) {
|
|
5980
6710
|
const matchedServiceId = serviceNameToServiceNode(composeName, services);
|
|
@@ -5996,15 +6726,15 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
5996
6726
|
for (const dep of dependsOnList(svc.depends_on)) {
|
|
5997
6727
|
const targetId = composeNameToNodeId.get(dep);
|
|
5998
6728
|
if (!targetId) continue;
|
|
5999
|
-
const edgeId = (0, import_types5.extractedEdgeId)(sourceId, targetId,
|
|
6729
|
+
const edgeId = (0, import_types5.extractedEdgeId)(sourceId, targetId, import_types22.EdgeType.DEPENDS_ON);
|
|
6000
6730
|
if (graph.hasEdge(edgeId)) continue;
|
|
6001
6731
|
const edge = {
|
|
6002
6732
|
id: edgeId,
|
|
6003
6733
|
source: sourceId,
|
|
6004
6734
|
target: targetId,
|
|
6005
|
-
type:
|
|
6006
|
-
provenance:
|
|
6007
|
-
confidence: (0,
|
|
6735
|
+
type: import_types22.EdgeType.DEPENDS_ON,
|
|
6736
|
+
provenance: import_types22.Provenance.EXTRACTED,
|
|
6737
|
+
confidence: (0, import_types22.confidenceForExtracted)("structural"),
|
|
6008
6738
|
evidence: { file: evidenceFile }
|
|
6009
6739
|
};
|
|
6010
6740
|
graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
|
|
@@ -6016,9 +6746,9 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
6016
6746
|
|
|
6017
6747
|
// src/extract/infra/dockerfile.ts
|
|
6018
6748
|
init_cjs_shims();
|
|
6019
|
-
var
|
|
6749
|
+
var import_node_path33 = __toESM(require("path"), 1);
|
|
6020
6750
|
var import_node_fs17 = require("fs");
|
|
6021
|
-
var
|
|
6751
|
+
var import_types23 = require("@neat.is/types");
|
|
6022
6752
|
function readDockerfile(content) {
|
|
6023
6753
|
let image = null;
|
|
6024
6754
|
const ports = [];
|
|
@@ -6047,7 +6777,7 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
6047
6777
|
let nodesAdded = 0;
|
|
6048
6778
|
let edgesAdded = 0;
|
|
6049
6779
|
for (const service of services) {
|
|
6050
|
-
const dockerfilePath =
|
|
6780
|
+
const dockerfilePath = import_node_path33.default.join(service.dir, "Dockerfile");
|
|
6051
6781
|
if (!await exists2(dockerfilePath)) continue;
|
|
6052
6782
|
let content;
|
|
6053
6783
|
try {
|
|
@@ -6055,7 +6785,7 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
6055
6785
|
} catch (err) {
|
|
6056
6786
|
recordExtractionError(
|
|
6057
6787
|
"infra dockerfile",
|
|
6058
|
-
|
|
6788
|
+
import_node_path33.default.relative(scanPath, dockerfilePath),
|
|
6059
6789
|
err
|
|
6060
6790
|
);
|
|
6061
6791
|
continue;
|
|
@@ -6067,8 +6797,8 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
6067
6797
|
graph.addNode(node.id, node);
|
|
6068
6798
|
nodesAdded++;
|
|
6069
6799
|
}
|
|
6070
|
-
const relDockerfile = toPosix2(
|
|
6071
|
-
const evidenceFile = toPosix2(
|
|
6800
|
+
const relDockerfile = toPosix2(import_node_path33.default.relative(service.dir, dockerfilePath));
|
|
6801
|
+
const evidenceFile = toPosix2(import_node_path33.default.relative(scanPath, dockerfilePath));
|
|
6072
6802
|
const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
|
|
6073
6803
|
graph,
|
|
6074
6804
|
service.pkg.name,
|
|
@@ -6077,15 +6807,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
6077
6807
|
);
|
|
6078
6808
|
nodesAdded += fn;
|
|
6079
6809
|
edgesAdded += fe;
|
|
6080
|
-
const edgeId = (0, import_types5.extractedEdgeId)(fileNodeId, node.id,
|
|
6810
|
+
const edgeId = (0, import_types5.extractedEdgeId)(fileNodeId, node.id, import_types23.EdgeType.RUNS_ON);
|
|
6081
6811
|
if (!graph.hasEdge(edgeId)) {
|
|
6082
6812
|
const edge = {
|
|
6083
6813
|
id: edgeId,
|
|
6084
6814
|
source: fileNodeId,
|
|
6085
6815
|
target: node.id,
|
|
6086
|
-
type:
|
|
6087
|
-
provenance:
|
|
6088
|
-
confidence: (0,
|
|
6816
|
+
type: import_types23.EdgeType.RUNS_ON,
|
|
6817
|
+
provenance: import_types23.Provenance.EXTRACTED,
|
|
6818
|
+
confidence: (0, import_types23.confidenceForExtracted)("structural"),
|
|
6089
6819
|
evidence: {
|
|
6090
6820
|
file: evidenceFile,
|
|
6091
6821
|
...facts.entrypoint ? { snippet: facts.entrypoint.slice(0, 120) } : {}
|
|
@@ -6100,15 +6830,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
6100
6830
|
graph.addNode(portNode.id, portNode);
|
|
6101
6831
|
nodesAdded++;
|
|
6102
6832
|
}
|
|
6103
|
-
const portEdgeId = (0, import_types5.extractedEdgeId)(fileNodeId, portNode.id,
|
|
6833
|
+
const portEdgeId = (0, import_types5.extractedEdgeId)(fileNodeId, portNode.id, import_types23.EdgeType.CONNECTS_TO);
|
|
6104
6834
|
if (graph.hasEdge(portEdgeId)) continue;
|
|
6105
6835
|
const portEdge = {
|
|
6106
6836
|
id: portEdgeId,
|
|
6107
6837
|
source: fileNodeId,
|
|
6108
6838
|
target: portNode.id,
|
|
6109
|
-
type:
|
|
6110
|
-
provenance:
|
|
6111
|
-
confidence: (0,
|
|
6839
|
+
type: import_types23.EdgeType.CONNECTS_TO,
|
|
6840
|
+
provenance: import_types23.Provenance.EXTRACTED,
|
|
6841
|
+
confidence: (0, import_types23.confidenceForExtracted)("structural"),
|
|
6112
6842
|
evidence: { file: evidenceFile, snippet: `EXPOSE ${port}` }
|
|
6113
6843
|
};
|
|
6114
6844
|
graph.addEdgeWithKey(portEdgeId, portEdge.source, portEdge.target, portEdge);
|
|
@@ -6121,8 +6851,8 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
6121
6851
|
// src/extract/infra/terraform.ts
|
|
6122
6852
|
init_cjs_shims();
|
|
6123
6853
|
var import_node_fs18 = require("fs");
|
|
6124
|
-
var
|
|
6125
|
-
var
|
|
6854
|
+
var import_node_path34 = __toESM(require("path"), 1);
|
|
6855
|
+
var import_types24 = require("@neat.is/types");
|
|
6126
6856
|
var RESOURCE_RE = /resource\s+"(aws_[A-Za-z0-9_]+)"\s+"([A-Za-z0-9_-]+)"/g;
|
|
6127
6857
|
var REFERENCE_RE = /(?<![\w.])(aws_[A-Za-z0-9_]+)\.([A-Za-z0-9_-]+)/g;
|
|
6128
6858
|
async function walkTfFiles(start, depth = 0, max = 5) {
|
|
@@ -6132,11 +6862,11 @@ async function walkTfFiles(start, depth = 0, max = 5) {
|
|
|
6132
6862
|
for (const entry of entries) {
|
|
6133
6863
|
if (entry.isDirectory()) {
|
|
6134
6864
|
if (IGNORED_DIRS.has(entry.name) || entry.name === ".terraform") continue;
|
|
6135
|
-
const child =
|
|
6865
|
+
const child = import_node_path34.default.join(start, entry.name);
|
|
6136
6866
|
if (await isPythonVenvDir(child)) continue;
|
|
6137
6867
|
out.push(...await walkTfFiles(child, depth + 1, max));
|
|
6138
6868
|
} else if (entry.isFile() && entry.name.endsWith(".tf")) {
|
|
6139
|
-
out.push(
|
|
6869
|
+
out.push(import_node_path34.default.join(start, entry.name));
|
|
6140
6870
|
}
|
|
6141
6871
|
}
|
|
6142
6872
|
return out;
|
|
@@ -6168,7 +6898,7 @@ async function addTerraformResources(graph, scanPath) {
|
|
|
6168
6898
|
const files = await walkTfFiles(scanPath);
|
|
6169
6899
|
for (const file of files) {
|
|
6170
6900
|
const content = await import_node_fs18.promises.readFile(file, "utf8");
|
|
6171
|
-
const evidenceFile = toPosix2(
|
|
6901
|
+
const evidenceFile = toPosix2(import_node_path34.default.relative(scanPath, file));
|
|
6172
6902
|
const resources = [];
|
|
6173
6903
|
const byKey = /* @__PURE__ */ new Map();
|
|
6174
6904
|
RESOURCE_RE.lastIndex = 0;
|
|
@@ -6203,16 +6933,16 @@ async function addTerraformResources(graph, scanPath) {
|
|
|
6203
6933
|
if (!target) continue;
|
|
6204
6934
|
if (seen.has(target.nodeId)) continue;
|
|
6205
6935
|
seen.add(target.nodeId);
|
|
6206
|
-
const edgeId = (0, import_types5.extractedEdgeId)(resource.nodeId, target.nodeId,
|
|
6936
|
+
const edgeId = (0, import_types5.extractedEdgeId)(resource.nodeId, target.nodeId, import_types24.EdgeType.DEPENDS_ON);
|
|
6207
6937
|
if (graph.hasEdge(edgeId)) continue;
|
|
6208
6938
|
const line = lineAt(content, resource.bodyOffset + ref.index);
|
|
6209
6939
|
const edge = {
|
|
6210
6940
|
id: edgeId,
|
|
6211
6941
|
source: resource.nodeId,
|
|
6212
6942
|
target: target.nodeId,
|
|
6213
|
-
type:
|
|
6214
|
-
provenance:
|
|
6215
|
-
confidence: (0,
|
|
6943
|
+
type: import_types24.EdgeType.DEPENDS_ON,
|
|
6944
|
+
provenance: import_types24.Provenance.EXTRACTED,
|
|
6945
|
+
confidence: (0, import_types24.confidenceForExtracted)("structural"),
|
|
6216
6946
|
evidence: { file: evidenceFile, line, snippet: key }
|
|
6217
6947
|
};
|
|
6218
6948
|
graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
|
|
@@ -6226,7 +6956,7 @@ async function addTerraformResources(graph, scanPath) {
|
|
|
6226
6956
|
// src/extract/infra/k8s.ts
|
|
6227
6957
|
init_cjs_shims();
|
|
6228
6958
|
var import_node_fs19 = require("fs");
|
|
6229
|
-
var
|
|
6959
|
+
var import_node_path35 = __toESM(require("path"), 1);
|
|
6230
6960
|
var import_yaml3 = require("yaml");
|
|
6231
6961
|
var K8S_KIND_TO_INFRA_KIND = {
|
|
6232
6962
|
Service: "k8s-service",
|
|
@@ -6244,11 +6974,11 @@ async function walkYamlFiles2(start, depth = 0, max = 5) {
|
|
|
6244
6974
|
for (const entry of entries) {
|
|
6245
6975
|
if (entry.isDirectory()) {
|
|
6246
6976
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
6247
|
-
const child =
|
|
6977
|
+
const child = import_node_path35.default.join(start, entry.name);
|
|
6248
6978
|
if (await isPythonVenvDir(child)) continue;
|
|
6249
6979
|
out.push(...await walkYamlFiles2(child, depth + 1, max));
|
|
6250
|
-
} else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(
|
|
6251
|
-
out.push(
|
|
6980
|
+
} else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path35.default.extname(entry.name))) {
|
|
6981
|
+
out.push(import_node_path35.default.join(start, entry.name));
|
|
6252
6982
|
}
|
|
6253
6983
|
}
|
|
6254
6984
|
return out;
|
|
@@ -6292,17 +7022,17 @@ async function addInfra(graph, scanPath, services) {
|
|
|
6292
7022
|
}
|
|
6293
7023
|
|
|
6294
7024
|
// src/extract/index.ts
|
|
6295
|
-
var
|
|
7025
|
+
var import_node_path37 = __toESM(require("path"), 1);
|
|
6296
7026
|
|
|
6297
7027
|
// src/extract/retire.ts
|
|
6298
7028
|
init_cjs_shims();
|
|
6299
7029
|
var import_node_fs20 = require("fs");
|
|
6300
|
-
var
|
|
6301
|
-
var
|
|
7030
|
+
var import_node_path36 = __toESM(require("path"), 1);
|
|
7031
|
+
var import_types25 = require("@neat.is/types");
|
|
6302
7032
|
function dropOrphanedFileNodes(graph) {
|
|
6303
7033
|
const orphans = [];
|
|
6304
7034
|
graph.forEachNode((id, attrs) => {
|
|
6305
|
-
if (attrs.type !==
|
|
7035
|
+
if (attrs.type !== import_types25.NodeType.FileNode) return;
|
|
6306
7036
|
if (graph.inboundEdges(id).length === 0 && graph.outboundEdges(id).length === 0) {
|
|
6307
7037
|
orphans.push(id);
|
|
6308
7038
|
}
|
|
@@ -6315,14 +7045,14 @@ function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = []) {
|
|
|
6315
7045
|
const bases = [scanPath, ...serviceDirs];
|
|
6316
7046
|
graph.forEachEdge((id, attrs) => {
|
|
6317
7047
|
const edge = attrs;
|
|
6318
|
-
if (edge.provenance !==
|
|
7048
|
+
if (edge.provenance !== import_types25.Provenance.EXTRACTED) return;
|
|
6319
7049
|
const evidenceFile = edge.evidence?.file;
|
|
6320
7050
|
if (!evidenceFile) return;
|
|
6321
|
-
if (
|
|
7051
|
+
if (import_node_path36.default.isAbsolute(evidenceFile)) {
|
|
6322
7052
|
if (!(0, import_node_fs20.existsSync)(evidenceFile)) toDrop.push(id);
|
|
6323
7053
|
return;
|
|
6324
7054
|
}
|
|
6325
|
-
const found = bases.some((base) => (0, import_node_fs20.existsSync)(
|
|
7055
|
+
const found = bases.some((base) => (0, import_node_fs20.existsSync)(import_node_path36.default.join(base, evidenceFile)));
|
|
6326
7056
|
if (!found) toDrop.push(id);
|
|
6327
7057
|
});
|
|
6328
7058
|
for (const id of toDrop) graph.dropEdge(id);
|
|
@@ -6341,6 +7071,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
6341
7071
|
const importGraph = await addImports(graph, services);
|
|
6342
7072
|
const phase2 = await addDatabasesAndCompat(graph, services, scanPath);
|
|
6343
7073
|
const phase3 = await addConfigNodes(graph, services, scanPath);
|
|
7074
|
+
const routePhase = await addRoutes(graph, services);
|
|
6344
7075
|
const phase4 = await addCallEdges(graph, services);
|
|
6345
7076
|
const phase5 = await addInfra(graph, scanPath, services);
|
|
6346
7077
|
const ghostsRetired = retireExtractedEdgesByMissingFile(
|
|
@@ -6362,7 +7093,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
6362
7093
|
}
|
|
6363
7094
|
const droppedEntries = drainDroppedExtracted();
|
|
6364
7095
|
if (isRejectedLogEnabled() && opts.errorsPath && droppedEntries.length > 0) {
|
|
6365
|
-
const rejectedPath =
|
|
7096
|
+
const rejectedPath = import_node_path37.default.join(import_node_path37.default.dirname(opts.errorsPath), "rejected.ndjson");
|
|
6366
7097
|
try {
|
|
6367
7098
|
await writeRejectedExtracted(droppedEntries, rejectedPath);
|
|
6368
7099
|
} catch (err) {
|
|
@@ -6372,8 +7103,8 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
6372
7103
|
}
|
|
6373
7104
|
}
|
|
6374
7105
|
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,
|
|
7106
|
+
nodesAdded: phase1Nodes + fileEnum.nodesAdded + importGraph.nodesAdded + phase2.nodesAdded + phase3.nodesAdded + routePhase.nodesAdded + phase4.nodesAdded + phase5.nodesAdded,
|
|
7107
|
+
edgesAdded: fileEnum.edgesAdded + importGraph.edgesAdded + phase2.edgesAdded + phase3.edgesAdded + routePhase.edgesAdded + phase4.edgesAdded + phase5.edgesAdded,
|
|
6377
7108
|
frontiersPromoted,
|
|
6378
7109
|
extractionErrors: errorEntries.length,
|
|
6379
7110
|
errorEntries,
|
|
@@ -6474,8 +7205,8 @@ function canonicalJson(value) {
|
|
|
6474
7205
|
// src/persist.ts
|
|
6475
7206
|
init_cjs_shims();
|
|
6476
7207
|
var import_node_fs22 = require("fs");
|
|
6477
|
-
var
|
|
6478
|
-
var
|
|
7208
|
+
var import_node_path38 = __toESM(require("path"), 1);
|
|
7209
|
+
var import_types26 = require("@neat.is/types");
|
|
6479
7210
|
var SCHEMA_VERSION = 4;
|
|
6480
7211
|
function migrateV1ToV2(payload) {
|
|
6481
7212
|
const nodes = payload.graph.nodes;
|
|
@@ -6497,12 +7228,12 @@ function migrateV2ToV3(payload) {
|
|
|
6497
7228
|
for (const edge of edges) {
|
|
6498
7229
|
const attrs = edge.attributes;
|
|
6499
7230
|
if (!attrs || attrs.provenance !== "FRONTIER") continue;
|
|
6500
|
-
attrs.provenance =
|
|
7231
|
+
attrs.provenance = import_types26.Provenance.OBSERVED;
|
|
6501
7232
|
const type = typeof attrs.type === "string" ? attrs.type : void 0;
|
|
6502
7233
|
const source = typeof attrs.source === "string" ? attrs.source : void 0;
|
|
6503
7234
|
const target = typeof attrs.target === "string" ? attrs.target : void 0;
|
|
6504
7235
|
if (type && source && target) {
|
|
6505
|
-
const newId = (0,
|
|
7236
|
+
const newId = (0, import_types26.observedEdgeId)(source, target, type);
|
|
6506
7237
|
attrs.id = newId;
|
|
6507
7238
|
if (edge.key) edge.key = newId;
|
|
6508
7239
|
}
|
|
@@ -6511,7 +7242,7 @@ function migrateV2ToV3(payload) {
|
|
|
6511
7242
|
return { ...payload, schemaVersion: 3 };
|
|
6512
7243
|
}
|
|
6513
7244
|
async function ensureDir(filePath) {
|
|
6514
|
-
await import_node_fs22.promises.mkdir(
|
|
7245
|
+
await import_node_fs22.promises.mkdir(import_node_path38.default.dirname(filePath), { recursive: true });
|
|
6515
7246
|
}
|
|
6516
7247
|
async function saveGraphToDisk(graph, outPath) {
|
|
6517
7248
|
await ensureDir(outPath);
|
|
@@ -6592,23 +7323,23 @@ function startPersistLoop(graph, outPath, opts = {}) {
|
|
|
6592
7323
|
|
|
6593
7324
|
// src/projects.ts
|
|
6594
7325
|
init_cjs_shims();
|
|
6595
|
-
var
|
|
7326
|
+
var import_node_path39 = __toESM(require("path"), 1);
|
|
6596
7327
|
function pathsForProject(project, baseDir) {
|
|
6597
7328
|
if (project === DEFAULT_PROJECT) {
|
|
6598
7329
|
return {
|
|
6599
|
-
snapshotPath:
|
|
6600
|
-
errorsPath:
|
|
6601
|
-
staleEventsPath:
|
|
6602
|
-
embeddingsCachePath:
|
|
6603
|
-
policyViolationsPath:
|
|
7330
|
+
snapshotPath: import_node_path39.default.join(baseDir, "graph.json"),
|
|
7331
|
+
errorsPath: import_node_path39.default.join(baseDir, "errors.ndjson"),
|
|
7332
|
+
staleEventsPath: import_node_path39.default.join(baseDir, "stale-events.ndjson"),
|
|
7333
|
+
embeddingsCachePath: import_node_path39.default.join(baseDir, "embeddings.json"),
|
|
7334
|
+
policyViolationsPath: import_node_path39.default.join(baseDir, "policy-violations.ndjson")
|
|
6604
7335
|
};
|
|
6605
7336
|
}
|
|
6606
7337
|
return {
|
|
6607
|
-
snapshotPath:
|
|
6608
|
-
errorsPath:
|
|
6609
|
-
staleEventsPath:
|
|
6610
|
-
embeddingsCachePath:
|
|
6611
|
-
policyViolationsPath:
|
|
7338
|
+
snapshotPath: import_node_path39.default.join(baseDir, `${project}.json`),
|
|
7339
|
+
errorsPath: import_node_path39.default.join(baseDir, `errors.${project}.ndjson`),
|
|
7340
|
+
staleEventsPath: import_node_path39.default.join(baseDir, `stale-events.${project}.ndjson`),
|
|
7341
|
+
embeddingsCachePath: import_node_path39.default.join(baseDir, `embeddings.${project}.json`),
|
|
7342
|
+
policyViolationsPath: import_node_path39.default.join(baseDir, `policy-violations.${project}.ndjson`)
|
|
6612
7343
|
};
|
|
6613
7344
|
}
|
|
6614
7345
|
var Projects = class {
|
|
@@ -6650,15 +7381,15 @@ function parseExtraProjects(raw) {
|
|
|
6650
7381
|
init_cjs_shims();
|
|
6651
7382
|
var import_node_fs23 = require("fs");
|
|
6652
7383
|
var import_node_os3 = __toESM(require("os"), 1);
|
|
6653
|
-
var
|
|
6654
|
-
var
|
|
7384
|
+
var import_node_path40 = __toESM(require("path"), 1);
|
|
7385
|
+
var import_types27 = require("@neat.is/types");
|
|
6655
7386
|
function neatHome() {
|
|
6656
7387
|
const override = process.env.NEAT_HOME;
|
|
6657
|
-
if (override && override.length > 0) return
|
|
6658
|
-
return
|
|
7388
|
+
if (override && override.length > 0) return import_node_path40.default.resolve(override);
|
|
7389
|
+
return import_node_path40.default.join(import_node_os3.default.homedir(), ".neat");
|
|
6659
7390
|
}
|
|
6660
7391
|
function registryPath() {
|
|
6661
|
-
return
|
|
7392
|
+
return import_node_path40.default.join(neatHome(), "projects.json");
|
|
6662
7393
|
}
|
|
6663
7394
|
async function readRegistry() {
|
|
6664
7395
|
const file = registryPath();
|
|
@@ -6672,7 +7403,7 @@ async function readRegistry() {
|
|
|
6672
7403
|
throw err;
|
|
6673
7404
|
}
|
|
6674
7405
|
const parsed = JSON.parse(raw);
|
|
6675
|
-
return
|
|
7406
|
+
return import_types27.RegistryFileSchema.parse(parsed);
|
|
6676
7407
|
}
|
|
6677
7408
|
async function getProject(name) {
|
|
6678
7409
|
const reg = await readRegistry();
|
|
@@ -6893,11 +7624,11 @@ function registerRoutes(scope, ctx) {
|
|
|
6893
7624
|
const candidates = req.query.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
6894
7625
|
const parsed = [];
|
|
6895
7626
|
for (const c of candidates) {
|
|
6896
|
-
const r =
|
|
7627
|
+
const r = import_types28.DivergenceTypeSchema.safeParse(c);
|
|
6897
7628
|
if (!r.success) {
|
|
6898
7629
|
return reply.code(400).send({
|
|
6899
7630
|
error: `unknown divergence type "${c}"`,
|
|
6900
|
-
allowed:
|
|
7631
|
+
allowed: import_types28.DivergenceTypeSchema.options
|
|
6901
7632
|
});
|
|
6902
7633
|
}
|
|
6903
7634
|
parsed.push(r.data);
|
|
@@ -7116,7 +7847,7 @@ function registerRoutes(scope, ctx) {
|
|
|
7116
7847
|
const log = new PolicyViolationsLog(proj.paths.policyViolationsPath);
|
|
7117
7848
|
let violations = await log.readAll();
|
|
7118
7849
|
if (req.query.severity) {
|
|
7119
|
-
const sev =
|
|
7850
|
+
const sev = import_types28.PolicySeveritySchema.safeParse(req.query.severity);
|
|
7120
7851
|
if (!sev.success) {
|
|
7121
7852
|
return reply.code(400).send({
|
|
7122
7853
|
error: "invalid severity",
|
|
@@ -7155,7 +7886,7 @@ function registerRoutes(scope, ctx) {
|
|
|
7155
7886
|
scope.post("/policies/check", async (req, reply) => {
|
|
7156
7887
|
const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
|
|
7157
7888
|
if (!proj) return;
|
|
7158
|
-
const parsed =
|
|
7889
|
+
const parsed = import_types28.PoliciesCheckBodySchema.safeParse(req.body ?? {});
|
|
7159
7890
|
if (!parsed.success) {
|
|
7160
7891
|
return reply.code(400).send({
|
|
7161
7892
|
error: "invalid /policies/check body",
|
|
@@ -7418,7 +8149,7 @@ init_otel_grpc();
|
|
|
7418
8149
|
// src/search.ts
|
|
7419
8150
|
init_cjs_shims();
|
|
7420
8151
|
var import_node_fs24 = require("fs");
|
|
7421
|
-
var
|
|
8152
|
+
var import_node_path43 = __toESM(require("path"), 1);
|
|
7422
8153
|
var import_node_crypto3 = require("crypto");
|
|
7423
8154
|
var DEFAULT_LIMIT = 10;
|
|
7424
8155
|
var NOMIC_DIM = 768;
|
|
@@ -7453,6 +8184,18 @@ function embedText(node) {
|
|
|
7453
8184
|
if (filePath) parts.push(`path=${filePath}`);
|
|
7454
8185
|
break;
|
|
7455
8186
|
}
|
|
8187
|
+
case "RouteNode": {
|
|
8188
|
+
const method = node.method;
|
|
8189
|
+
const tmpl = node.pathTemplate;
|
|
8190
|
+
if (method) parts.push(`method=${method}`);
|
|
8191
|
+
if (tmpl) parts.push(`path=${tmpl}`);
|
|
8192
|
+
break;
|
|
8193
|
+
}
|
|
8194
|
+
case "GraphQLOperationNode": {
|
|
8195
|
+
const opType = node.operationType;
|
|
8196
|
+
if (opType) parts.push(`operationType=${opType}`);
|
|
8197
|
+
break;
|
|
8198
|
+
}
|
|
7456
8199
|
default:
|
|
7457
8200
|
break;
|
|
7458
8201
|
}
|
|
@@ -7557,7 +8300,7 @@ async function readCache(cachePath) {
|
|
|
7557
8300
|
}
|
|
7558
8301
|
}
|
|
7559
8302
|
async function writeCache(cachePath, cache) {
|
|
7560
|
-
await import_node_fs24.promises.mkdir(
|
|
8303
|
+
await import_node_fs24.promises.mkdir(import_node_path43.default.dirname(cachePath), { recursive: true });
|
|
7561
8304
|
await import_node_fs24.promises.writeFile(cachePath, JSON.stringify(cache));
|
|
7562
8305
|
}
|
|
7563
8306
|
var VectorIndex = class {
|
|
@@ -7738,14 +8481,14 @@ async function bootProject(registry, name, scanPath, baseDir) {
|
|
|
7738
8481
|
async function main() {
|
|
7739
8482
|
const baseDirEnv = process.env.NEAT_OUT_DIR;
|
|
7740
8483
|
const legacyOutPath = process.env.NEAT_OUT_PATH;
|
|
7741
|
-
const baseDir = baseDirEnv ?
|
|
7742
|
-
const defaultScanPath =
|
|
8484
|
+
const baseDir = baseDirEnv ? import_node_path44.default.resolve(baseDirEnv) : legacyOutPath ? import_node_path44.default.resolve(import_node_path44.default.dirname(legacyOutPath)) : import_node_path44.default.resolve("./neat-out");
|
|
8485
|
+
const defaultScanPath = import_node_path44.default.resolve(process.env.NEAT_SCAN_PATH ?? "./demo");
|
|
7743
8486
|
const registry = new Projects();
|
|
7744
8487
|
await bootProject(registry, DEFAULT_PROJECT, defaultScanPath, baseDir);
|
|
7745
8488
|
for (const name of parseExtraProjects(process.env.NEAT_PROJECTS)) {
|
|
7746
8489
|
const envKey = `NEAT_PROJECT_SCAN_PATH_${name.toUpperCase().replace(/[^A-Z0-9]/g, "_")}`;
|
|
7747
8490
|
const projectScan = process.env[envKey];
|
|
7748
|
-
await bootProject(registry, name, projectScan ?
|
|
8491
|
+
await bootProject(registry, name, projectScan ? import_node_path44.default.resolve(projectScan) : void 0, baseDir);
|
|
7749
8492
|
}
|
|
7750
8493
|
const host = process.env.HOST ?? "0.0.0.0";
|
|
7751
8494
|
const port = Number(process.env.PORT ?? 8080);
|