@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
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
mountBearerAuth,
|
|
3
3
|
readAuthEnv
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-2LNICOVU.js";
|
|
5
5
|
|
|
6
6
|
// src/graph.ts
|
|
7
7
|
import GraphDefault from "graphology";
|
|
@@ -456,19 +456,19 @@ function confidenceFromMix(edges, now = Date.now()) {
|
|
|
456
456
|
function longestIncomingWalk(graph, start, maxDepth) {
|
|
457
457
|
let best = { path: [start], edges: [] };
|
|
458
458
|
const visited = /* @__PURE__ */ new Set([start]);
|
|
459
|
-
function step(node,
|
|
460
|
-
if (
|
|
461
|
-
best = { path: [...
|
|
459
|
+
function step(node, path41, edges) {
|
|
460
|
+
if (path41.length > best.path.length) {
|
|
461
|
+
best = { path: [...path41], edges: [...edges] };
|
|
462
462
|
}
|
|
463
|
-
if (
|
|
463
|
+
if (path41.length - 1 >= maxDepth) return;
|
|
464
464
|
const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
|
|
465
465
|
for (const [srcId, edge] of incoming) {
|
|
466
466
|
if (visited.has(srcId)) continue;
|
|
467
467
|
visited.add(srcId);
|
|
468
|
-
|
|
468
|
+
path41.push(srcId);
|
|
469
469
|
edges.push(edge);
|
|
470
|
-
step(srcId,
|
|
471
|
-
|
|
470
|
+
step(srcId, path41, edges);
|
|
471
|
+
path41.pop();
|
|
472
472
|
edges.pop();
|
|
473
473
|
visited.delete(srcId);
|
|
474
474
|
}
|
|
@@ -476,14 +476,14 @@ function longestIncomingWalk(graph, start, maxDepth) {
|
|
|
476
476
|
step(start, [start], []);
|
|
477
477
|
return best;
|
|
478
478
|
}
|
|
479
|
-
function databaseRootCauseShape(graph, origin,
|
|
479
|
+
function databaseRootCauseShape(graph, origin, walk3) {
|
|
480
480
|
const targetDb = origin;
|
|
481
481
|
const candidatePairs = compatPairs().filter((p) => p.engine === targetDb.engine);
|
|
482
482
|
if (candidatePairs.length === 0) return null;
|
|
483
|
-
for (const id of
|
|
483
|
+
for (const id of walk3.path) {
|
|
484
484
|
const owner = resolveOwningService(graph, id);
|
|
485
485
|
if (!owner) continue;
|
|
486
|
-
const { id:
|
|
486
|
+
const { id: serviceId4, svc } = owner;
|
|
487
487
|
const deps = svc.dependencies ?? {};
|
|
488
488
|
for (const pair of candidatePairs) {
|
|
489
489
|
const declared = deps[pair.driver];
|
|
@@ -496,7 +496,7 @@ function databaseRootCauseShape(graph, origin, walk) {
|
|
|
496
496
|
);
|
|
497
497
|
if (!result.compatible) {
|
|
498
498
|
return {
|
|
499
|
-
rootCauseNode:
|
|
499
|
+
rootCauseNode: serviceId4,
|
|
500
500
|
rootCauseReason: result.reason ?? "incompatible driver",
|
|
501
501
|
...result.minDriverVersion ? {
|
|
502
502
|
fixRecommendation: `Upgrade ${svc.name} ${pair.driver} driver to >= ${result.minDriverVersion}`
|
|
@@ -507,11 +507,11 @@ function databaseRootCauseShape(graph, origin, walk) {
|
|
|
507
507
|
}
|
|
508
508
|
return null;
|
|
509
509
|
}
|
|
510
|
-
function serviceRootCauseShape(graph, _origin,
|
|
511
|
-
for (const id of
|
|
510
|
+
function serviceRootCauseShape(graph, _origin, walk3) {
|
|
511
|
+
for (const id of walk3.path) {
|
|
512
512
|
const owner = resolveOwningService(graph, id);
|
|
513
513
|
if (!owner) continue;
|
|
514
|
-
const { id:
|
|
514
|
+
const { id: serviceId4, svc } = owner;
|
|
515
515
|
const deps = svc.dependencies ?? {};
|
|
516
516
|
const serviceNodeEngine = svc.nodeEngine;
|
|
517
517
|
for (const constraint of nodeEngineConstraints()) {
|
|
@@ -520,7 +520,7 @@ function serviceRootCauseShape(graph, _origin, walk) {
|
|
|
520
520
|
const result = checkNodeEngineConstraint(constraint, declared, serviceNodeEngine);
|
|
521
521
|
if (!result.compatible && result.reason) {
|
|
522
522
|
return {
|
|
523
|
-
rootCauseNode:
|
|
523
|
+
rootCauseNode: serviceId4,
|
|
524
524
|
rootCauseReason: result.reason,
|
|
525
525
|
...result.requiredNodeVersion ? {
|
|
526
526
|
fixRecommendation: `Bump ${svc.name}'s engines.node to >= ${result.requiredNodeVersion}`
|
|
@@ -535,7 +535,7 @@ function serviceRootCauseShape(graph, _origin, walk) {
|
|
|
535
535
|
const result = checkPackageConflict(conflict, declared, requiredDeclared);
|
|
536
536
|
if (!result.compatible && result.reason) {
|
|
537
537
|
return {
|
|
538
|
-
rootCauseNode:
|
|
538
|
+
rootCauseNode: serviceId4,
|
|
539
539
|
rootCauseReason: result.reason,
|
|
540
540
|
fixRecommendation: `Upgrade ${svc.name}'s ${conflict.requires.name} to >= ${conflict.requires.minVersion}`
|
|
541
541
|
};
|
|
@@ -544,10 +544,10 @@ function serviceRootCauseShape(graph, _origin, walk) {
|
|
|
544
544
|
}
|
|
545
545
|
return null;
|
|
546
546
|
}
|
|
547
|
-
function fileRootCauseShape(graph, origin,
|
|
547
|
+
function fileRootCauseShape(graph, origin, walk3) {
|
|
548
548
|
const owner = resolveOwningService(graph, origin.id);
|
|
549
549
|
if (!owner) return null;
|
|
550
|
-
return serviceRootCauseShape(graph, owner.svc,
|
|
550
|
+
return serviceRootCauseShape(graph, owner.svc, walk3);
|
|
551
551
|
}
|
|
552
552
|
var rootCauseShapes = {
|
|
553
553
|
[NodeType.DatabaseNode]: databaseRootCauseShape,
|
|
@@ -559,16 +559,16 @@ function getRootCause(graph, errorNodeId, errorEvent, incidents) {
|
|
|
559
559
|
const origin = graph.getNodeAttributes(errorNodeId);
|
|
560
560
|
const shape = rootCauseShapes[origin.type];
|
|
561
561
|
if (shape) {
|
|
562
|
-
const
|
|
563
|
-
const match = shape(graph, origin,
|
|
562
|
+
const walk3 = longestIncomingWalk(graph, errorNodeId, ROOT_CAUSE_MAX_DEPTH);
|
|
563
|
+
const match = shape(graph, origin, walk3);
|
|
564
564
|
if (match) {
|
|
565
565
|
const reason = errorEvent ? `${match.rootCauseReason} (observed error: ${errorEvent.errorMessage})` : match.rootCauseReason;
|
|
566
566
|
return RootCauseResultSchema.parse({
|
|
567
567
|
rootCauseNode: match.rootCauseNode,
|
|
568
568
|
rootCauseReason: reason,
|
|
569
|
-
traversalPath:
|
|
570
|
-
edgeProvenances:
|
|
571
|
-
confidence: confidenceFromMix(
|
|
569
|
+
traversalPath: walk3.path,
|
|
570
|
+
edgeProvenances: walk3.edges.map((e) => e.provenance),
|
|
571
|
+
confidence: confidenceFromMix(walk3.edges),
|
|
572
572
|
fixRecommendation: match.fixRecommendation
|
|
573
573
|
});
|
|
574
574
|
}
|
|
@@ -626,9 +626,9 @@ function rootCauseFromIncidents(nodeId, incidents, errorEvent) {
|
|
|
626
626
|
function isFailingCallEdge(e) {
|
|
627
627
|
return e.type === EdgeType.CALLS && (e.signal?.errorCount ?? 0) > 0;
|
|
628
628
|
}
|
|
629
|
-
function callSourcesForService(graph,
|
|
630
|
-
const ids = [
|
|
631
|
-
for (const edgeId of graph.outboundEdges(
|
|
629
|
+
function callSourcesForService(graph, serviceId4) {
|
|
630
|
+
const ids = [serviceId4];
|
|
631
|
+
for (const edgeId of graph.outboundEdges(serviceId4)) {
|
|
632
632
|
const e = graph.getEdgeAttributes(edgeId);
|
|
633
633
|
if (e.type !== EdgeType.CONTAINS) continue;
|
|
634
634
|
const tgt = graph.getNodeAttributes(e.target);
|
|
@@ -645,9 +645,9 @@ function failingCallDominates(e, id, curEdge, curId) {
|
|
|
645
645
|
}
|
|
646
646
|
return id < curId;
|
|
647
647
|
}
|
|
648
|
-
function dominantFailingCall(graph,
|
|
648
|
+
function dominantFailingCall(graph, serviceId4, visited) {
|
|
649
649
|
let best = null;
|
|
650
|
-
for (const src of callSourcesForService(graph,
|
|
650
|
+
for (const src of callSourcesForService(graph, serviceId4)) {
|
|
651
651
|
for (const edgeId of graph.outboundEdges(src)) {
|
|
652
652
|
const e = graph.getEdgeAttributes(edgeId);
|
|
653
653
|
if (!isFailingCallEdge(e)) continue;
|
|
@@ -662,26 +662,26 @@ function dominantFailingCall(graph, serviceId3, visited) {
|
|
|
662
662
|
return best;
|
|
663
663
|
}
|
|
664
664
|
function followFailingCallChain(graph, originServiceId, maxDepth) {
|
|
665
|
-
const
|
|
665
|
+
const path41 = [originServiceId];
|
|
666
666
|
const edges = [];
|
|
667
667
|
const visited = /* @__PURE__ */ new Set([originServiceId]);
|
|
668
668
|
let current = originServiceId;
|
|
669
669
|
for (let depth = 0; depth < maxDepth; depth++) {
|
|
670
670
|
const hop = dominantFailingCall(graph, current, visited);
|
|
671
671
|
if (!hop) break;
|
|
672
|
-
|
|
672
|
+
path41.push(hop.nextService);
|
|
673
673
|
edges.push(hop.edge);
|
|
674
674
|
visited.add(hop.nextService);
|
|
675
675
|
current = hop.nextService;
|
|
676
676
|
}
|
|
677
677
|
if (edges.length === 0) return null;
|
|
678
|
-
return { path:
|
|
678
|
+
return { path: path41, edges, culprit: current };
|
|
679
679
|
}
|
|
680
680
|
function crossServiceRootCause(graph, originId, incidents, errorEvent) {
|
|
681
681
|
const chain = followFailingCallChain(graph, originId, ROOT_CAUSE_MAX_DEPTH);
|
|
682
682
|
if (!chain) return null;
|
|
683
683
|
const culprit = chain.culprit;
|
|
684
|
-
const
|
|
684
|
+
const path41 = [...chain.path];
|
|
685
685
|
const edgeProvenances = chain.edges.map((e) => e.provenance);
|
|
686
686
|
const baseConfidence = confidenceFromMix(chain.edges);
|
|
687
687
|
const confidence = Math.max(0, Math.min(1, baseConfidence * INCIDENT_ROOT_CAUSE_CONFIDENCE));
|
|
@@ -689,14 +689,14 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
|
|
|
689
689
|
if (loc) {
|
|
690
690
|
let rootCauseNode = culprit;
|
|
691
691
|
if (loc.fileNode) {
|
|
692
|
-
|
|
692
|
+
path41.push(loc.fileNode);
|
|
693
693
|
edgeProvenances.push(Provenance.OBSERVED);
|
|
694
694
|
rootCauseNode = loc.fileNode;
|
|
695
695
|
}
|
|
696
696
|
return RootCauseResultSchema.parse({
|
|
697
697
|
rootCauseNode,
|
|
698
698
|
rootCauseReason: loc.rootCauseReason,
|
|
699
|
-
traversalPath:
|
|
699
|
+
traversalPath: path41,
|
|
700
700
|
edgeProvenances,
|
|
701
701
|
confidence,
|
|
702
702
|
...loc.fixRecommendation ? { fixRecommendation: loc.fixRecommendation } : {}
|
|
@@ -708,7 +708,7 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
|
|
|
708
708
|
return RootCauseResultSchema.parse({
|
|
709
709
|
rootCauseNode: culprit,
|
|
710
710
|
rootCauseReason: `${culpritName} is failing downstream calls (${errs} observed error${errs === 1 ? "" : "s"})`,
|
|
711
|
-
traversalPath:
|
|
711
|
+
traversalPath: path41,
|
|
712
712
|
edgeProvenances,
|
|
713
713
|
confidence,
|
|
714
714
|
fixRecommendation: `Inspect ${culpritName}'s failing handler`
|
|
@@ -1377,10 +1377,13 @@ import {
|
|
|
1377
1377
|
Provenance as Provenance2,
|
|
1378
1378
|
confidenceForObservedSignal,
|
|
1379
1379
|
databaseId,
|
|
1380
|
+
localDatabaseId,
|
|
1380
1381
|
extractedEdgeId,
|
|
1381
1382
|
fileId,
|
|
1382
1383
|
frontierId,
|
|
1384
|
+
graphqlOperationId,
|
|
1383
1385
|
inferredEdgeId,
|
|
1386
|
+
infraId,
|
|
1384
1387
|
observedEdgeId,
|
|
1385
1388
|
serviceId
|
|
1386
1389
|
} from "@neat.is/types";
|
|
@@ -1719,10 +1722,48 @@ var STITCH_EDGE_TYPES = /* @__PURE__ */ new Set([
|
|
|
1719
1722
|
]);
|
|
1720
1723
|
var WIRE_SPAN_KIND_CLIENT = 3;
|
|
1721
1724
|
var WIRE_SPAN_KIND_PRODUCER = 4;
|
|
1725
|
+
var WIRE_SPAN_KIND_CONSUMER = 5;
|
|
1722
1726
|
function spanMintsObservedEdge(kind) {
|
|
1723
1727
|
if (kind === void 0 || kind === 0) return true;
|
|
1724
1728
|
return kind === WIRE_SPAN_KIND_CLIENT || kind === WIRE_SPAN_KIND_PRODUCER;
|
|
1725
1729
|
}
|
|
1730
|
+
function spanMintsMessagingEdge(kind) {
|
|
1731
|
+
return kind === WIRE_SPAN_KIND_PRODUCER || kind === WIRE_SPAN_KIND_CONSUMER;
|
|
1732
|
+
}
|
|
1733
|
+
function spanServesGraphqlOperation(kind) {
|
|
1734
|
+
return kind !== WIRE_SPAN_KIND_CLIENT && kind !== WIRE_SPAN_KIND_PRODUCER && kind !== WIRE_SPAN_KIND_CONSUMER;
|
|
1735
|
+
}
|
|
1736
|
+
function ensureGraphqlOperationNode(graph, serviceName, operationType, operationName) {
|
|
1737
|
+
const id = graphqlOperationId(serviceName, operationType, operationName);
|
|
1738
|
+
if (graph.hasNode(id)) return id;
|
|
1739
|
+
const node = {
|
|
1740
|
+
id,
|
|
1741
|
+
type: NodeType3.GraphQLOperationNode,
|
|
1742
|
+
name: operationName,
|
|
1743
|
+
service: serviceName,
|
|
1744
|
+
operationType: operationType.toLowerCase(),
|
|
1745
|
+
operationName,
|
|
1746
|
+
discoveredVia: "otel"
|
|
1747
|
+
};
|
|
1748
|
+
graph.addNode(id, node);
|
|
1749
|
+
return id;
|
|
1750
|
+
}
|
|
1751
|
+
function messagingDestinationKind(system) {
|
|
1752
|
+
return `${system}-topic`;
|
|
1753
|
+
}
|
|
1754
|
+
function ensureMessagingDestinationNode(graph, system, destination) {
|
|
1755
|
+
const id = infraId(messagingDestinationKind(system), destination);
|
|
1756
|
+
if (graph.hasNode(id)) return id;
|
|
1757
|
+
const node = {
|
|
1758
|
+
id,
|
|
1759
|
+
type: NodeType3.InfraNode,
|
|
1760
|
+
name: destination,
|
|
1761
|
+
provider: "self",
|
|
1762
|
+
kind: messagingDestinationKind(system)
|
|
1763
|
+
};
|
|
1764
|
+
graph.addNode(id, node);
|
|
1765
|
+
return id;
|
|
1766
|
+
}
|
|
1726
1767
|
var PARENT_SPAN_CACHE_SIZE = 1e4;
|
|
1727
1768
|
var PARENT_SPAN_CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
1728
1769
|
var parentSpanCache = /* @__PURE__ */ new Map();
|
|
@@ -1816,6 +1857,21 @@ function ensureDatabaseNode(graph, host, engine) {
|
|
|
1816
1857
|
graph.addNode(id, node);
|
|
1817
1858
|
return id;
|
|
1818
1859
|
}
|
|
1860
|
+
function ensureLocalDatabaseNode(graph, serviceName, name, engine) {
|
|
1861
|
+
const id = localDatabaseId(serviceName, name);
|
|
1862
|
+
if (graph.hasNode(id)) return id;
|
|
1863
|
+
const node = {
|
|
1864
|
+
id,
|
|
1865
|
+
type: NodeType3.DatabaseNode,
|
|
1866
|
+
name,
|
|
1867
|
+
engine,
|
|
1868
|
+
engineVersion: "unknown",
|
|
1869
|
+
compatibleDrivers: [],
|
|
1870
|
+
discoveredVia: "otel"
|
|
1871
|
+
};
|
|
1872
|
+
graph.addNode(id, node);
|
|
1873
|
+
return id;
|
|
1874
|
+
}
|
|
1819
1875
|
function ensureFrontierNode(graph, host, ts) {
|
|
1820
1876
|
const id = frontierIdFor(host);
|
|
1821
1877
|
if (graph.hasNode(id)) {
|
|
@@ -2067,10 +2123,21 @@ async function handleSpan(ctx, span) {
|
|
|
2067
2123
|
let affectedNode = sourceId;
|
|
2068
2124
|
const mintsFromCallerSide = spanMintsObservedEdge(span.kind);
|
|
2069
2125
|
if (span.dbSystem) {
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2126
|
+
if (mintsFromCallerSide) {
|
|
2127
|
+
const host = pickAddress(span);
|
|
2128
|
+
let targetId;
|
|
2129
|
+
if (host) {
|
|
2130
|
+
ensureDatabaseNode(ctx.graph, host, span.dbSystem);
|
|
2131
|
+
targetId = databaseId(host);
|
|
2132
|
+
} else {
|
|
2133
|
+
const localName = span.dbName ?? span.dbSystem;
|
|
2134
|
+
targetId = ensureLocalDatabaseNode(
|
|
2135
|
+
ctx.graph,
|
|
2136
|
+
span.service,
|
|
2137
|
+
localName,
|
|
2138
|
+
span.dbSystem
|
|
2139
|
+
);
|
|
2140
|
+
}
|
|
2074
2141
|
const result = upsertObservedEdge(
|
|
2075
2142
|
ctx.graph,
|
|
2076
2143
|
EdgeType3.CONNECTS_TO,
|
|
@@ -2082,6 +2149,40 @@ async function handleSpan(ctx, span) {
|
|
|
2082
2149
|
);
|
|
2083
2150
|
if (result) affectedNode = targetId;
|
|
2084
2151
|
}
|
|
2152
|
+
} else if (span.messagingSystem && span.messagingDestination && spanMintsMessagingEdge(span.kind)) {
|
|
2153
|
+
const targetId = ensureMessagingDestinationNode(
|
|
2154
|
+
ctx.graph,
|
|
2155
|
+
span.messagingSystem,
|
|
2156
|
+
span.messagingDestination
|
|
2157
|
+
);
|
|
2158
|
+
const edgeType = span.kind === WIRE_SPAN_KIND_CONSUMER ? EdgeType3.CONSUMES_FROM : EdgeType3.PUBLISHES_TO;
|
|
2159
|
+
const result = upsertObservedEdge(
|
|
2160
|
+
ctx.graph,
|
|
2161
|
+
edgeType,
|
|
2162
|
+
observedSource(),
|
|
2163
|
+
targetId,
|
|
2164
|
+
ts,
|
|
2165
|
+
isError,
|
|
2166
|
+
callSiteEvidence
|
|
2167
|
+
);
|
|
2168
|
+
if (result) affectedNode = targetId;
|
|
2169
|
+
} else if (span.graphqlOperationName && span.graphqlOperationType && spanServesGraphqlOperation(span.kind)) {
|
|
2170
|
+
const targetId = ensureGraphqlOperationNode(
|
|
2171
|
+
ctx.graph,
|
|
2172
|
+
span.service,
|
|
2173
|
+
span.graphqlOperationType,
|
|
2174
|
+
span.graphqlOperationName
|
|
2175
|
+
);
|
|
2176
|
+
const result = upsertObservedEdge(
|
|
2177
|
+
ctx.graph,
|
|
2178
|
+
EdgeType3.CONTAINS,
|
|
2179
|
+
observedSource(),
|
|
2180
|
+
targetId,
|
|
2181
|
+
ts,
|
|
2182
|
+
isError,
|
|
2183
|
+
callSiteEvidence
|
|
2184
|
+
);
|
|
2185
|
+
if (result) affectedNode = targetId;
|
|
2085
2186
|
} else {
|
|
2086
2187
|
const host = pickAddress(span);
|
|
2087
2188
|
let resolvedViaAddress = false;
|
|
@@ -2191,29 +2292,29 @@ function promoteFrontierNodes(graph, opts = {}) {
|
|
|
2191
2292
|
toPromote.push({ frontierId: id, serviceId: target });
|
|
2192
2293
|
});
|
|
2193
2294
|
let promoted = 0;
|
|
2194
|
-
for (const { frontierId: frontierId2, serviceId:
|
|
2295
|
+
for (const { frontierId: frontierId2, serviceId: serviceId4 } of toPromote) {
|
|
2195
2296
|
if (opts.policies && opts.policies.length > 0 && opts.policyCtx) {
|
|
2196
2297
|
const gate = canPromoteFrontier(graph, frontierId2, opts.policies, opts.policyCtx);
|
|
2197
2298
|
if (!gate.allowed) {
|
|
2198
2299
|
continue;
|
|
2199
2300
|
}
|
|
2200
2301
|
}
|
|
2201
|
-
rewireFrontierEdges(graph, frontierId2,
|
|
2302
|
+
rewireFrontierEdges(graph, frontierId2, serviceId4);
|
|
2202
2303
|
graph.dropNode(frontierId2);
|
|
2203
2304
|
promoted++;
|
|
2204
2305
|
}
|
|
2205
2306
|
return promoted;
|
|
2206
2307
|
}
|
|
2207
|
-
function rewireFrontierEdges(graph, frontierId2,
|
|
2308
|
+
function rewireFrontierEdges(graph, frontierId2, serviceId4) {
|
|
2208
2309
|
const inbound = [...graph.inboundEdges(frontierId2)];
|
|
2209
2310
|
const outbound = [...graph.outboundEdges(frontierId2)];
|
|
2210
2311
|
for (const edgeId of inbound) {
|
|
2211
2312
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
2212
|
-
rebuildEdge(graph, edge, edge.source,
|
|
2313
|
+
rebuildEdge(graph, edge, edge.source, serviceId4, edgeId);
|
|
2213
2314
|
}
|
|
2214
2315
|
for (const edgeId of outbound) {
|
|
2215
2316
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
2216
|
-
rebuildEdge(graph, edge,
|
|
2317
|
+
rebuildEdge(graph, edge, serviceId4, edge.target, edgeId);
|
|
2217
2318
|
}
|
|
2218
2319
|
}
|
|
2219
2320
|
function rebuildEdge(graph, edge, newSource, newTarget, oldEdgeId) {
|
|
@@ -2981,9 +3082,9 @@ var K8S_KINDS_WITH_HOSTNAMES = /* @__PURE__ */ new Set([
|
|
|
2981
3082
|
"StatefulSet",
|
|
2982
3083
|
"DaemonSet"
|
|
2983
3084
|
]);
|
|
2984
|
-
function addAliases(graph,
|
|
2985
|
-
if (!graph.hasNode(
|
|
2986
|
-
const node = graph.getNodeAttributes(
|
|
3085
|
+
function addAliases(graph, serviceId4, candidates) {
|
|
3086
|
+
if (!graph.hasNode(serviceId4)) return;
|
|
3087
|
+
const node = graph.getNodeAttributes(serviceId4);
|
|
2987
3088
|
if (node.type !== NodeType5.ServiceNode) return;
|
|
2988
3089
|
const set = new Set(node.aliases ?? []);
|
|
2989
3090
|
for (const c of candidates) {
|
|
@@ -2993,7 +3094,7 @@ function addAliases(graph, serviceId3, candidates) {
|
|
|
2993
3094
|
}
|
|
2994
3095
|
if (set.size === 0) return;
|
|
2995
3096
|
const updated = { ...node, aliases: [...set].sort() };
|
|
2996
|
-
graph.replaceNodeAttributes(
|
|
3097
|
+
graph.replaceNodeAttributes(serviceId4, updated);
|
|
2997
3098
|
}
|
|
2998
3099
|
function indexServicesByName(services) {
|
|
2999
3100
|
const map = /* @__PURE__ */ new Map();
|
|
@@ -3026,12 +3127,12 @@ async function collectComposeAliases(graph, scanPath, serviceIndex) {
|
|
|
3026
3127
|
}
|
|
3027
3128
|
if (!compose?.services) return;
|
|
3028
3129
|
for (const [composeName, svc] of Object.entries(compose.services)) {
|
|
3029
|
-
const
|
|
3030
|
-
if (!
|
|
3130
|
+
const serviceId4 = serviceIndex.get(composeName);
|
|
3131
|
+
if (!serviceId4) continue;
|
|
3031
3132
|
const aliases = /* @__PURE__ */ new Set([composeName]);
|
|
3032
3133
|
if (svc.container_name) aliases.add(svc.container_name);
|
|
3033
3134
|
if (svc.hostname) aliases.add(svc.hostname);
|
|
3034
|
-
addAliases(graph,
|
|
3135
|
+
addAliases(graph, serviceId4, aliases);
|
|
3035
3136
|
}
|
|
3036
3137
|
}
|
|
3037
3138
|
var LABEL_KEYS = /* @__PURE__ */ new Set([
|
|
@@ -3147,16 +3248,28 @@ import {
|
|
|
3147
3248
|
extractedEdgeId as extractedEdgeId3,
|
|
3148
3249
|
fileId as fileId2
|
|
3149
3250
|
} from "@neat.is/types";
|
|
3251
|
+
function buildServiceHostIndex(services) {
|
|
3252
|
+
const knownHosts = /* @__PURE__ */ new Set();
|
|
3253
|
+
const hostToNodeId = /* @__PURE__ */ new Map();
|
|
3254
|
+
for (const service of services) {
|
|
3255
|
+
const base = path10.basename(service.dir);
|
|
3256
|
+
knownHosts.add(base);
|
|
3257
|
+
knownHosts.add(service.pkg.name);
|
|
3258
|
+
hostToNodeId.set(base, service.node.id);
|
|
3259
|
+
hostToNodeId.set(service.pkg.name, service.node.id);
|
|
3260
|
+
}
|
|
3261
|
+
return { knownHosts, hostToNodeId };
|
|
3262
|
+
}
|
|
3150
3263
|
async function walkSourceFiles(dir) {
|
|
3151
3264
|
const out = [];
|
|
3152
|
-
async function
|
|
3265
|
+
async function walk3(current) {
|
|
3153
3266
|
const entries = await fs10.readdir(current, { withFileTypes: true }).catch(() => []);
|
|
3154
3267
|
for (const entry of entries) {
|
|
3155
3268
|
const full = path10.join(current, entry.name);
|
|
3156
3269
|
if (entry.isDirectory()) {
|
|
3157
3270
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
3158
3271
|
if (await isPythonVenvDir(full)) continue;
|
|
3159
|
-
await
|
|
3272
|
+
await walk3(full);
|
|
3160
3273
|
} else if (entry.isFile() && SERVICE_FILE_EXTENSIONS.has(path10.extname(entry.name)) && // Skip NEAT's own generated `otel-init.*` bootstrap — extracting it
|
|
3161
3274
|
// would attribute our instrumentation imports to the user's service.
|
|
3162
3275
|
!isNeatAuthoredSourceFile(entry.name)) {
|
|
@@ -3164,7 +3277,7 @@ async function walkSourceFiles(dir) {
|
|
|
3164
3277
|
}
|
|
3165
3278
|
}
|
|
3166
3279
|
}
|
|
3167
|
-
await
|
|
3280
|
+
await walk3(dir);
|
|
3168
3281
|
return out;
|
|
3169
3282
|
}
|
|
3170
3283
|
async function loadSourceFiles(dir) {
|
|
@@ -4264,20 +4377,20 @@ import {
|
|
|
4264
4377
|
} from "@neat.is/types";
|
|
4265
4378
|
async function walkConfigFiles(dir) {
|
|
4266
4379
|
const out = [];
|
|
4267
|
-
async function
|
|
4380
|
+
async function walk3(current) {
|
|
4268
4381
|
const entries = await fs14.readdir(current, { withFileTypes: true });
|
|
4269
4382
|
for (const entry of entries) {
|
|
4270
4383
|
const full = path21.join(current, entry.name);
|
|
4271
4384
|
if (entry.isDirectory()) {
|
|
4272
4385
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
4273
4386
|
if (await isPythonVenvDir(full)) continue;
|
|
4274
|
-
await
|
|
4387
|
+
await walk3(full);
|
|
4275
4388
|
} else if (entry.isFile() && isConfigFile(entry.name).match) {
|
|
4276
4389
|
out.push(full);
|
|
4277
4390
|
}
|
|
4278
4391
|
}
|
|
4279
4392
|
}
|
|
4280
|
-
await
|
|
4393
|
+
await walk3(dir);
|
|
4281
4394
|
return out;
|
|
4282
4395
|
}
|
|
4283
4396
|
async function addConfigNodes(graph, services, scanPath) {
|
|
@@ -4325,24 +4438,360 @@ async function addConfigNodes(graph, services, scanPath) {
|
|
|
4325
4438
|
return { nodesAdded, edgesAdded };
|
|
4326
4439
|
}
|
|
4327
4440
|
|
|
4328
|
-
// src/extract/
|
|
4329
|
-
import {
|
|
4330
|
-
EdgeType as EdgeType9,
|
|
4331
|
-
NodeType as NodeType9,
|
|
4332
|
-
Provenance as Provenance8,
|
|
4333
|
-
confidenceForExtracted as confidenceForExtracted6,
|
|
4334
|
-
passesExtractedFloor as passesExtractedFloor2
|
|
4335
|
-
} from "@neat.is/types";
|
|
4336
|
-
|
|
4337
|
-
// src/extract/calls/http.ts
|
|
4441
|
+
// src/extract/routes.ts
|
|
4338
4442
|
import path22 from "path";
|
|
4339
4443
|
import Parser2 from "tree-sitter";
|
|
4340
4444
|
import JavaScript2 from "tree-sitter-javascript";
|
|
4341
|
-
import Python2 from "tree-sitter-python";
|
|
4342
4445
|
import {
|
|
4343
4446
|
EdgeType as EdgeType8,
|
|
4447
|
+
NodeType as NodeType9,
|
|
4344
4448
|
Provenance as Provenance7,
|
|
4345
4449
|
confidenceForExtracted as confidenceForExtracted5,
|
|
4450
|
+
extractedEdgeId as extractedEdgeId5,
|
|
4451
|
+
routeId
|
|
4452
|
+
} from "@neat.is/types";
|
|
4453
|
+
var PARSE_CHUNK2 = 16384;
|
|
4454
|
+
function parseSource2(parser, source) {
|
|
4455
|
+
return parser.parse(
|
|
4456
|
+
(index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK2)
|
|
4457
|
+
);
|
|
4458
|
+
}
|
|
4459
|
+
function makeJsParser2() {
|
|
4460
|
+
const p = new Parser2();
|
|
4461
|
+
p.setLanguage(JavaScript2);
|
|
4462
|
+
return p;
|
|
4463
|
+
}
|
|
4464
|
+
var ROUTER_METHODS = /* @__PURE__ */ new Set([
|
|
4465
|
+
"get",
|
|
4466
|
+
"post",
|
|
4467
|
+
"put",
|
|
4468
|
+
"patch",
|
|
4469
|
+
"delete",
|
|
4470
|
+
"options",
|
|
4471
|
+
"head",
|
|
4472
|
+
"all"
|
|
4473
|
+
]);
|
|
4474
|
+
var NEXT_APP_METHODS = /* @__PURE__ */ new Set(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"]);
|
|
4475
|
+
var JS_ROUTE_EXTENSIONS = /* @__PURE__ */ new Set([".js", ".jsx", ".mjs", ".cjs", ".ts", ".tsx"]);
|
|
4476
|
+
function canonicalizeTemplate(raw) {
|
|
4477
|
+
let p = raw.split("?")[0].split("#")[0];
|
|
4478
|
+
if (!p.startsWith("/")) p = "/" + p;
|
|
4479
|
+
if (p.length > 1 && p.endsWith("/")) p = p.slice(0, -1);
|
|
4480
|
+
return p;
|
|
4481
|
+
}
|
|
4482
|
+
function isDynamicSegment(seg) {
|
|
4483
|
+
if (seg.length === 0) return false;
|
|
4484
|
+
if (seg.includes(":")) return true;
|
|
4485
|
+
if (seg.startsWith("{") || seg.startsWith("[")) return true;
|
|
4486
|
+
if (/^\d+$/.test(seg)) return true;
|
|
4487
|
+
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;
|
|
4488
|
+
if (/^[0-9a-f]{24,}$/i.test(seg)) return true;
|
|
4489
|
+
return false;
|
|
4490
|
+
}
|
|
4491
|
+
function normalizePathTemplate(raw) {
|
|
4492
|
+
const canonical = canonicalizeTemplate(raw);
|
|
4493
|
+
const segments = canonical.split("/").filter((s) => s.length > 0);
|
|
4494
|
+
const normalised = segments.map((seg) => isDynamicSegment(seg) ? ":param" : seg.toLowerCase());
|
|
4495
|
+
return "/" + normalised.join("/");
|
|
4496
|
+
}
|
|
4497
|
+
function walk(node, visit) {
|
|
4498
|
+
visit(node);
|
|
4499
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
4500
|
+
const child = node.namedChild(i);
|
|
4501
|
+
if (child) walk(child, visit);
|
|
4502
|
+
}
|
|
4503
|
+
}
|
|
4504
|
+
function staticStringText(node) {
|
|
4505
|
+
if (node.type === "string") {
|
|
4506
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
4507
|
+
const child = node.namedChild(i);
|
|
4508
|
+
if (child?.type === "string_fragment") return child.text;
|
|
4509
|
+
}
|
|
4510
|
+
return "";
|
|
4511
|
+
}
|
|
4512
|
+
if (node.type === "template_string") {
|
|
4513
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
4514
|
+
if (node.namedChild(i)?.type === "template_substitution") return null;
|
|
4515
|
+
}
|
|
4516
|
+
const raw = node.text;
|
|
4517
|
+
return raw.length >= 2 ? raw.slice(1, -1) : "";
|
|
4518
|
+
}
|
|
4519
|
+
return null;
|
|
4520
|
+
}
|
|
4521
|
+
function objectStringProp(objNode, key) {
|
|
4522
|
+
for (let i = 0; i < objNode.namedChildCount; i++) {
|
|
4523
|
+
const pair = objNode.namedChild(i);
|
|
4524
|
+
if (!pair || pair.type !== "pair") continue;
|
|
4525
|
+
const k = pair.childForFieldName("key");
|
|
4526
|
+
if (!k) continue;
|
|
4527
|
+
const kText = k.type === "string" ? staticStringText(k) : k.text;
|
|
4528
|
+
if (kText !== key) continue;
|
|
4529
|
+
const v = pair.childForFieldName("value");
|
|
4530
|
+
if (v) return staticStringText(v);
|
|
4531
|
+
}
|
|
4532
|
+
return null;
|
|
4533
|
+
}
|
|
4534
|
+
function fastifyRouteMethods(objNode) {
|
|
4535
|
+
for (let i = 0; i < objNode.namedChildCount; i++) {
|
|
4536
|
+
const pair = objNode.namedChild(i);
|
|
4537
|
+
if (!pair || pair.type !== "pair") continue;
|
|
4538
|
+
const k = pair.childForFieldName("key");
|
|
4539
|
+
const kText = k ? k.type === "string" ? staticStringText(k) : k.text : null;
|
|
4540
|
+
if (kText !== "method") continue;
|
|
4541
|
+
const v = pair.childForFieldName("value");
|
|
4542
|
+
if (!v) return [];
|
|
4543
|
+
if (v.type === "string" || v.type === "template_string") {
|
|
4544
|
+
const s = staticStringText(v);
|
|
4545
|
+
return s ? [s.toUpperCase()] : [];
|
|
4546
|
+
}
|
|
4547
|
+
if (v.type === "array") {
|
|
4548
|
+
const out = [];
|
|
4549
|
+
for (let j = 0; j < v.namedChildCount; j++) {
|
|
4550
|
+
const el = v.namedChild(j);
|
|
4551
|
+
if (el && (el.type === "string" || el.type === "template_string")) {
|
|
4552
|
+
const s = staticStringText(el);
|
|
4553
|
+
if (s) out.push(s.toUpperCase());
|
|
4554
|
+
}
|
|
4555
|
+
}
|
|
4556
|
+
return out;
|
|
4557
|
+
}
|
|
4558
|
+
}
|
|
4559
|
+
return [];
|
|
4560
|
+
}
|
|
4561
|
+
function serverRoutesFromSource(source, parser, hasExpress, hasFastify) {
|
|
4562
|
+
const tree = parseSource2(parser, source);
|
|
4563
|
+
const out = [];
|
|
4564
|
+
const framework = hasExpress ? "express" : "fastify";
|
|
4565
|
+
walk(tree.rootNode, (node) => {
|
|
4566
|
+
if (node.type !== "call_expression") return;
|
|
4567
|
+
const fn = node.childForFieldName("function");
|
|
4568
|
+
if (!fn || fn.type !== "member_expression") return;
|
|
4569
|
+
const prop = fn.childForFieldName("property");
|
|
4570
|
+
if (!prop) return;
|
|
4571
|
+
const method = prop.text.toLowerCase();
|
|
4572
|
+
const args = node.childForFieldName("arguments");
|
|
4573
|
+
const first = args?.namedChild(0);
|
|
4574
|
+
if (!first) return;
|
|
4575
|
+
const line = node.startPosition.row + 1;
|
|
4576
|
+
if (ROUTER_METHODS.has(method)) {
|
|
4577
|
+
const p = staticStringText(first);
|
|
4578
|
+
if (p && p.startsWith("/")) {
|
|
4579
|
+
out.push({
|
|
4580
|
+
method: method === "all" ? "ALL" : method.toUpperCase(),
|
|
4581
|
+
pathTemplate: canonicalizeTemplate(p),
|
|
4582
|
+
line,
|
|
4583
|
+
framework
|
|
4584
|
+
});
|
|
4585
|
+
}
|
|
4586
|
+
return;
|
|
4587
|
+
}
|
|
4588
|
+
if (method === "route" && hasFastify && first.type === "object") {
|
|
4589
|
+
const url = objectStringProp(first, "url");
|
|
4590
|
+
if (!url || !url.startsWith("/")) return;
|
|
4591
|
+
const methods = fastifyRouteMethods(first);
|
|
4592
|
+
const list = methods.length > 0 ? methods : ["ALL"];
|
|
4593
|
+
for (const m of list) {
|
|
4594
|
+
out.push({
|
|
4595
|
+
method: m === "ALL" ? "ALL" : m.toUpperCase(),
|
|
4596
|
+
pathTemplate: canonicalizeTemplate(url),
|
|
4597
|
+
line,
|
|
4598
|
+
framework: "fastify"
|
|
4599
|
+
});
|
|
4600
|
+
}
|
|
4601
|
+
}
|
|
4602
|
+
});
|
|
4603
|
+
return out;
|
|
4604
|
+
}
|
|
4605
|
+
function segmentsOf(relFile) {
|
|
4606
|
+
return toPosix2(relFile).split("/").filter((s) => s.length > 0);
|
|
4607
|
+
}
|
|
4608
|
+
function isNextAppRouteFile(relFile) {
|
|
4609
|
+
const segs = segmentsOf(relFile);
|
|
4610
|
+
if (!segs.includes("app")) return false;
|
|
4611
|
+
const base = segs[segs.length - 1] ?? "";
|
|
4612
|
+
return /^route\.(?:js|jsx|mjs|cjs|ts|tsx)$/.test(base);
|
|
4613
|
+
}
|
|
4614
|
+
function isNextPagesApiFile(relFile) {
|
|
4615
|
+
const segs = segmentsOf(relFile);
|
|
4616
|
+
const pagesIdx = segs.indexOf("pages");
|
|
4617
|
+
if (pagesIdx === -1 || segs[pagesIdx + 1] !== "api") return false;
|
|
4618
|
+
const base = segs[segs.length - 1] ?? "";
|
|
4619
|
+
if (/^_(app|document|middleware)\./.test(base)) return false;
|
|
4620
|
+
return JS_ROUTE_EXTENSIONS.has(path22.extname(base));
|
|
4621
|
+
}
|
|
4622
|
+
function nextSegment(seg) {
|
|
4623
|
+
if (seg.startsWith("(") && seg.endsWith(")")) return null;
|
|
4624
|
+
const catchAll = seg.match(/^\[\[?\.\.\.(.+?)\]?\]$/);
|
|
4625
|
+
if (catchAll) return ":" + catchAll[1];
|
|
4626
|
+
const dynamic = seg.match(/^\[(.+?)\]$/);
|
|
4627
|
+
if (dynamic) return ":" + dynamic[1];
|
|
4628
|
+
return seg;
|
|
4629
|
+
}
|
|
4630
|
+
function nextAppPathTemplate(relFile) {
|
|
4631
|
+
const segs = segmentsOf(relFile);
|
|
4632
|
+
const appIdx = segs.lastIndexOf("app");
|
|
4633
|
+
const between = segs.slice(appIdx + 1, segs.length - 1);
|
|
4634
|
+
const parts = [];
|
|
4635
|
+
for (const seg of between) {
|
|
4636
|
+
const mapped = nextSegment(seg);
|
|
4637
|
+
if (mapped !== null) parts.push(mapped);
|
|
4638
|
+
}
|
|
4639
|
+
return "/" + parts.join("/");
|
|
4640
|
+
}
|
|
4641
|
+
function nextPagesApiPathTemplate(relFile) {
|
|
4642
|
+
const segs = segmentsOf(relFile);
|
|
4643
|
+
const pagesIdx = segs.indexOf("pages");
|
|
4644
|
+
const rest = segs.slice(pagesIdx + 1);
|
|
4645
|
+
const parts = [];
|
|
4646
|
+
for (let i = 0; i < rest.length; i++) {
|
|
4647
|
+
let seg = rest[i];
|
|
4648
|
+
if (i === rest.length - 1) {
|
|
4649
|
+
seg = seg.replace(/\.(?:js|jsx|mjs|cjs|ts|tsx)$/, "");
|
|
4650
|
+
if (seg === "index") continue;
|
|
4651
|
+
}
|
|
4652
|
+
const mapped = nextSegment(seg);
|
|
4653
|
+
if (mapped !== null) parts.push(mapped);
|
|
4654
|
+
}
|
|
4655
|
+
return "/" + parts.join("/");
|
|
4656
|
+
}
|
|
4657
|
+
function nextAppMethods(root) {
|
|
4658
|
+
const out = [];
|
|
4659
|
+
walk(root, (node) => {
|
|
4660
|
+
if (node.type !== "export_statement") return;
|
|
4661
|
+
const decl = node.childForFieldName("declaration");
|
|
4662
|
+
if (!decl) return;
|
|
4663
|
+
const line = node.startPosition.row + 1;
|
|
4664
|
+
if (decl.type === "function_declaration") {
|
|
4665
|
+
const name = decl.childForFieldName("name")?.text;
|
|
4666
|
+
if (name && NEXT_APP_METHODS.has(name)) out.push({ method: name, line });
|
|
4667
|
+
return;
|
|
4668
|
+
}
|
|
4669
|
+
if (decl.type === "lexical_declaration" || decl.type === "variable_declaration") {
|
|
4670
|
+
for (let i = 0; i < decl.namedChildCount; i++) {
|
|
4671
|
+
const d = decl.namedChild(i);
|
|
4672
|
+
if (d?.type !== "variable_declarator") continue;
|
|
4673
|
+
const name = d.childForFieldName("name")?.text;
|
|
4674
|
+
if (name && NEXT_APP_METHODS.has(name)) out.push({ method: name, line });
|
|
4675
|
+
}
|
|
4676
|
+
}
|
|
4677
|
+
});
|
|
4678
|
+
return out;
|
|
4679
|
+
}
|
|
4680
|
+
function nextRoutesFromFile(source, relFile, parser) {
|
|
4681
|
+
if (isNextAppRouteFile(relFile)) {
|
|
4682
|
+
const tree = parseSource2(parser, source);
|
|
4683
|
+
const template = nextAppPathTemplate(relFile);
|
|
4684
|
+
return nextAppMethods(tree.rootNode).map(({ method, line }) => ({
|
|
4685
|
+
method,
|
|
4686
|
+
pathTemplate: canonicalizeTemplate(template),
|
|
4687
|
+
line,
|
|
4688
|
+
framework: "next"
|
|
4689
|
+
}));
|
|
4690
|
+
}
|
|
4691
|
+
if (isNextPagesApiFile(relFile)) {
|
|
4692
|
+
return [
|
|
4693
|
+
{
|
|
4694
|
+
method: "ALL",
|
|
4695
|
+
pathTemplate: canonicalizeTemplate(nextPagesApiPathTemplate(relFile)),
|
|
4696
|
+
line: 1,
|
|
4697
|
+
framework: "next"
|
|
4698
|
+
}
|
|
4699
|
+
];
|
|
4700
|
+
}
|
|
4701
|
+
return [];
|
|
4702
|
+
}
|
|
4703
|
+
async function addRoutes(graph, services) {
|
|
4704
|
+
const jsParser = makeJsParser2();
|
|
4705
|
+
let nodesAdded = 0;
|
|
4706
|
+
let edgesAdded = 0;
|
|
4707
|
+
for (const service of services) {
|
|
4708
|
+
const deps = {
|
|
4709
|
+
...service.pkg.dependencies ?? {},
|
|
4710
|
+
...service.pkg.devDependencies ?? {}
|
|
4711
|
+
};
|
|
4712
|
+
const hasExpress = deps["express"] !== void 0;
|
|
4713
|
+
const hasFastify = deps["fastify"] !== void 0;
|
|
4714
|
+
const hasNext = deps["next"] !== void 0;
|
|
4715
|
+
if (!hasExpress && !hasFastify && !hasNext) continue;
|
|
4716
|
+
const files = await loadSourceFiles(service.dir);
|
|
4717
|
+
for (const file of files) {
|
|
4718
|
+
if (isTestPath(file.path)) continue;
|
|
4719
|
+
if (!JS_ROUTE_EXTENSIONS.has(path22.extname(file.path))) continue;
|
|
4720
|
+
const relFile = toPosix2(path22.relative(service.dir, file.path));
|
|
4721
|
+
let routes;
|
|
4722
|
+
try {
|
|
4723
|
+
if (hasNext && (isNextAppRouteFile(relFile) || isNextPagesApiFile(relFile))) {
|
|
4724
|
+
routes = nextRoutesFromFile(file.content, relFile, jsParser);
|
|
4725
|
+
} else if (hasExpress || hasFastify) {
|
|
4726
|
+
routes = serverRoutesFromSource(file.content, jsParser, hasExpress, hasFastify);
|
|
4727
|
+
} else {
|
|
4728
|
+
routes = [];
|
|
4729
|
+
}
|
|
4730
|
+
} catch (err) {
|
|
4731
|
+
recordExtractionError("route extraction", file.path, err);
|
|
4732
|
+
continue;
|
|
4733
|
+
}
|
|
4734
|
+
if (routes.length === 0) continue;
|
|
4735
|
+
for (const route of routes) {
|
|
4736
|
+
const rid = routeId(service.pkg.name, route.method, route.pathTemplate);
|
|
4737
|
+
if (!graph.hasNode(rid)) {
|
|
4738
|
+
const node = {
|
|
4739
|
+
id: rid,
|
|
4740
|
+
type: NodeType9.RouteNode,
|
|
4741
|
+
name: `${route.method} ${route.pathTemplate}`,
|
|
4742
|
+
service: service.pkg.name,
|
|
4743
|
+
method: route.method,
|
|
4744
|
+
pathTemplate: route.pathTemplate,
|
|
4745
|
+
path: relFile,
|
|
4746
|
+
line: route.line,
|
|
4747
|
+
framework: route.framework,
|
|
4748
|
+
discoveredVia: "static"
|
|
4749
|
+
};
|
|
4750
|
+
graph.addNode(rid, node);
|
|
4751
|
+
nodesAdded++;
|
|
4752
|
+
}
|
|
4753
|
+
const containsId = extractedEdgeId5(service.node.id, rid, EdgeType8.CONTAINS);
|
|
4754
|
+
if (!graph.hasEdge(containsId)) {
|
|
4755
|
+
const edge = {
|
|
4756
|
+
id: containsId,
|
|
4757
|
+
source: service.node.id,
|
|
4758
|
+
target: rid,
|
|
4759
|
+
type: EdgeType8.CONTAINS,
|
|
4760
|
+
provenance: Provenance7.EXTRACTED,
|
|
4761
|
+
confidence: confidenceForExtracted5("structural"),
|
|
4762
|
+
evidence: {
|
|
4763
|
+
file: relFile,
|
|
4764
|
+
line: route.line,
|
|
4765
|
+
snippet: snippet(file.content, route.line)
|
|
4766
|
+
}
|
|
4767
|
+
};
|
|
4768
|
+
graph.addEdgeWithKey(containsId, service.node.id, rid, edge);
|
|
4769
|
+
edgesAdded++;
|
|
4770
|
+
}
|
|
4771
|
+
}
|
|
4772
|
+
}
|
|
4773
|
+
}
|
|
4774
|
+
return { nodesAdded, edgesAdded };
|
|
4775
|
+
}
|
|
4776
|
+
|
|
4777
|
+
// src/extract/calls/index.ts
|
|
4778
|
+
import {
|
|
4779
|
+
EdgeType as EdgeType11,
|
|
4780
|
+
NodeType as NodeType11,
|
|
4781
|
+
Provenance as Provenance10,
|
|
4782
|
+
confidenceForExtracted as confidenceForExtracted8,
|
|
4783
|
+
passesExtractedFloor as passesExtractedFloor3
|
|
4784
|
+
} from "@neat.is/types";
|
|
4785
|
+
|
|
4786
|
+
// src/extract/calls/http.ts
|
|
4787
|
+
import path23 from "path";
|
|
4788
|
+
import Parser3 from "tree-sitter";
|
|
4789
|
+
import JavaScript3 from "tree-sitter-javascript";
|
|
4790
|
+
import Python2 from "tree-sitter-python";
|
|
4791
|
+
import {
|
|
4792
|
+
EdgeType as EdgeType9,
|
|
4793
|
+
Provenance as Provenance8,
|
|
4794
|
+
confidenceForExtracted as confidenceForExtracted6,
|
|
4346
4795
|
passesExtractedFloor
|
|
4347
4796
|
} from "@neat.is/types";
|
|
4348
4797
|
var STRING_LITERAL_NODE_TYPES = /* @__PURE__ */ new Set(["string_fragment", "string_content"]);
|
|
@@ -4372,14 +4821,14 @@ function collectStringLiterals(node, out) {
|
|
|
4372
4821
|
if (child) collectStringLiterals(child, out);
|
|
4373
4822
|
}
|
|
4374
4823
|
}
|
|
4375
|
-
var
|
|
4376
|
-
function
|
|
4824
|
+
var PARSE_CHUNK3 = 16384;
|
|
4825
|
+
function parseSource3(parser, source) {
|
|
4377
4826
|
return parser.parse(
|
|
4378
|
-
(index) => index >= source.length ? "" : source.slice(index, index +
|
|
4827
|
+
(index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK3)
|
|
4379
4828
|
);
|
|
4380
4829
|
}
|
|
4381
4830
|
function callsFromSource(source, parser, knownHosts) {
|
|
4382
|
-
const tree =
|
|
4831
|
+
const tree = parseSource3(parser, source);
|
|
4383
4832
|
const literals = [];
|
|
4384
4833
|
collectStringLiterals(tree.rootNode, literals);
|
|
4385
4834
|
const out = [];
|
|
@@ -4393,27 +4842,20 @@ function callsFromSource(source, parser, knownHosts) {
|
|
|
4393
4842
|
}
|
|
4394
4843
|
return out;
|
|
4395
4844
|
}
|
|
4396
|
-
function
|
|
4397
|
-
const p = new
|
|
4398
|
-
p.setLanguage(
|
|
4845
|
+
function makeJsParser3() {
|
|
4846
|
+
const p = new Parser3();
|
|
4847
|
+
p.setLanguage(JavaScript3);
|
|
4399
4848
|
return p;
|
|
4400
4849
|
}
|
|
4401
4850
|
function makePyParser2() {
|
|
4402
|
-
const p = new
|
|
4851
|
+
const p = new Parser3();
|
|
4403
4852
|
p.setLanguage(Python2);
|
|
4404
4853
|
return p;
|
|
4405
4854
|
}
|
|
4406
4855
|
async function addHttpCallEdges(graph, services) {
|
|
4407
|
-
const jsParser =
|
|
4856
|
+
const jsParser = makeJsParser3();
|
|
4408
4857
|
const pyParser = makePyParser2();
|
|
4409
|
-
const knownHosts
|
|
4410
|
-
const hostToNodeId = /* @__PURE__ */ new Map();
|
|
4411
|
-
for (const service of services) {
|
|
4412
|
-
knownHosts.add(path22.basename(service.dir));
|
|
4413
|
-
knownHosts.add(service.pkg.name);
|
|
4414
|
-
hostToNodeId.set(path22.basename(service.dir), service.node.id);
|
|
4415
|
-
hostToNodeId.set(service.pkg.name, service.node.id);
|
|
4416
|
-
}
|
|
4858
|
+
const { knownHosts, hostToNodeId } = buildServiceHostIndex(services);
|
|
4417
4859
|
let nodesAdded = 0;
|
|
4418
4860
|
let edgesAdded = 0;
|
|
4419
4861
|
for (const service of services) {
|
|
@@ -4421,7 +4863,7 @@ async function addHttpCallEdges(graph, services) {
|
|
|
4421
4863
|
const seen = /* @__PURE__ */ new Set();
|
|
4422
4864
|
for (const file of files) {
|
|
4423
4865
|
if (isTestPath(file.path)) continue;
|
|
4424
|
-
const parser =
|
|
4866
|
+
const parser = path23.extname(file.path) === ".py" ? pyParser : jsParser;
|
|
4425
4867
|
let sites;
|
|
4426
4868
|
try {
|
|
4427
4869
|
sites = callsFromSource(file.content, parser, knownHosts);
|
|
@@ -4430,14 +4872,14 @@ async function addHttpCallEdges(graph, services) {
|
|
|
4430
4872
|
continue;
|
|
4431
4873
|
}
|
|
4432
4874
|
if (sites.length === 0) continue;
|
|
4433
|
-
const relFile = toPosix2(
|
|
4875
|
+
const relFile = toPosix2(path23.relative(service.dir, file.path));
|
|
4434
4876
|
for (const site of sites) {
|
|
4435
4877
|
const targetId = hostToNodeId.get(site.host);
|
|
4436
4878
|
if (!targetId || targetId === service.node.id) continue;
|
|
4437
4879
|
const dedupKey = `${relFile}|${targetId}`;
|
|
4438
4880
|
if (seen.has(dedupKey)) continue;
|
|
4439
4881
|
seen.add(dedupKey);
|
|
4440
|
-
const confidence =
|
|
4882
|
+
const confidence = confidenceForExtracted6("url-literal-service-target");
|
|
4441
4883
|
const ev = {
|
|
4442
4884
|
file: relFile,
|
|
4443
4885
|
line: site.line,
|
|
@@ -4455,21 +4897,21 @@ async function addHttpCallEdges(graph, services) {
|
|
|
4455
4897
|
noteExtractedDropped({
|
|
4456
4898
|
source: fileNodeId,
|
|
4457
4899
|
target: targetId,
|
|
4458
|
-
type:
|
|
4900
|
+
type: EdgeType9.CALLS,
|
|
4459
4901
|
confidence,
|
|
4460
4902
|
confidenceKind: "url-literal-service-target",
|
|
4461
4903
|
evidence: ev
|
|
4462
4904
|
});
|
|
4463
4905
|
continue;
|
|
4464
4906
|
}
|
|
4465
|
-
const edgeId = extractedEdgeId2(fileNodeId, targetId,
|
|
4907
|
+
const edgeId = extractedEdgeId2(fileNodeId, targetId, EdgeType9.CALLS);
|
|
4466
4908
|
if (!graph.hasEdge(edgeId)) {
|
|
4467
4909
|
const edge = {
|
|
4468
4910
|
id: edgeId,
|
|
4469
4911
|
source: fileNodeId,
|
|
4470
4912
|
target: targetId,
|
|
4471
|
-
type:
|
|
4472
|
-
provenance:
|
|
4913
|
+
type: EdgeType9.CALLS,
|
|
4914
|
+
provenance: Provenance8.EXTRACTED,
|
|
4473
4915
|
confidence,
|
|
4474
4916
|
evidence: ev
|
|
4475
4917
|
};
|
|
@@ -4482,9 +4924,284 @@ async function addHttpCallEdges(graph, services) {
|
|
|
4482
4924
|
return { nodesAdded, edgesAdded };
|
|
4483
4925
|
}
|
|
4484
4926
|
|
|
4927
|
+
// src/extract/calls/route-match.ts
|
|
4928
|
+
import path24 from "path";
|
|
4929
|
+
import Parser4 from "tree-sitter";
|
|
4930
|
+
import JavaScript4 from "tree-sitter-javascript";
|
|
4931
|
+
import {
|
|
4932
|
+
EdgeType as EdgeType10,
|
|
4933
|
+
NodeType as NodeType10,
|
|
4934
|
+
Provenance as Provenance9,
|
|
4935
|
+
confidenceForExtracted as confidenceForExtracted7,
|
|
4936
|
+
passesExtractedFloor as passesExtractedFloor2,
|
|
4937
|
+
serviceId as serviceId3
|
|
4938
|
+
} from "@neat.is/types";
|
|
4939
|
+
var PARSE_CHUNK4 = 16384;
|
|
4940
|
+
function parseSource4(parser, source) {
|
|
4941
|
+
return parser.parse(
|
|
4942
|
+
(index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK4)
|
|
4943
|
+
);
|
|
4944
|
+
}
|
|
4945
|
+
function makeJsParser4() {
|
|
4946
|
+
const p = new Parser4();
|
|
4947
|
+
p.setLanguage(JavaScript4);
|
|
4948
|
+
return p;
|
|
4949
|
+
}
|
|
4950
|
+
var JS_CLIENT_EXTENSIONS = /* @__PURE__ */ new Set([".js", ".jsx", ".mjs", ".cjs", ".ts", ".tsx"]);
|
|
4951
|
+
var AXIOS_METHODS = /* @__PURE__ */ new Set(["get", "post", "put", "patch", "delete", "head", "options", "request"]);
|
|
4952
|
+
function walk2(node, visit) {
|
|
4953
|
+
visit(node);
|
|
4954
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
4955
|
+
const child = node.namedChild(i);
|
|
4956
|
+
if (child) walk2(child, visit);
|
|
4957
|
+
}
|
|
4958
|
+
}
|
|
4959
|
+
function reconstructUrl(node) {
|
|
4960
|
+
if (node.type === "string") {
|
|
4961
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
4962
|
+
const child = node.namedChild(i);
|
|
4963
|
+
if (child?.type === "string_fragment") return child.text;
|
|
4964
|
+
}
|
|
4965
|
+
return "";
|
|
4966
|
+
}
|
|
4967
|
+
if (node.type === "template_string") {
|
|
4968
|
+
let out = "";
|
|
4969
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
4970
|
+
const child = node.namedChild(i);
|
|
4971
|
+
if (!child) continue;
|
|
4972
|
+
if (child.type === "string_fragment") out += child.text;
|
|
4973
|
+
else if (child.type === "template_substitution") out += ":param";
|
|
4974
|
+
}
|
|
4975
|
+
if (out.length === 0) {
|
|
4976
|
+
const raw = node.text;
|
|
4977
|
+
return raw.length >= 2 ? raw.slice(1, -1) : "";
|
|
4978
|
+
}
|
|
4979
|
+
return out;
|
|
4980
|
+
}
|
|
4981
|
+
return null;
|
|
4982
|
+
}
|
|
4983
|
+
function methodFromOptions(objNode) {
|
|
4984
|
+
for (let i = 0; i < objNode.namedChildCount; i++) {
|
|
4985
|
+
const pair = objNode.namedChild(i);
|
|
4986
|
+
if (!pair || pair.type !== "pair") continue;
|
|
4987
|
+
const k = pair.childForFieldName("key");
|
|
4988
|
+
const kText = k ? k.type === "string" ? stringText(k) : k.text : null;
|
|
4989
|
+
if (kText !== "method") continue;
|
|
4990
|
+
const v = pair.childForFieldName("value");
|
|
4991
|
+
if (v && (v.type === "string" || v.type === "template_string")) {
|
|
4992
|
+
const s = stringText(v);
|
|
4993
|
+
return s ? s.toUpperCase() : void 0;
|
|
4994
|
+
}
|
|
4995
|
+
}
|
|
4996
|
+
return void 0;
|
|
4997
|
+
}
|
|
4998
|
+
function stringText(node) {
|
|
4999
|
+
if (node.type === "string") {
|
|
5000
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
5001
|
+
const child = node.namedChild(i);
|
|
5002
|
+
if (child?.type === "string_fragment") return child.text;
|
|
5003
|
+
}
|
|
5004
|
+
return "";
|
|
5005
|
+
}
|
|
5006
|
+
return null;
|
|
5007
|
+
}
|
|
5008
|
+
function urlNodeFromConfig(objNode) {
|
|
5009
|
+
for (let i = 0; i < objNode.namedChildCount; i++) {
|
|
5010
|
+
const pair = objNode.namedChild(i);
|
|
5011
|
+
if (!pair || pair.type !== "pair") continue;
|
|
5012
|
+
const k = pair.childForFieldName("key");
|
|
5013
|
+
const kText = k ? k.type === "string" ? stringText(k) : k.text : null;
|
|
5014
|
+
if (kText === "url") return pair.childForFieldName("value");
|
|
5015
|
+
}
|
|
5016
|
+
return null;
|
|
5017
|
+
}
|
|
5018
|
+
function pathOf(urlStr) {
|
|
5019
|
+
try {
|
|
5020
|
+
const candidate = urlStr.startsWith("//") ? `http:${urlStr}` : urlStr;
|
|
5021
|
+
const parsed = new URL(candidate);
|
|
5022
|
+
return parsed.pathname || "/";
|
|
5023
|
+
} catch {
|
|
5024
|
+
return null;
|
|
5025
|
+
}
|
|
5026
|
+
}
|
|
5027
|
+
function matchHost(urlStr, knownHosts) {
|
|
5028
|
+
for (const host of knownHosts) {
|
|
5029
|
+
if (urlMatchesHost(urlStr, host)) return host;
|
|
5030
|
+
}
|
|
5031
|
+
return null;
|
|
5032
|
+
}
|
|
5033
|
+
function clientCallSitesFromSource(source, parser, knownHosts) {
|
|
5034
|
+
const tree = parseSource4(parser, source);
|
|
5035
|
+
const out = [];
|
|
5036
|
+
const push = (urlNode, method, callNode) => {
|
|
5037
|
+
const urlStr = reconstructUrl(urlNode);
|
|
5038
|
+
if (!urlStr) return;
|
|
5039
|
+
const host = matchHost(urlStr, knownHosts);
|
|
5040
|
+
if (!host) return;
|
|
5041
|
+
const p = pathOf(urlStr);
|
|
5042
|
+
if (p === null) return;
|
|
5043
|
+
const line = callNode.startPosition.row + 1;
|
|
5044
|
+
out.push({
|
|
5045
|
+
host,
|
|
5046
|
+
method,
|
|
5047
|
+
pathTemplate: p,
|
|
5048
|
+
line,
|
|
5049
|
+
snippet: snippet(source, line)
|
|
5050
|
+
});
|
|
5051
|
+
};
|
|
5052
|
+
walk2(tree.rootNode, (node) => {
|
|
5053
|
+
if (node.type !== "call_expression") return;
|
|
5054
|
+
const fn = node.childForFieldName("function");
|
|
5055
|
+
if (!fn) return;
|
|
5056
|
+
const args = node.childForFieldName("arguments");
|
|
5057
|
+
const first = args?.namedChild(0);
|
|
5058
|
+
if (!first) return;
|
|
5059
|
+
const fnName = fn.type === "identifier" ? fn.text : fn.type === "member_expression" ? fn.childForFieldName("property")?.text ?? "" : "";
|
|
5060
|
+
if (fn.type === "identifier" && fnName === "fetch") {
|
|
5061
|
+
const opts = args?.namedChild(1);
|
|
5062
|
+
const method = opts && opts.type === "object" ? methodFromOptions(opts) ?? "GET" : "GET";
|
|
5063
|
+
push(first, method, node);
|
|
5064
|
+
return;
|
|
5065
|
+
}
|
|
5066
|
+
if (fn.type === "identifier" && fnName === "axios") {
|
|
5067
|
+
if (first.type === "object") {
|
|
5068
|
+
const urlNode = urlNodeFromConfig(first);
|
|
5069
|
+
if (urlNode) push(urlNode, methodFromOptions(first) ?? "GET", node);
|
|
5070
|
+
} else {
|
|
5071
|
+
const opts = args?.namedChild(1);
|
|
5072
|
+
const method = opts && opts.type === "object" ? methodFromOptions(opts) ?? "GET" : "GET";
|
|
5073
|
+
push(first, method, node);
|
|
5074
|
+
}
|
|
5075
|
+
return;
|
|
5076
|
+
}
|
|
5077
|
+
if (fn.type === "member_expression") {
|
|
5078
|
+
const obj = fn.childForFieldName("object");
|
|
5079
|
+
const objName = obj?.text ?? "";
|
|
5080
|
+
if (objName === "axios" && AXIOS_METHODS.has(fnName)) {
|
|
5081
|
+
if (fnName === "request" && first.type === "object") {
|
|
5082
|
+
const urlNode = urlNodeFromConfig(first);
|
|
5083
|
+
if (urlNode) push(urlNode, methodFromOptions(first) ?? "GET", node);
|
|
5084
|
+
} else {
|
|
5085
|
+
push(first, fnName.toUpperCase(), node);
|
|
5086
|
+
}
|
|
5087
|
+
return;
|
|
5088
|
+
}
|
|
5089
|
+
if ((objName === "http" || objName === "https") && (fnName === "request" || fnName === "get")) {
|
|
5090
|
+
const opts = args?.namedChild(1);
|
|
5091
|
+
const method = opts && opts.type === "object" ? methodFromOptions(opts) ?? (fnName === "get" ? "GET" : "GET") : "GET";
|
|
5092
|
+
push(first, method, node);
|
|
5093
|
+
return;
|
|
5094
|
+
}
|
|
5095
|
+
}
|
|
5096
|
+
});
|
|
5097
|
+
return out;
|
|
5098
|
+
}
|
|
5099
|
+
function buildRouteIndex(graph) {
|
|
5100
|
+
const index = /* @__PURE__ */ new Map();
|
|
5101
|
+
graph.forEachNode((_id, attrs) => {
|
|
5102
|
+
const node = attrs;
|
|
5103
|
+
if (node.type !== NodeType10.RouteNode) return;
|
|
5104
|
+
const route = attrs;
|
|
5105
|
+
const owner = serviceId3(route.service);
|
|
5106
|
+
const entry = {
|
|
5107
|
+
method: route.method.toUpperCase(),
|
|
5108
|
+
normalizedPath: normalizePathTemplate(route.pathTemplate),
|
|
5109
|
+
routeNodeId: route.id
|
|
5110
|
+
};
|
|
5111
|
+
const list = index.get(owner);
|
|
5112
|
+
if (list) list.push(entry);
|
|
5113
|
+
else index.set(owner, [entry]);
|
|
5114
|
+
});
|
|
5115
|
+
return index;
|
|
5116
|
+
}
|
|
5117
|
+
function findRoute(entries, method, normalizedPath) {
|
|
5118
|
+
return entries.find(
|
|
5119
|
+
(e) => e.normalizedPath === normalizedPath && (e.method === "ALL" || method === void 0 || e.method === method)
|
|
5120
|
+
);
|
|
5121
|
+
}
|
|
5122
|
+
async function addRouteCallEdges(graph, services) {
|
|
5123
|
+
const jsParser = makeJsParser4();
|
|
5124
|
+
const { knownHosts, hostToNodeId } = buildServiceHostIndex(services);
|
|
5125
|
+
const routeIndex = buildRouteIndex(graph);
|
|
5126
|
+
if (routeIndex.size === 0) return { nodesAdded: 0, edgesAdded: 0 };
|
|
5127
|
+
let nodesAdded = 0;
|
|
5128
|
+
let edgesAdded = 0;
|
|
5129
|
+
for (const service of services) {
|
|
5130
|
+
const files = await loadSourceFiles(service.dir);
|
|
5131
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5132
|
+
for (const file of files) {
|
|
5133
|
+
if (isTestPath(file.path)) continue;
|
|
5134
|
+
if (!JS_CLIENT_EXTENSIONS.has(path24.extname(file.path))) continue;
|
|
5135
|
+
let sites;
|
|
5136
|
+
try {
|
|
5137
|
+
sites = clientCallSitesFromSource(file.content, jsParser, knownHosts);
|
|
5138
|
+
} catch (err) {
|
|
5139
|
+
recordExtractionError("route-match call extraction", file.path, err);
|
|
5140
|
+
continue;
|
|
5141
|
+
}
|
|
5142
|
+
if (sites.length === 0) continue;
|
|
5143
|
+
const relFile = toPosix2(path24.relative(service.dir, file.path));
|
|
5144
|
+
for (const site of sites) {
|
|
5145
|
+
const serverServiceId = hostToNodeId.get(site.host);
|
|
5146
|
+
if (!serverServiceId || serverServiceId === service.node.id) continue;
|
|
5147
|
+
const entries = routeIndex.get(serverServiceId);
|
|
5148
|
+
if (!entries) continue;
|
|
5149
|
+
const normalizedPath = normalizePathTemplate(site.pathTemplate);
|
|
5150
|
+
const match = findRoute(entries, site.method, normalizedPath);
|
|
5151
|
+
if (!match) continue;
|
|
5152
|
+
const dedupKey = `${relFile}|${match.routeNodeId}`;
|
|
5153
|
+
if (seen.has(dedupKey)) continue;
|
|
5154
|
+
seen.add(dedupKey);
|
|
5155
|
+
const { fileNodeId, nodesAdded: n, edgesAdded: e } = ensureFileNode(
|
|
5156
|
+
graph,
|
|
5157
|
+
service.pkg.name,
|
|
5158
|
+
service.node.id,
|
|
5159
|
+
relFile
|
|
5160
|
+
);
|
|
5161
|
+
nodesAdded += n;
|
|
5162
|
+
edgesAdded += e;
|
|
5163
|
+
const confidence = confidenceForExtracted7("verified-call-site");
|
|
5164
|
+
const ev = {
|
|
5165
|
+
file: relFile,
|
|
5166
|
+
line: site.line,
|
|
5167
|
+
snippet: site.snippet,
|
|
5168
|
+
method: site.method ?? match.method,
|
|
5169
|
+
pathTemplate: site.pathTemplate
|
|
5170
|
+
};
|
|
5171
|
+
if (!passesExtractedFloor2(confidence)) {
|
|
5172
|
+
noteExtractedDropped({
|
|
5173
|
+
source: fileNodeId,
|
|
5174
|
+
target: match.routeNodeId,
|
|
5175
|
+
type: EdgeType10.CALLS,
|
|
5176
|
+
confidence,
|
|
5177
|
+
confidenceKind: "verified-call-site",
|
|
5178
|
+
evidence: ev
|
|
5179
|
+
});
|
|
5180
|
+
continue;
|
|
5181
|
+
}
|
|
5182
|
+
const edgeId = extractedEdgeId2(fileNodeId, match.routeNodeId, EdgeType10.CALLS);
|
|
5183
|
+
if (!graph.hasEdge(edgeId)) {
|
|
5184
|
+
const edge = {
|
|
5185
|
+
id: edgeId,
|
|
5186
|
+
source: fileNodeId,
|
|
5187
|
+
target: match.routeNodeId,
|
|
5188
|
+
type: EdgeType10.CALLS,
|
|
5189
|
+
provenance: Provenance9.EXTRACTED,
|
|
5190
|
+
confidence,
|
|
5191
|
+
evidence: ev
|
|
5192
|
+
};
|
|
5193
|
+
graph.addEdgeWithKey(edgeId, fileNodeId, match.routeNodeId, edge);
|
|
5194
|
+
edgesAdded++;
|
|
5195
|
+
}
|
|
5196
|
+
}
|
|
5197
|
+
}
|
|
5198
|
+
}
|
|
5199
|
+
return { nodesAdded, edgesAdded };
|
|
5200
|
+
}
|
|
5201
|
+
|
|
4485
5202
|
// src/extract/calls/kafka.ts
|
|
4486
|
-
import
|
|
4487
|
-
import { infraId } from "@neat.is/types";
|
|
5203
|
+
import path25 from "path";
|
|
5204
|
+
import { infraId as infraId2 } from "@neat.is/types";
|
|
4488
5205
|
var PRODUCER_TOPIC_RE = /(?:producer|kafkaProducer)[\s\S]{0,40}?\.send\s*\(\s*\{[\s\S]{0,200}?topic\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
4489
5206
|
var CONSUMER_TOPIC_RE = /(?:consumer|kafkaConsumer)[\s\S]{0,40}?\.(?:subscribe|run)\s*\(\s*\{[\s\S]{0,200}?topic[s]?\s*:\s*(?:\[\s*)?['"`]([^'"`]+)['"`]/g;
|
|
4490
5207
|
function findAll(re, text) {
|
|
@@ -4505,7 +5222,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
|
|
|
4505
5222
|
seen.add(key);
|
|
4506
5223
|
const line = lineOf(file.content, topic);
|
|
4507
5224
|
out.push({
|
|
4508
|
-
infraId:
|
|
5225
|
+
infraId: infraId2("kafka-topic", topic),
|
|
4509
5226
|
name: topic,
|
|
4510
5227
|
kind: "kafka-topic",
|
|
4511
5228
|
edgeType,
|
|
@@ -4514,7 +5231,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
|
|
|
4514
5231
|
// tier (ADR-066).
|
|
4515
5232
|
confidenceKind: "verified-call-site",
|
|
4516
5233
|
evidence: {
|
|
4517
|
-
file:
|
|
5234
|
+
file: path25.relative(serviceDir, file.path),
|
|
4518
5235
|
line,
|
|
4519
5236
|
snippet: snippet(file.content, line)
|
|
4520
5237
|
}
|
|
@@ -4526,8 +5243,8 @@ function kafkaEndpointsFromFile(file, serviceDir) {
|
|
|
4526
5243
|
}
|
|
4527
5244
|
|
|
4528
5245
|
// src/extract/calls/redis.ts
|
|
4529
|
-
import
|
|
4530
|
-
import { infraId as
|
|
5246
|
+
import path26 from "path";
|
|
5247
|
+
import { infraId as infraId3 } from "@neat.is/types";
|
|
4531
5248
|
var REDIS_URL_RE = /redis(?:s)?:\/\/(?:[^@'"`\s]+@)?([^:/'"`\s]+)(?::(\d+))?/g;
|
|
4532
5249
|
function redisEndpointsFromFile(file, serviceDir) {
|
|
4533
5250
|
const out = [];
|
|
@@ -4540,7 +5257,7 @@ function redisEndpointsFromFile(file, serviceDir) {
|
|
|
4540
5257
|
seen.add(host);
|
|
4541
5258
|
const line = lineOf(file.content, host);
|
|
4542
5259
|
out.push({
|
|
4543
|
-
infraId:
|
|
5260
|
+
infraId: infraId3("redis", host),
|
|
4544
5261
|
name: host,
|
|
4545
5262
|
kind: "redis",
|
|
4546
5263
|
edgeType: "CALLS",
|
|
@@ -4549,7 +5266,7 @@ function redisEndpointsFromFile(file, serviceDir) {
|
|
|
4549
5266
|
// support tier (ADR-066).
|
|
4550
5267
|
confidenceKind: "url-with-structural-support",
|
|
4551
5268
|
evidence: {
|
|
4552
|
-
file:
|
|
5269
|
+
file: path26.relative(serviceDir, file.path),
|
|
4553
5270
|
line,
|
|
4554
5271
|
snippet: snippet(file.content, line)
|
|
4555
5272
|
}
|
|
@@ -4559,8 +5276,8 @@ function redisEndpointsFromFile(file, serviceDir) {
|
|
|
4559
5276
|
}
|
|
4560
5277
|
|
|
4561
5278
|
// src/extract/calls/aws.ts
|
|
4562
|
-
import
|
|
4563
|
-
import { infraId as
|
|
5279
|
+
import path27 from "path";
|
|
5280
|
+
import { infraId as infraId4 } from "@neat.is/types";
|
|
4564
5281
|
var S3_BUCKET_RE = /Bucket\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
4565
5282
|
var DYNAMO_TABLE_RE = /TableName\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
4566
5283
|
function hasMarker(text, markers) {
|
|
@@ -4584,7 +5301,7 @@ function awsEndpointsFromFile(file, serviceDir) {
|
|
|
4584
5301
|
seen.add(key);
|
|
4585
5302
|
const line = lineOf(file.content, name);
|
|
4586
5303
|
out.push({
|
|
4587
|
-
infraId:
|
|
5304
|
+
infraId: infraId4(kind, name),
|
|
4588
5305
|
name,
|
|
4589
5306
|
kind,
|
|
4590
5307
|
edgeType: "CALLS",
|
|
@@ -4593,7 +5310,7 @@ function awsEndpointsFromFile(file, serviceDir) {
|
|
|
4593
5310
|
// (ADR-066).
|
|
4594
5311
|
confidenceKind: "verified-call-site",
|
|
4595
5312
|
evidence: {
|
|
4596
|
-
file:
|
|
5313
|
+
file: path27.relative(serviceDir, file.path),
|
|
4597
5314
|
line,
|
|
4598
5315
|
snippet: snippet(file.content, line)
|
|
4599
5316
|
}
|
|
@@ -4617,8 +5334,8 @@ function awsEndpointsFromFile(file, serviceDir) {
|
|
|
4617
5334
|
}
|
|
4618
5335
|
|
|
4619
5336
|
// src/extract/calls/grpc.ts
|
|
4620
|
-
import
|
|
4621
|
-
import { infraId as
|
|
5337
|
+
import path28 from "path";
|
|
5338
|
+
import { infraId as infraId5 } from "@neat.is/types";
|
|
4622
5339
|
var GRPC_CLIENT_RE = /new\s+([A-Z][A-Za-z0-9_]*)Client\s*\(\s*['"`]?([^,'"`)]+)?/g;
|
|
4623
5340
|
var AWS_SDK_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@aws-sdk\/client-([a-z0-9-]+)['"`]/g;
|
|
4624
5341
|
var GRPC_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@grpc\/grpc-js['"`]|_grpc_pb['"`]/;
|
|
@@ -4667,7 +5384,7 @@ function grpcEndpointsFromFile(file, serviceDir) {
|
|
|
4667
5384
|
const { kind } = classified;
|
|
4668
5385
|
const line = lineOf(file.content, m[0]);
|
|
4669
5386
|
out.push({
|
|
4670
|
-
infraId:
|
|
5387
|
+
infraId: infraId5(kind, name),
|
|
4671
5388
|
name,
|
|
4672
5389
|
kind,
|
|
4673
5390
|
edgeType: "CALLS",
|
|
@@ -4676,7 +5393,7 @@ function grpcEndpointsFromFile(file, serviceDir) {
|
|
|
4676
5393
|
// tier (ADR-066).
|
|
4677
5394
|
confidenceKind: "verified-call-site",
|
|
4678
5395
|
evidence: {
|
|
4679
|
-
file:
|
|
5396
|
+
file: path28.relative(serviceDir, file.path),
|
|
4680
5397
|
line,
|
|
4681
5398
|
snippet: snippet(file.content, line)
|
|
4682
5399
|
}
|
|
@@ -4686,8 +5403,8 @@ function grpcEndpointsFromFile(file, serviceDir) {
|
|
|
4686
5403
|
}
|
|
4687
5404
|
|
|
4688
5405
|
// src/extract/calls/supabase.ts
|
|
4689
|
-
import
|
|
4690
|
-
import { infraId as
|
|
5406
|
+
import path29 from "path";
|
|
5407
|
+
import { infraId as infraId6 } from "@neat.is/types";
|
|
4691
5408
|
var SUPABASE_JS_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@supabase\/supabase-js['"`]/;
|
|
4692
5409
|
var SUPABASE_SSR_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@supabase\/ssr['"`]/;
|
|
4693
5410
|
var SUPABASE_CLIENT_RE = /\b(createClient|createServerClient|createBrowserClient)\s*\(\s*(?:['"`]([^'"`]*)['"`])?/g;
|
|
@@ -4725,7 +5442,7 @@ function supabaseEndpointsFromFile(file, serviceDir) {
|
|
|
4725
5442
|
seen.add(name);
|
|
4726
5443
|
const line = lineOf(file.content, m[0]);
|
|
4727
5444
|
out.push({
|
|
4728
|
-
infraId:
|
|
5445
|
+
infraId: infraId6("supabase", name),
|
|
4729
5446
|
name,
|
|
4730
5447
|
kind: "supabase",
|
|
4731
5448
|
edgeType: "CALLS",
|
|
@@ -4735,7 +5452,7 @@ function supabaseEndpointsFromFile(file, serviceDir) {
|
|
|
4735
5452
|
// tier (ADR-066), the same grade aws.ts / grpc.ts emit at.
|
|
4736
5453
|
confidenceKind: "verified-call-site",
|
|
4737
5454
|
evidence: {
|
|
4738
|
-
file:
|
|
5455
|
+
file: path29.relative(serviceDir, file.path),
|
|
4739
5456
|
line,
|
|
4740
5457
|
snippet: snippet(file.content, line)
|
|
4741
5458
|
}
|
|
@@ -4748,11 +5465,11 @@ function supabaseEndpointsFromFile(file, serviceDir) {
|
|
|
4748
5465
|
function edgeTypeFromEndpoint(ep) {
|
|
4749
5466
|
switch (ep.edgeType) {
|
|
4750
5467
|
case "PUBLISHES_TO":
|
|
4751
|
-
return
|
|
5468
|
+
return EdgeType11.PUBLISHES_TO;
|
|
4752
5469
|
case "CONSUMES_FROM":
|
|
4753
|
-
return
|
|
5470
|
+
return EdgeType11.CONSUMES_FROM;
|
|
4754
5471
|
default:
|
|
4755
|
-
return
|
|
5472
|
+
return EdgeType11.CALLS;
|
|
4756
5473
|
}
|
|
4757
5474
|
}
|
|
4758
5475
|
function isAwsKind(kind) {
|
|
@@ -4780,7 +5497,7 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
4780
5497
|
if (!graph.hasNode(ep.infraId)) {
|
|
4781
5498
|
const node = {
|
|
4782
5499
|
id: ep.infraId,
|
|
4783
|
-
type:
|
|
5500
|
+
type: NodeType11.InfraNode,
|
|
4784
5501
|
name: ep.name,
|
|
4785
5502
|
// #238 — `aws-*` covers AWS-SDK client kinds (aws-s3, aws-dynamodb,
|
|
4786
5503
|
// aws-cognito-identity-provider, …); `s3-` / `dynamodb-` cover the
|
|
@@ -4792,7 +5509,7 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
4792
5509
|
nodesAdded++;
|
|
4793
5510
|
}
|
|
4794
5511
|
const edgeType = edgeTypeFromEndpoint(ep);
|
|
4795
|
-
const confidence =
|
|
5512
|
+
const confidence = confidenceForExtracted8(ep.confidenceKind);
|
|
4796
5513
|
const relFile = toPosix2(ep.evidence.file);
|
|
4797
5514
|
const { fileNodeId, nodesAdded: n, edgesAdded: e } = ensureFileNode(
|
|
4798
5515
|
graph,
|
|
@@ -4802,7 +5519,7 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
4802
5519
|
);
|
|
4803
5520
|
nodesAdded += n;
|
|
4804
5521
|
edgesAdded += e;
|
|
4805
|
-
if (!
|
|
5522
|
+
if (!passesExtractedFloor3(confidence)) {
|
|
4806
5523
|
noteExtractedDropped({
|
|
4807
5524
|
source: fileNodeId,
|
|
4808
5525
|
target: ep.infraId,
|
|
@@ -4822,7 +5539,7 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
4822
5539
|
source: fileNodeId,
|
|
4823
5540
|
target: ep.infraId,
|
|
4824
5541
|
type: edgeType,
|
|
4825
|
-
provenance:
|
|
5542
|
+
provenance: Provenance10.EXTRACTED,
|
|
4826
5543
|
confidence,
|
|
4827
5544
|
evidence: ep.evidence
|
|
4828
5545
|
};
|
|
@@ -4836,22 +5553,23 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
4836
5553
|
async function addCallEdges(graph, services) {
|
|
4837
5554
|
const http = await addHttpCallEdges(graph, services);
|
|
4838
5555
|
const ext = await addExternalEndpointEdges(graph, services);
|
|
5556
|
+
const routes = await addRouteCallEdges(graph, services);
|
|
4839
5557
|
return {
|
|
4840
|
-
nodesAdded: http.nodesAdded + ext.nodesAdded,
|
|
4841
|
-
edgesAdded: http.edgesAdded + ext.edgesAdded
|
|
5558
|
+
nodesAdded: http.nodesAdded + ext.nodesAdded + routes.nodesAdded,
|
|
5559
|
+
edgesAdded: http.edgesAdded + ext.edgesAdded + routes.edgesAdded
|
|
4842
5560
|
};
|
|
4843
5561
|
}
|
|
4844
5562
|
|
|
4845
5563
|
// src/extract/infra/docker-compose.ts
|
|
4846
|
-
import
|
|
4847
|
-
import { EdgeType as
|
|
5564
|
+
import path30 from "path";
|
|
5565
|
+
import { EdgeType as EdgeType12, Provenance as Provenance11, confidenceForExtracted as confidenceForExtracted9 } from "@neat.is/types";
|
|
4848
5566
|
|
|
4849
5567
|
// src/extract/infra/shared.ts
|
|
4850
|
-
import { NodeType as
|
|
5568
|
+
import { NodeType as NodeType12, infraId as infraId7 } from "@neat.is/types";
|
|
4851
5569
|
function makeInfraNode(kind, name, provider = "self", extras) {
|
|
4852
5570
|
return {
|
|
4853
|
-
id:
|
|
4854
|
-
type:
|
|
5571
|
+
id: infraId7(kind, name),
|
|
5572
|
+
type: NodeType12.InfraNode,
|
|
4855
5573
|
name,
|
|
4856
5574
|
provider,
|
|
4857
5575
|
kind,
|
|
@@ -4880,7 +5598,7 @@ function dependsOnList(value) {
|
|
|
4880
5598
|
}
|
|
4881
5599
|
function serviceNameToServiceNode(name, services) {
|
|
4882
5600
|
for (const s of services) {
|
|
4883
|
-
if (s.node.name === name ||
|
|
5601
|
+
if (s.node.name === name || path30.basename(s.dir) === name) return s.node.id;
|
|
4884
5602
|
}
|
|
4885
5603
|
return null;
|
|
4886
5604
|
}
|
|
@@ -4889,7 +5607,7 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
4889
5607
|
let edgesAdded = 0;
|
|
4890
5608
|
let composePath = null;
|
|
4891
5609
|
for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
|
|
4892
|
-
const abs =
|
|
5610
|
+
const abs = path30.join(scanPath, name);
|
|
4893
5611
|
if (await exists(abs)) {
|
|
4894
5612
|
composePath = abs;
|
|
4895
5613
|
break;
|
|
@@ -4902,13 +5620,13 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
4902
5620
|
} catch (err) {
|
|
4903
5621
|
recordExtractionError(
|
|
4904
5622
|
"infra docker-compose",
|
|
4905
|
-
|
|
5623
|
+
path30.relative(scanPath, composePath),
|
|
4906
5624
|
err
|
|
4907
5625
|
);
|
|
4908
5626
|
return { nodesAdded, edgesAdded };
|
|
4909
5627
|
}
|
|
4910
5628
|
if (!compose?.services) return { nodesAdded, edgesAdded };
|
|
4911
|
-
const evidenceFile =
|
|
5629
|
+
const evidenceFile = path30.relative(scanPath, composePath).split(path30.sep).join("/");
|
|
4912
5630
|
const composeNameToNodeId = /* @__PURE__ */ new Map();
|
|
4913
5631
|
for (const [composeName, svc] of Object.entries(compose.services)) {
|
|
4914
5632
|
const matchedServiceId = serviceNameToServiceNode(composeName, services);
|
|
@@ -4930,15 +5648,15 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
4930
5648
|
for (const dep of dependsOnList(svc.depends_on)) {
|
|
4931
5649
|
const targetId = composeNameToNodeId.get(dep);
|
|
4932
5650
|
if (!targetId) continue;
|
|
4933
|
-
const edgeId = extractedEdgeId2(sourceId, targetId,
|
|
5651
|
+
const edgeId = extractedEdgeId2(sourceId, targetId, EdgeType12.DEPENDS_ON);
|
|
4934
5652
|
if (graph.hasEdge(edgeId)) continue;
|
|
4935
5653
|
const edge = {
|
|
4936
5654
|
id: edgeId,
|
|
4937
5655
|
source: sourceId,
|
|
4938
5656
|
target: targetId,
|
|
4939
|
-
type:
|
|
4940
|
-
provenance:
|
|
4941
|
-
confidence:
|
|
5657
|
+
type: EdgeType12.DEPENDS_ON,
|
|
5658
|
+
provenance: Provenance11.EXTRACTED,
|
|
5659
|
+
confidence: confidenceForExtracted9("structural"),
|
|
4942
5660
|
evidence: { file: evidenceFile }
|
|
4943
5661
|
};
|
|
4944
5662
|
graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
|
|
@@ -4949,9 +5667,9 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
4949
5667
|
}
|
|
4950
5668
|
|
|
4951
5669
|
// src/extract/infra/dockerfile.ts
|
|
4952
|
-
import
|
|
5670
|
+
import path31 from "path";
|
|
4953
5671
|
import { promises as fs15 } from "fs";
|
|
4954
|
-
import { EdgeType as
|
|
5672
|
+
import { EdgeType as EdgeType13, Provenance as Provenance12, confidenceForExtracted as confidenceForExtracted10 } from "@neat.is/types";
|
|
4955
5673
|
function readDockerfile(content) {
|
|
4956
5674
|
let image = null;
|
|
4957
5675
|
const ports = [];
|
|
@@ -4980,7 +5698,7 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
4980
5698
|
let nodesAdded = 0;
|
|
4981
5699
|
let edgesAdded = 0;
|
|
4982
5700
|
for (const service of services) {
|
|
4983
|
-
const dockerfilePath =
|
|
5701
|
+
const dockerfilePath = path31.join(service.dir, "Dockerfile");
|
|
4984
5702
|
if (!await exists(dockerfilePath)) continue;
|
|
4985
5703
|
let content;
|
|
4986
5704
|
try {
|
|
@@ -4988,7 +5706,7 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
4988
5706
|
} catch (err) {
|
|
4989
5707
|
recordExtractionError(
|
|
4990
5708
|
"infra dockerfile",
|
|
4991
|
-
|
|
5709
|
+
path31.relative(scanPath, dockerfilePath),
|
|
4992
5710
|
err
|
|
4993
5711
|
);
|
|
4994
5712
|
continue;
|
|
@@ -5000,8 +5718,8 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
5000
5718
|
graph.addNode(node.id, node);
|
|
5001
5719
|
nodesAdded++;
|
|
5002
5720
|
}
|
|
5003
|
-
const relDockerfile = toPosix2(
|
|
5004
|
-
const evidenceFile = toPosix2(
|
|
5721
|
+
const relDockerfile = toPosix2(path31.relative(service.dir, dockerfilePath));
|
|
5722
|
+
const evidenceFile = toPosix2(path31.relative(scanPath, dockerfilePath));
|
|
5005
5723
|
const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
|
|
5006
5724
|
graph,
|
|
5007
5725
|
service.pkg.name,
|
|
@@ -5010,15 +5728,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
5010
5728
|
);
|
|
5011
5729
|
nodesAdded += fn;
|
|
5012
5730
|
edgesAdded += fe;
|
|
5013
|
-
const edgeId = extractedEdgeId2(fileNodeId, node.id,
|
|
5731
|
+
const edgeId = extractedEdgeId2(fileNodeId, node.id, EdgeType13.RUNS_ON);
|
|
5014
5732
|
if (!graph.hasEdge(edgeId)) {
|
|
5015
5733
|
const edge = {
|
|
5016
5734
|
id: edgeId,
|
|
5017
5735
|
source: fileNodeId,
|
|
5018
5736
|
target: node.id,
|
|
5019
|
-
type:
|
|
5020
|
-
provenance:
|
|
5021
|
-
confidence:
|
|
5737
|
+
type: EdgeType13.RUNS_ON,
|
|
5738
|
+
provenance: Provenance12.EXTRACTED,
|
|
5739
|
+
confidence: confidenceForExtracted10("structural"),
|
|
5022
5740
|
evidence: {
|
|
5023
5741
|
file: evidenceFile,
|
|
5024
5742
|
...facts.entrypoint ? { snippet: facts.entrypoint.slice(0, 120) } : {}
|
|
@@ -5033,15 +5751,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
5033
5751
|
graph.addNode(portNode.id, portNode);
|
|
5034
5752
|
nodesAdded++;
|
|
5035
5753
|
}
|
|
5036
|
-
const portEdgeId = extractedEdgeId2(fileNodeId, portNode.id,
|
|
5754
|
+
const portEdgeId = extractedEdgeId2(fileNodeId, portNode.id, EdgeType13.CONNECTS_TO);
|
|
5037
5755
|
if (graph.hasEdge(portEdgeId)) continue;
|
|
5038
5756
|
const portEdge = {
|
|
5039
5757
|
id: portEdgeId,
|
|
5040
5758
|
source: fileNodeId,
|
|
5041
5759
|
target: portNode.id,
|
|
5042
|
-
type:
|
|
5043
|
-
provenance:
|
|
5044
|
-
confidence:
|
|
5760
|
+
type: EdgeType13.CONNECTS_TO,
|
|
5761
|
+
provenance: Provenance12.EXTRACTED,
|
|
5762
|
+
confidence: confidenceForExtracted10("structural"),
|
|
5045
5763
|
evidence: { file: evidenceFile, snippet: `EXPOSE ${port}` }
|
|
5046
5764
|
};
|
|
5047
5765
|
graph.addEdgeWithKey(portEdgeId, portEdge.source, portEdge.target, portEdge);
|
|
@@ -5053,8 +5771,8 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
5053
5771
|
|
|
5054
5772
|
// src/extract/infra/terraform.ts
|
|
5055
5773
|
import { promises as fs16 } from "fs";
|
|
5056
|
-
import
|
|
5057
|
-
import { EdgeType as
|
|
5774
|
+
import path32 from "path";
|
|
5775
|
+
import { EdgeType as EdgeType14, Provenance as Provenance13, confidenceForExtracted as confidenceForExtracted11 } from "@neat.is/types";
|
|
5058
5776
|
var RESOURCE_RE = /resource\s+"(aws_[A-Za-z0-9_]+)"\s+"([A-Za-z0-9_-]+)"/g;
|
|
5059
5777
|
var REFERENCE_RE = /(?<![\w.])(aws_[A-Za-z0-9_]+)\.([A-Za-z0-9_-]+)/g;
|
|
5060
5778
|
async function walkTfFiles(start, depth = 0, max = 5) {
|
|
@@ -5064,11 +5782,11 @@ async function walkTfFiles(start, depth = 0, max = 5) {
|
|
|
5064
5782
|
for (const entry of entries) {
|
|
5065
5783
|
if (entry.isDirectory()) {
|
|
5066
5784
|
if (IGNORED_DIRS.has(entry.name) || entry.name === ".terraform") continue;
|
|
5067
|
-
const child =
|
|
5785
|
+
const child = path32.join(start, entry.name);
|
|
5068
5786
|
if (await isPythonVenvDir(child)) continue;
|
|
5069
5787
|
out.push(...await walkTfFiles(child, depth + 1, max));
|
|
5070
5788
|
} else if (entry.isFile() && entry.name.endsWith(".tf")) {
|
|
5071
|
-
out.push(
|
|
5789
|
+
out.push(path32.join(start, entry.name));
|
|
5072
5790
|
}
|
|
5073
5791
|
}
|
|
5074
5792
|
return out;
|
|
@@ -5100,7 +5818,7 @@ async function addTerraformResources(graph, scanPath) {
|
|
|
5100
5818
|
const files = await walkTfFiles(scanPath);
|
|
5101
5819
|
for (const file of files) {
|
|
5102
5820
|
const content = await fs16.readFile(file, "utf8");
|
|
5103
|
-
const evidenceFile = toPosix2(
|
|
5821
|
+
const evidenceFile = toPosix2(path32.relative(scanPath, file));
|
|
5104
5822
|
const resources = [];
|
|
5105
5823
|
const byKey = /* @__PURE__ */ new Map();
|
|
5106
5824
|
RESOURCE_RE.lastIndex = 0;
|
|
@@ -5135,16 +5853,16 @@ async function addTerraformResources(graph, scanPath) {
|
|
|
5135
5853
|
if (!target) continue;
|
|
5136
5854
|
if (seen.has(target.nodeId)) continue;
|
|
5137
5855
|
seen.add(target.nodeId);
|
|
5138
|
-
const edgeId = extractedEdgeId2(resource.nodeId, target.nodeId,
|
|
5856
|
+
const edgeId = extractedEdgeId2(resource.nodeId, target.nodeId, EdgeType14.DEPENDS_ON);
|
|
5139
5857
|
if (graph.hasEdge(edgeId)) continue;
|
|
5140
5858
|
const line = lineAt(content, resource.bodyOffset + ref.index);
|
|
5141
5859
|
const edge = {
|
|
5142
5860
|
id: edgeId,
|
|
5143
5861
|
source: resource.nodeId,
|
|
5144
5862
|
target: target.nodeId,
|
|
5145
|
-
type:
|
|
5146
|
-
provenance:
|
|
5147
|
-
confidence:
|
|
5863
|
+
type: EdgeType14.DEPENDS_ON,
|
|
5864
|
+
provenance: Provenance13.EXTRACTED,
|
|
5865
|
+
confidence: confidenceForExtracted11("structural"),
|
|
5148
5866
|
evidence: { file: evidenceFile, line, snippet: key }
|
|
5149
5867
|
};
|
|
5150
5868
|
graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
|
|
@@ -5157,7 +5875,7 @@ async function addTerraformResources(graph, scanPath) {
|
|
|
5157
5875
|
|
|
5158
5876
|
// src/extract/infra/k8s.ts
|
|
5159
5877
|
import { promises as fs17 } from "fs";
|
|
5160
|
-
import
|
|
5878
|
+
import path33 from "path";
|
|
5161
5879
|
import { parseAllDocuments as parseAllDocuments2 } from "yaml";
|
|
5162
5880
|
var K8S_KIND_TO_INFRA_KIND = {
|
|
5163
5881
|
Service: "k8s-service",
|
|
@@ -5175,11 +5893,11 @@ async function walkYamlFiles2(start, depth = 0, max = 5) {
|
|
|
5175
5893
|
for (const entry of entries) {
|
|
5176
5894
|
if (entry.isDirectory()) {
|
|
5177
5895
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
5178
|
-
const child =
|
|
5896
|
+
const child = path33.join(start, entry.name);
|
|
5179
5897
|
if (await isPythonVenvDir(child)) continue;
|
|
5180
5898
|
out.push(...await walkYamlFiles2(child, depth + 1, max));
|
|
5181
|
-
} else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(
|
|
5182
|
-
out.push(
|
|
5899
|
+
} else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(path33.extname(entry.name))) {
|
|
5900
|
+
out.push(path33.join(start, entry.name));
|
|
5183
5901
|
}
|
|
5184
5902
|
}
|
|
5185
5903
|
return out;
|
|
@@ -5223,16 +5941,16 @@ async function addInfra(graph, scanPath, services) {
|
|
|
5223
5941
|
}
|
|
5224
5942
|
|
|
5225
5943
|
// src/extract/index.ts
|
|
5226
|
-
import
|
|
5944
|
+
import path35 from "path";
|
|
5227
5945
|
|
|
5228
5946
|
// src/extract/retire.ts
|
|
5229
5947
|
import { existsSync as existsSync2 } from "fs";
|
|
5230
|
-
import
|
|
5231
|
-
import { NodeType as
|
|
5948
|
+
import path34 from "path";
|
|
5949
|
+
import { NodeType as NodeType13, Provenance as Provenance14 } from "@neat.is/types";
|
|
5232
5950
|
function dropOrphanedFileNodes(graph) {
|
|
5233
5951
|
const orphans = [];
|
|
5234
5952
|
graph.forEachNode((id, attrs) => {
|
|
5235
|
-
if (attrs.type !==
|
|
5953
|
+
if (attrs.type !== NodeType13.FileNode) return;
|
|
5236
5954
|
if (graph.inboundEdges(id).length === 0 && graph.outboundEdges(id).length === 0) {
|
|
5237
5955
|
orphans.push(id);
|
|
5238
5956
|
}
|
|
@@ -5245,7 +5963,7 @@ function retireEdgesByFile(graph, file) {
|
|
|
5245
5963
|
const toDrop = [];
|
|
5246
5964
|
graph.forEachEdge((id, attrs) => {
|
|
5247
5965
|
const edge = attrs;
|
|
5248
|
-
if (edge.provenance !==
|
|
5966
|
+
if (edge.provenance !== Provenance14.EXTRACTED) return;
|
|
5249
5967
|
if (!edge.evidence?.file) return;
|
|
5250
5968
|
if (edge.evidence.file === normalized) toDrop.push(id);
|
|
5251
5969
|
});
|
|
@@ -5258,14 +5976,14 @@ function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = []) {
|
|
|
5258
5976
|
const bases = [scanPath, ...serviceDirs];
|
|
5259
5977
|
graph.forEachEdge((id, attrs) => {
|
|
5260
5978
|
const edge = attrs;
|
|
5261
|
-
if (edge.provenance !==
|
|
5979
|
+
if (edge.provenance !== Provenance14.EXTRACTED) return;
|
|
5262
5980
|
const evidenceFile = edge.evidence?.file;
|
|
5263
5981
|
if (!evidenceFile) return;
|
|
5264
|
-
if (
|
|
5982
|
+
if (path34.isAbsolute(evidenceFile)) {
|
|
5265
5983
|
if (!existsSync2(evidenceFile)) toDrop.push(id);
|
|
5266
5984
|
return;
|
|
5267
5985
|
}
|
|
5268
|
-
const found = bases.some((base) => existsSync2(
|
|
5986
|
+
const found = bases.some((base) => existsSync2(path34.join(base, evidenceFile)));
|
|
5269
5987
|
if (!found) toDrop.push(id);
|
|
5270
5988
|
});
|
|
5271
5989
|
for (const id of toDrop) graph.dropEdge(id);
|
|
@@ -5284,6 +6002,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
5284
6002
|
const importGraph = await addImports(graph, services);
|
|
5285
6003
|
const phase2 = await addDatabasesAndCompat(graph, services, scanPath);
|
|
5286
6004
|
const phase3 = await addConfigNodes(graph, services, scanPath);
|
|
6005
|
+
const routePhase = await addRoutes(graph, services);
|
|
5287
6006
|
const phase4 = await addCallEdges(graph, services);
|
|
5288
6007
|
const phase5 = await addInfra(graph, scanPath, services);
|
|
5289
6008
|
const ghostsRetired = retireExtractedEdgesByMissingFile(
|
|
@@ -5305,7 +6024,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
5305
6024
|
}
|
|
5306
6025
|
const droppedEntries = drainDroppedExtracted();
|
|
5307
6026
|
if (isRejectedLogEnabled() && opts.errorsPath && droppedEntries.length > 0) {
|
|
5308
|
-
const rejectedPath =
|
|
6027
|
+
const rejectedPath = path35.join(path35.dirname(opts.errorsPath), "rejected.ndjson");
|
|
5309
6028
|
try {
|
|
5310
6029
|
await writeRejectedExtracted(droppedEntries, rejectedPath);
|
|
5311
6030
|
} catch (err) {
|
|
@@ -5315,8 +6034,8 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
5315
6034
|
}
|
|
5316
6035
|
}
|
|
5317
6036
|
const result = {
|
|
5318
|
-
nodesAdded: phase1Nodes + fileEnum.nodesAdded + importGraph.nodesAdded + phase2.nodesAdded + phase3.nodesAdded + phase4.nodesAdded + phase5.nodesAdded,
|
|
5319
|
-
edgesAdded: fileEnum.edgesAdded + importGraph.edgesAdded + phase2.edgesAdded + phase3.edgesAdded + phase4.edgesAdded + phase5.edgesAdded,
|
|
6037
|
+
nodesAdded: phase1Nodes + fileEnum.nodesAdded + importGraph.nodesAdded + phase2.nodesAdded + phase3.nodesAdded + routePhase.nodesAdded + phase4.nodesAdded + phase5.nodesAdded,
|
|
6038
|
+
edgesAdded: fileEnum.edgesAdded + importGraph.edgesAdded + phase2.edgesAdded + phase3.edgesAdded + routePhase.edgesAdded + phase4.edgesAdded + phase5.edgesAdded,
|
|
5320
6039
|
frontiersPromoted,
|
|
5321
6040
|
extractionErrors: errorEntries.length,
|
|
5322
6041
|
errorEntries,
|
|
@@ -5341,10 +6060,10 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
5341
6060
|
import {
|
|
5342
6061
|
databaseId as databaseId3,
|
|
5343
6062
|
DivergenceResultSchema,
|
|
5344
|
-
EdgeType as
|
|
5345
|
-
NodeType as
|
|
6063
|
+
EdgeType as EdgeType15,
|
|
6064
|
+
NodeType as NodeType14,
|
|
5346
6065
|
parseEdgeId,
|
|
5347
|
-
Provenance as
|
|
6066
|
+
Provenance as Provenance15
|
|
5348
6067
|
} from "@neat.is/types";
|
|
5349
6068
|
function bucketKey(source, target, type) {
|
|
5350
6069
|
return `${type}|${source}|${target}`;
|
|
@@ -5358,17 +6077,17 @@ function bucketEdges(graph) {
|
|
|
5358
6077
|
const key = bucketKey(e.source, e.target, e.type);
|
|
5359
6078
|
const cur = buckets.get(key) ?? { source: e.source, target: e.target, type: e.type };
|
|
5360
6079
|
switch (provenance) {
|
|
5361
|
-
case
|
|
6080
|
+
case Provenance15.EXTRACTED:
|
|
5362
6081
|
cur.extracted = e;
|
|
5363
6082
|
break;
|
|
5364
|
-
case
|
|
6083
|
+
case Provenance15.OBSERVED:
|
|
5365
6084
|
cur.observed = e;
|
|
5366
6085
|
break;
|
|
5367
|
-
case
|
|
6086
|
+
case Provenance15.INFERRED:
|
|
5368
6087
|
cur.inferred = e;
|
|
5369
6088
|
break;
|
|
5370
6089
|
default:
|
|
5371
|
-
if (e.provenance ===
|
|
6090
|
+
if (e.provenance === Provenance15.STALE) cur.stale = e;
|
|
5372
6091
|
}
|
|
5373
6092
|
buckets.set(key, cur);
|
|
5374
6093
|
});
|
|
@@ -5377,7 +6096,7 @@ function bucketEdges(graph) {
|
|
|
5377
6096
|
function nodeIsFrontier(graph, nodeId) {
|
|
5378
6097
|
if (!graph.hasNode(nodeId)) return false;
|
|
5379
6098
|
const attrs = graph.getNodeAttributes(nodeId);
|
|
5380
|
-
return attrs.type ===
|
|
6099
|
+
return attrs.type === NodeType14.FrontierNode;
|
|
5381
6100
|
}
|
|
5382
6101
|
function clampConfidence(n) {
|
|
5383
6102
|
if (!Number.isFinite(n)) return 0;
|
|
@@ -5397,14 +6116,14 @@ function gradedConfidence(edge) {
|
|
|
5397
6116
|
return clampConfidence(confidenceForEdge(edge));
|
|
5398
6117
|
}
|
|
5399
6118
|
var OBSERVABLE_EDGE_TYPES = /* @__PURE__ */ new Set([
|
|
5400
|
-
|
|
5401
|
-
|
|
5402
|
-
|
|
5403
|
-
|
|
6119
|
+
EdgeType15.CALLS,
|
|
6120
|
+
EdgeType15.CONNECTS_TO,
|
|
6121
|
+
EdgeType15.PUBLISHES_TO,
|
|
6122
|
+
EdgeType15.CONSUMES_FROM
|
|
5404
6123
|
]);
|
|
5405
6124
|
function detectMissingDivergences(graph, bucket) {
|
|
5406
6125
|
const out = [];
|
|
5407
|
-
if (bucket.type ===
|
|
6126
|
+
if (bucket.type === EdgeType15.CONTAINS) return out;
|
|
5408
6127
|
if (bucket.extracted && !bucket.observed && OBSERVABLE_EDGE_TYPES.has(bucket.type)) {
|
|
5409
6128
|
if (!nodeIsFrontier(graph, bucket.target)) {
|
|
5410
6129
|
out.push({
|
|
@@ -5445,7 +6164,7 @@ function declaredHostFor(svc) {
|
|
|
5445
6164
|
function hasExtractedConfiguredBy(graph, svcId) {
|
|
5446
6165
|
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
5447
6166
|
const e = graph.getEdgeAttributes(edgeId);
|
|
5448
|
-
if (e.type ===
|
|
6167
|
+
if (e.type === EdgeType15.CONFIGURED_BY && e.provenance === Provenance15.EXTRACTED) {
|
|
5449
6168
|
return true;
|
|
5450
6169
|
}
|
|
5451
6170
|
}
|
|
@@ -5458,10 +6177,10 @@ function detectHostMismatch(graph, svcId, svc) {
|
|
|
5458
6177
|
const out = [];
|
|
5459
6178
|
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
5460
6179
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
5461
|
-
if (edge.type !==
|
|
5462
|
-
if (edge.provenance !==
|
|
6180
|
+
if (edge.type !== EdgeType15.CONNECTS_TO) continue;
|
|
6181
|
+
if (edge.provenance !== Provenance15.OBSERVED) continue;
|
|
5463
6182
|
const target = graph.getNodeAttributes(edge.target);
|
|
5464
|
-
if (target.type !==
|
|
6183
|
+
if (target.type !== NodeType14.DatabaseNode) continue;
|
|
5465
6184
|
const observedHost = target.host?.trim();
|
|
5466
6185
|
if (!observedHost) continue;
|
|
5467
6186
|
if (observedHost === declaredHost) continue;
|
|
@@ -5483,10 +6202,10 @@ function detectCompatDivergences(graph, svcId, svc) {
|
|
|
5483
6202
|
const deps = svc.dependencies ?? {};
|
|
5484
6203
|
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
5485
6204
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
5486
|
-
if (edge.type !==
|
|
5487
|
-
if (edge.provenance !==
|
|
6205
|
+
if (edge.type !== EdgeType15.CONNECTS_TO) continue;
|
|
6206
|
+
if (edge.provenance !== Provenance15.OBSERVED) continue;
|
|
5488
6207
|
const target = graph.getNodeAttributes(edge.target);
|
|
5489
|
-
if (target.type !==
|
|
6208
|
+
if (target.type !== NodeType14.DatabaseNode) continue;
|
|
5490
6209
|
for (const pair of compatPairs()) {
|
|
5491
6210
|
if (pair.engine !== target.engine) continue;
|
|
5492
6211
|
const declared = deps[pair.driver];
|
|
@@ -5564,7 +6283,7 @@ function computeDivergences(graph, opts = {}) {
|
|
|
5564
6283
|
}
|
|
5565
6284
|
graph.forEachNode((nodeId, attrs) => {
|
|
5566
6285
|
const n = attrs;
|
|
5567
|
-
if (n.type !==
|
|
6286
|
+
if (n.type !== NodeType14.ServiceNode) return;
|
|
5568
6287
|
const svc = n;
|
|
5569
6288
|
for (const d of detectHostMismatch(graph, nodeId, svc)) all.push(d);
|
|
5570
6289
|
for (const d of detectCompatDivergences(graph, nodeId, svc)) all.push(d);
|
|
@@ -5607,8 +6326,8 @@ function computeDivergences(graph, opts = {}) {
|
|
|
5607
6326
|
|
|
5608
6327
|
// src/persist.ts
|
|
5609
6328
|
import { promises as fs18 } from "fs";
|
|
5610
|
-
import
|
|
5611
|
-
import { Provenance as
|
|
6329
|
+
import path36 from "path";
|
|
6330
|
+
import { Provenance as Provenance16, observedEdgeId as observedEdgeId2 } from "@neat.is/types";
|
|
5612
6331
|
var SCHEMA_VERSION = 4;
|
|
5613
6332
|
function migrateV1ToV2(payload) {
|
|
5614
6333
|
const nodes = payload.graph.nodes;
|
|
@@ -5630,7 +6349,7 @@ function migrateV2ToV3(payload) {
|
|
|
5630
6349
|
for (const edge of edges) {
|
|
5631
6350
|
const attrs = edge.attributes;
|
|
5632
6351
|
if (!attrs || attrs.provenance !== "FRONTIER") continue;
|
|
5633
|
-
attrs.provenance =
|
|
6352
|
+
attrs.provenance = Provenance16.OBSERVED;
|
|
5634
6353
|
const type = typeof attrs.type === "string" ? attrs.type : void 0;
|
|
5635
6354
|
const source = typeof attrs.source === "string" ? attrs.source : void 0;
|
|
5636
6355
|
const target = typeof attrs.target === "string" ? attrs.target : void 0;
|
|
@@ -5644,7 +6363,7 @@ function migrateV2ToV3(payload) {
|
|
|
5644
6363
|
return { ...payload, schemaVersion: 3 };
|
|
5645
6364
|
}
|
|
5646
6365
|
async function ensureDir(filePath) {
|
|
5647
|
-
await fs18.mkdir(
|
|
6366
|
+
await fs18.mkdir(path36.dirname(filePath), { recursive: true });
|
|
5648
6367
|
}
|
|
5649
6368
|
async function saveGraphToDisk(graph, outPath) {
|
|
5650
6369
|
await ensureDir(outPath);
|
|
@@ -5800,23 +6519,23 @@ function canonicalJson(value) {
|
|
|
5800
6519
|
}
|
|
5801
6520
|
|
|
5802
6521
|
// src/projects.ts
|
|
5803
|
-
import
|
|
6522
|
+
import path37 from "path";
|
|
5804
6523
|
function pathsForProject(project, baseDir) {
|
|
5805
6524
|
if (project === DEFAULT_PROJECT) {
|
|
5806
6525
|
return {
|
|
5807
|
-
snapshotPath:
|
|
5808
|
-
errorsPath:
|
|
5809
|
-
staleEventsPath:
|
|
5810
|
-
embeddingsCachePath:
|
|
5811
|
-
policyViolationsPath:
|
|
6526
|
+
snapshotPath: path37.join(baseDir, "graph.json"),
|
|
6527
|
+
errorsPath: path37.join(baseDir, "errors.ndjson"),
|
|
6528
|
+
staleEventsPath: path37.join(baseDir, "stale-events.ndjson"),
|
|
6529
|
+
embeddingsCachePath: path37.join(baseDir, "embeddings.json"),
|
|
6530
|
+
policyViolationsPath: path37.join(baseDir, "policy-violations.ndjson")
|
|
5812
6531
|
};
|
|
5813
6532
|
}
|
|
5814
6533
|
return {
|
|
5815
|
-
snapshotPath:
|
|
5816
|
-
errorsPath:
|
|
5817
|
-
staleEventsPath:
|
|
5818
|
-
embeddingsCachePath:
|
|
5819
|
-
policyViolationsPath:
|
|
6534
|
+
snapshotPath: path37.join(baseDir, `${project}.json`),
|
|
6535
|
+
errorsPath: path37.join(baseDir, `errors.${project}.ndjson`),
|
|
6536
|
+
staleEventsPath: path37.join(baseDir, `stale-events.${project}.ndjson`),
|
|
6537
|
+
embeddingsCachePath: path37.join(baseDir, `embeddings.${project}.json`),
|
|
6538
|
+
policyViolationsPath: path37.join(baseDir, `policy-violations.${project}.ndjson`)
|
|
5820
6539
|
};
|
|
5821
6540
|
}
|
|
5822
6541
|
var Projects = class {
|
|
@@ -5857,7 +6576,7 @@ function parseExtraProjects(raw) {
|
|
|
5857
6576
|
// src/registry.ts
|
|
5858
6577
|
import { promises as fs20 } from "fs";
|
|
5859
6578
|
import os2 from "os";
|
|
5860
|
-
import
|
|
6579
|
+
import path38 from "path";
|
|
5861
6580
|
import {
|
|
5862
6581
|
RegistryFileSchema
|
|
5863
6582
|
} from "@neat.is/types";
|
|
@@ -5865,20 +6584,20 @@ var LOCK_TIMEOUT_MS = 5e3;
|
|
|
5865
6584
|
var LOCK_RETRY_MS = 50;
|
|
5866
6585
|
function neatHome() {
|
|
5867
6586
|
const override = process.env.NEAT_HOME;
|
|
5868
|
-
if (override && override.length > 0) return
|
|
5869
|
-
return
|
|
6587
|
+
if (override && override.length > 0) return path38.resolve(override);
|
|
6588
|
+
return path38.join(os2.homedir(), ".neat");
|
|
5870
6589
|
}
|
|
5871
6590
|
function registryPath() {
|
|
5872
|
-
return
|
|
6591
|
+
return path38.join(neatHome(), "projects.json");
|
|
5873
6592
|
}
|
|
5874
6593
|
function registryLockPath() {
|
|
5875
|
-
return
|
|
6594
|
+
return path38.join(neatHome(), "projects.json.lock");
|
|
5876
6595
|
}
|
|
5877
6596
|
function daemonPidPath() {
|
|
5878
|
-
return
|
|
6597
|
+
return path38.join(neatHome(), "neatd.pid");
|
|
5879
6598
|
}
|
|
5880
6599
|
function daemonsDir() {
|
|
5881
|
-
return
|
|
6600
|
+
return path38.join(neatHome(), "daemons");
|
|
5882
6601
|
}
|
|
5883
6602
|
function isFiniteInt(v) {
|
|
5884
6603
|
return typeof v === "number" && Number.isFinite(v);
|
|
@@ -5919,7 +6638,7 @@ async function discoverDaemons(probe = defaultDiscoveryProbe) {
|
|
|
5919
6638
|
const out = [];
|
|
5920
6639
|
for (const name of names) {
|
|
5921
6640
|
if (!name.endsWith(".json")) continue;
|
|
5922
|
-
const file =
|
|
6641
|
+
const file = path38.join(dir, name);
|
|
5923
6642
|
let raw;
|
|
5924
6643
|
try {
|
|
5925
6644
|
raw = await fs20.readFile(file, "utf8");
|
|
@@ -6040,7 +6759,7 @@ function lockHolderMessage(holder, lockPath, timeoutMs) {
|
|
|
6040
6759
|
}
|
|
6041
6760
|
}
|
|
6042
6761
|
async function normalizeProjectPath(input) {
|
|
6043
|
-
const resolved =
|
|
6762
|
+
const resolved = path38.resolve(input);
|
|
6044
6763
|
try {
|
|
6045
6764
|
return await fs20.realpath(resolved);
|
|
6046
6765
|
} catch {
|
|
@@ -6048,7 +6767,7 @@ async function normalizeProjectPath(input) {
|
|
|
6048
6767
|
}
|
|
6049
6768
|
}
|
|
6050
6769
|
async function writeAtomically(target, contents) {
|
|
6051
|
-
await fs20.mkdir(
|
|
6770
|
+
await fs20.mkdir(path38.dirname(target), { recursive: true });
|
|
6052
6771
|
const tmp = `${target}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
|
|
6053
6772
|
const fd = await fs20.open(tmp, "w");
|
|
6054
6773
|
try {
|
|
@@ -6061,7 +6780,7 @@ async function writeAtomically(target, contents) {
|
|
|
6061
6780
|
}
|
|
6062
6781
|
async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaultLockHolderProbe) {
|
|
6063
6782
|
const deadline = Date.now() + timeoutMs;
|
|
6064
|
-
await fs20.mkdir(
|
|
6783
|
+
await fs20.mkdir(path38.dirname(lockPath), { recursive: true });
|
|
6065
6784
|
let probedHolder = false;
|
|
6066
6785
|
while (true) {
|
|
6067
6786
|
try {
|
|
@@ -6257,13 +6976,13 @@ import { DivergenceTypeSchema, PoliciesCheckBodySchema, PolicySeveritySchema } f
|
|
|
6257
6976
|
|
|
6258
6977
|
// src/extend/index.ts
|
|
6259
6978
|
import { promises as fs22 } from "fs";
|
|
6260
|
-
import
|
|
6979
|
+
import path40 from "path";
|
|
6261
6980
|
import os3 from "os";
|
|
6262
6981
|
import { resolve as registryResolve, list as registryList } from "@neat.is/instrumentation-registry";
|
|
6263
6982
|
|
|
6264
6983
|
// src/installers/package-manager.ts
|
|
6265
6984
|
import { promises as fs21 } from "fs";
|
|
6266
|
-
import
|
|
6985
|
+
import path39 from "path";
|
|
6267
6986
|
import { spawn } from "child_process";
|
|
6268
6987
|
var LOCKFILE_PRIORITY = [
|
|
6269
6988
|
{ lockfile: "bun.lockb", pm: "bun", args: ["install", "--no-summary"] },
|
|
@@ -6285,22 +7004,22 @@ async function exists2(p) {
|
|
|
6285
7004
|
}
|
|
6286
7005
|
}
|
|
6287
7006
|
async function detectPackageManager(serviceDir) {
|
|
6288
|
-
let dir =
|
|
7007
|
+
let dir = path39.resolve(serviceDir);
|
|
6289
7008
|
const stops = /* @__PURE__ */ new Set();
|
|
6290
7009
|
for (let i = 0; i < 64; i++) {
|
|
6291
7010
|
if (stops.has(dir)) break;
|
|
6292
7011
|
stops.add(dir);
|
|
6293
7012
|
for (const candidate of LOCKFILE_PRIORITY) {
|
|
6294
|
-
const lockPath =
|
|
7013
|
+
const lockPath = path39.join(dir, candidate.lockfile);
|
|
6295
7014
|
if (await exists2(lockPath)) {
|
|
6296
7015
|
return { pm: candidate.pm, cwd: dir, args: [...candidate.args] };
|
|
6297
7016
|
}
|
|
6298
7017
|
}
|
|
6299
|
-
const parent =
|
|
7018
|
+
const parent = path39.dirname(dir);
|
|
6300
7019
|
if (parent === dir) break;
|
|
6301
7020
|
dir = parent;
|
|
6302
7021
|
}
|
|
6303
|
-
return { pm: "npm", cwd:
|
|
7022
|
+
return { pm: "npm", cwd: path39.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
|
|
6304
7023
|
}
|
|
6305
7024
|
async function runPackageManagerInstall(cmd) {
|
|
6306
7025
|
return new Promise((resolve) => {
|
|
@@ -6349,7 +7068,7 @@ async function fileExists2(p) {
|
|
|
6349
7068
|
}
|
|
6350
7069
|
}
|
|
6351
7070
|
async function readPackageJson(scanPath) {
|
|
6352
|
-
const pkgPath =
|
|
7071
|
+
const pkgPath = path40.join(scanPath, "package.json");
|
|
6353
7072
|
const raw = await fs22.readFile(pkgPath, "utf8");
|
|
6354
7073
|
return JSON.parse(raw);
|
|
6355
7074
|
}
|
|
@@ -6360,11 +7079,11 @@ async function findHookFiles(scanPath) {
|
|
|
6360
7079
|
).sort();
|
|
6361
7080
|
}
|
|
6362
7081
|
function extendLogPath() {
|
|
6363
|
-
return process.env.NEAT_EXTEND_LOG ??
|
|
7082
|
+
return process.env.NEAT_EXTEND_LOG ?? path40.join(os3.homedir(), ".neat", "extend-log.ndjson");
|
|
6364
7083
|
}
|
|
6365
7084
|
async function appendExtendLog(entry) {
|
|
6366
7085
|
const logPath = extendLogPath();
|
|
6367
|
-
await fs22.mkdir(
|
|
7086
|
+
await fs22.mkdir(path40.dirname(logPath), { recursive: true });
|
|
6368
7087
|
await fs22.appendFile(logPath, JSON.stringify(entry) + "\n", "utf8");
|
|
6369
7088
|
}
|
|
6370
7089
|
function splicedContent(fileContent, snippet2) {
|
|
@@ -6423,7 +7142,7 @@ function lookupInstrumentation(library, installedVersion) {
|
|
|
6423
7142
|
}
|
|
6424
7143
|
async function describeProjectInstrumentation(ctx) {
|
|
6425
7144
|
const hookFiles = await findHookFiles(ctx.scanPath);
|
|
6426
|
-
const envNeat = await fileExists2(
|
|
7145
|
+
const envNeat = await fileExists2(path40.join(ctx.scanPath, ".env.neat"));
|
|
6427
7146
|
const registryInstrPackages = new Set(
|
|
6428
7147
|
registryList().map((e) => e.instrumentation_package).filter((p) => !!p)
|
|
6429
7148
|
);
|
|
@@ -6445,16 +7164,16 @@ async function applyExtension(ctx, args, options) {
|
|
|
6445
7164
|
);
|
|
6446
7165
|
}
|
|
6447
7166
|
for (const file of hookFiles) {
|
|
6448
|
-
const content = await fs22.readFile(
|
|
7167
|
+
const content = await fs22.readFile(path40.join(ctx.scanPath, file), "utf8");
|
|
6449
7168
|
if (content.includes(args.registration_snippet)) {
|
|
6450
7169
|
return { library: args.library, filesTouched: [], depsAdded: [], installOutput: "", alreadyApplied: true };
|
|
6451
7170
|
}
|
|
6452
7171
|
}
|
|
6453
7172
|
const primaryFile = hookFiles[0];
|
|
6454
|
-
const primaryPath =
|
|
7173
|
+
const primaryPath = path40.join(ctx.scanPath, primaryFile);
|
|
6455
7174
|
const filesTouched = [];
|
|
6456
7175
|
const depsAdded = [];
|
|
6457
|
-
const pkgPath =
|
|
7176
|
+
const pkgPath = path40.join(ctx.scanPath, "package.json");
|
|
6458
7177
|
const pkg = await readPackageJson(ctx.scanPath);
|
|
6459
7178
|
if (!(pkg.dependencies ?? {})[args.instrumentation_package]) {
|
|
6460
7179
|
pkg.dependencies = { ...pkg.dependencies ?? {}, [args.instrumentation_package]: args.version };
|
|
@@ -6500,7 +7219,7 @@ async function dryRunExtension(ctx, args) {
|
|
|
6500
7219
|
};
|
|
6501
7220
|
}
|
|
6502
7221
|
for (const file of hookFiles) {
|
|
6503
|
-
const content = await fs22.readFile(
|
|
7222
|
+
const content = await fs22.readFile(path40.join(ctx.scanPath, file), "utf8");
|
|
6504
7223
|
if (content.includes(args.registration_snippet)) {
|
|
6505
7224
|
return {
|
|
6506
7225
|
library: args.library,
|
|
@@ -6522,7 +7241,7 @@ async function dryRunExtension(ctx, args) {
|
|
|
6522
7241
|
depsToAdd.push(`${args.instrumentation_package}@${args.version}`);
|
|
6523
7242
|
filesTouched.push("package.json");
|
|
6524
7243
|
}
|
|
6525
|
-
const hookContent = await fs22.readFile(
|
|
7244
|
+
const hookContent = await fs22.readFile(path40.join(ctx.scanPath, primaryFile), "utf8");
|
|
6526
7245
|
const patched = splicedContent(hookContent, args.registration_snippet);
|
|
6527
7246
|
if (patched) {
|
|
6528
7247
|
filesTouched.push(primaryFile);
|
|
@@ -6543,7 +7262,7 @@ async function rollbackExtension(ctx, args) {
|
|
|
6543
7262
|
if (!match) {
|
|
6544
7263
|
return { undone: false, message: "no apply found for library" };
|
|
6545
7264
|
}
|
|
6546
|
-
const pkgPath =
|
|
7265
|
+
const pkgPath = path40.join(ctx.scanPath, "package.json");
|
|
6547
7266
|
if (await fileExists2(pkgPath)) {
|
|
6548
7267
|
const pkg = await readPackageJson(ctx.scanPath);
|
|
6549
7268
|
if (pkg.dependencies?.[match.instrumentation_package]) {
|
|
@@ -6554,7 +7273,7 @@ async function rollbackExtension(ctx, args) {
|
|
|
6554
7273
|
}
|
|
6555
7274
|
const hookFiles = await findHookFiles(ctx.scanPath);
|
|
6556
7275
|
for (const file of hookFiles) {
|
|
6557
|
-
const filePath =
|
|
7276
|
+
const filePath = path40.join(ctx.scanPath, file);
|
|
6558
7277
|
const content = await fs22.readFile(filePath, "utf8");
|
|
6559
7278
|
if (content.includes(match.registration_snippet)) {
|
|
6560
7279
|
const filtered = content.split("\n").filter((line) => !line.includes(match.registration_snippet)).join("\n");
|
|
@@ -7362,4 +8081,4 @@ export {
|
|
|
7362
8081
|
pruneRegistry,
|
|
7363
8082
|
buildApi
|
|
7364
8083
|
};
|
|
7365
|
-
//# sourceMappingURL=chunk-
|
|
8084
|
+
//# sourceMappingURL=chunk-WZYH5DVG.js.map
|