@neat.is/core 0.7.0 → 0.7.1
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-Q6DPK3RA.js → chunk-6H757ZNM.js} +2 -2
- package/dist/{chunk-UBQ4ZZT3.js → chunk-MDBE23Y3.js} +3 -3
- package/dist/{chunk-N5L3RBGP.js → chunk-P2ZEKJ35.js} +44 -2
- package/dist/chunk-P2ZEKJ35.js.map +1 -0
- package/dist/{chunk-5RIL3U5A.js → chunk-RR4LWQQB.js} +516 -229
- package/dist/chunk-RR4LWQQB.js.map +1 -0
- package/dist/cli.cjs +997 -649
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +25 -7
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +810 -480
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +4 -4
- package/dist/neatd.cjs +819 -489
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +3 -3
- package/dist/{otel-grpc-ZNC2QED2.js → otel-grpc-APVZSB6W.js} +3 -3
- package/dist/server.cjs +840 -510
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +3 -3
- package/package.json +2 -2
- package/dist/chunk-5RIL3U5A.js.map +0 -1
- package/dist/chunk-N5L3RBGP.js.map +0 -1
- /package/dist/{chunk-Q6DPK3RA.js.map → chunk-6H757ZNM.js.map} +0 -0
- /package/dist/{chunk-UBQ4ZZT3.js.map → chunk-MDBE23Y3.js.map} +0 -0
- /package/dist/{otel-grpc-ZNC2QED2.js.map → otel-grpc-APVZSB6W.js.map} +0 -0
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
|
+
columnsFromSqlStatement,
|
|
2
3
|
mountBearerAuth,
|
|
3
4
|
readAuthEnv,
|
|
4
5
|
tableFromSqlStatement
|
|
5
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-P2ZEKJ35.js";
|
|
6
7
|
|
|
7
8
|
// src/graph.ts
|
|
8
9
|
import GraphDefault from "graphology";
|
|
@@ -862,7 +863,7 @@ import {
|
|
|
862
863
|
GraphEdgeSchema,
|
|
863
864
|
GraphNodeSchema,
|
|
864
865
|
NodeType as NodeType4,
|
|
865
|
-
Provenance as
|
|
866
|
+
Provenance as Provenance4,
|
|
866
867
|
confidenceForObservedSignal,
|
|
867
868
|
databaseId,
|
|
868
869
|
localDatabaseId,
|
|
@@ -1945,6 +1946,41 @@ async function addRoutes(graph, services) {
|
|
|
1945
1946
|
return { nodesAdded, edgesAdded };
|
|
1946
1947
|
}
|
|
1947
1948
|
|
|
1949
|
+
// src/columns.ts
|
|
1950
|
+
import { Provenance as Provenance3 } from "@neat.is/types";
|
|
1951
|
+
var OBSERVED_COLUMN_CONFIDENCE = 0.9;
|
|
1952
|
+
function normalizeProvenances(provenances) {
|
|
1953
|
+
return [...new Set(provenances)].sort();
|
|
1954
|
+
}
|
|
1955
|
+
function foldColumns(existing, names, provenance, confidence) {
|
|
1956
|
+
const out = (existing ?? []).map((c) => ({
|
|
1957
|
+
...c,
|
|
1958
|
+
provenances: [...c.provenances]
|
|
1959
|
+
}));
|
|
1960
|
+
const byName = new Map(out.map((c) => [c.name, c]));
|
|
1961
|
+
for (const raw of names) {
|
|
1962
|
+
const name = raw.toLowerCase();
|
|
1963
|
+
const prior = byName.get(name);
|
|
1964
|
+
if (!prior) {
|
|
1965
|
+
const col = { name, provenances: [provenance], confidence };
|
|
1966
|
+
byName.set(name, col);
|
|
1967
|
+
out.push(col);
|
|
1968
|
+
continue;
|
|
1969
|
+
}
|
|
1970
|
+
if (!prior.provenances.includes(provenance)) {
|
|
1971
|
+
prior.provenances = normalizeProvenances([...prior.provenances, provenance]);
|
|
1972
|
+
}
|
|
1973
|
+
if (confidence > prior.confidence) prior.confidence = confidence;
|
|
1974
|
+
}
|
|
1975
|
+
return out;
|
|
1976
|
+
}
|
|
1977
|
+
function columnIsDeclared(col) {
|
|
1978
|
+
return col.provenances.includes(Provenance3.EXTRACTED);
|
|
1979
|
+
}
|
|
1980
|
+
function columnIsObserved(col) {
|
|
1981
|
+
return col.provenances.includes(Provenance3.OBSERVED);
|
|
1982
|
+
}
|
|
1983
|
+
|
|
1948
1984
|
// src/ingest.ts
|
|
1949
1985
|
var HOUR_MS = 60 * 60 * 1e3;
|
|
1950
1986
|
var DAY_MS = 24 * HOUR_MS;
|
|
@@ -2283,7 +2319,7 @@ function ensureObservedFileNode(graph, serviceName, serviceNodeId, callSite) {
|
|
|
2283
2319
|
source: serviceNodeId,
|
|
2284
2320
|
target: fileNodeId,
|
|
2285
2321
|
type: EdgeType4.CONTAINS,
|
|
2286
|
-
provenance:
|
|
2322
|
+
provenance: Provenance4.OBSERVED
|
|
2287
2323
|
};
|
|
2288
2324
|
graph.addEdgeWithKey(containsId, serviceNodeId, fileNodeId, edge);
|
|
2289
2325
|
}
|
|
@@ -2328,7 +2364,7 @@ function ensureObservedSymbolNode(graph, fileNodeId, service, relPath, fn, line)
|
|
|
2328
2364
|
source: fileNodeId,
|
|
2329
2365
|
target: sid,
|
|
2330
2366
|
type: EdgeType4.CONTAINS,
|
|
2331
|
-
provenance:
|
|
2367
|
+
provenance: Provenance4.OBSERVED
|
|
2332
2368
|
};
|
|
2333
2369
|
graph.addEdgeWithKey(containsId, fileNodeId, sid, edge);
|
|
2334
2370
|
}
|
|
@@ -2543,6 +2579,21 @@ function ensureInfraNode(graph, kind, name, provider) {
|
|
|
2543
2579
|
graph.addNode(id, node);
|
|
2544
2580
|
return id;
|
|
2545
2581
|
}
|
|
2582
|
+
var COLUMN_BEARING_INFRA_KINDS = /* @__PURE__ */ new Set(["sql-table", "supabase-table"]);
|
|
2583
|
+
function mergeColumnsAt(graph, tableNodeId, columns, provenance, confidence) {
|
|
2584
|
+
if (!columns || columns.length === 0 || !graph.hasNode(tableNodeId)) return;
|
|
2585
|
+
const node = graph.getNodeAttributes(tableNodeId);
|
|
2586
|
+
if (node.type !== NodeType4.InfraNode || !node.kind || !COLUMN_BEARING_INFRA_KINDS.has(node.kind)) {
|
|
2587
|
+
return;
|
|
2588
|
+
}
|
|
2589
|
+
graph.replaceNodeAttributes(tableNodeId, {
|
|
2590
|
+
...node,
|
|
2591
|
+
columns: foldColumns(node.columns, columns, provenance, confidence)
|
|
2592
|
+
});
|
|
2593
|
+
}
|
|
2594
|
+
function mergeObservedColumns(graph, tableNodeId, columns) {
|
|
2595
|
+
mergeColumnsAt(graph, tableNodeId, columns, Provenance4.OBSERVED, OBSERVED_COLUMN_CONFIDENCE);
|
|
2596
|
+
}
|
|
2546
2597
|
function ensureDatabaseNode(graph, host, engine) {
|
|
2547
2598
|
const id = databaseId(host);
|
|
2548
2599
|
if (graph.hasNode(id)) return id;
|
|
@@ -2586,7 +2637,7 @@ function findDeclaredDatabaseForService(graph, serviceNodeId, engine) {
|
|
|
2586
2637
|
if (!graph.hasNode(src)) continue;
|
|
2587
2638
|
for (const edgeId of graph.outboundEdges(src)) {
|
|
2588
2639
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
2589
|
-
if (edge.type !== EdgeType4.CONNECTS_TO || edge.provenance !==
|
|
2640
|
+
if (edge.type !== EdgeType4.CONNECTS_TO || edge.provenance !== Provenance4.EXTRACTED) continue;
|
|
2590
2641
|
if (!graph.hasNode(edge.target)) continue;
|
|
2591
2642
|
const target = graph.getNodeAttributes(edge.target);
|
|
2592
2643
|
if (target.type !== NodeType4.DatabaseNode || target.engine !== engine) continue;
|
|
@@ -2628,7 +2679,7 @@ function upsertObservedEdge(graph, type, source, target, ts, isError = false, ev
|
|
|
2628
2679
|
};
|
|
2629
2680
|
const updated = {
|
|
2630
2681
|
...existing,
|
|
2631
|
-
provenance:
|
|
2682
|
+
provenance: Provenance4.OBSERVED,
|
|
2632
2683
|
lastObserved: ts,
|
|
2633
2684
|
callCount: newSpanCount,
|
|
2634
2685
|
signal: newSignal,
|
|
@@ -2649,7 +2700,7 @@ function upsertObservedEdge(graph, type, source, target, ts, isError = false, ev
|
|
|
2649
2700
|
source,
|
|
2650
2701
|
target,
|
|
2651
2702
|
type,
|
|
2652
|
-
provenance:
|
|
2703
|
+
provenance: Provenance4.OBSERVED,
|
|
2653
2704
|
confidence: confidenceForObservedSignal(signal),
|
|
2654
2705
|
lastObserved: ts,
|
|
2655
2706
|
callCount: 1,
|
|
@@ -2672,7 +2723,7 @@ function stitchTrace(graph, sourceServiceId, ts) {
|
|
|
2672
2723
|
const outbound = graph.outboundEdges(nodeId);
|
|
2673
2724
|
for (const edgeId of outbound) {
|
|
2674
2725
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
2675
|
-
if (edge.provenance !==
|
|
2726
|
+
if (edge.provenance !== Provenance4.EXTRACTED) continue;
|
|
2676
2727
|
if (!STITCH_EDGE_TYPES.has(edge.type)) continue;
|
|
2677
2728
|
if (graph.hasEdge(observedEdgeId(edge.source, edge.target, edge.type))) continue;
|
|
2678
2729
|
upsertInferredEdge(graph, edge.type, edge.source, edge.target, ts);
|
|
@@ -2696,7 +2747,7 @@ function upsertInferredEdge(graph, type, source, target, ts) {
|
|
|
2696
2747
|
source,
|
|
2697
2748
|
target,
|
|
2698
2749
|
type,
|
|
2699
|
-
provenance:
|
|
2750
|
+
provenance: Provenance4.INFERRED,
|
|
2700
2751
|
confidence: INFERRED_CONFIDENCE,
|
|
2701
2752
|
lastObserved: ts
|
|
2702
2753
|
};
|
|
@@ -2916,6 +2967,7 @@ async function handleSpan(ctx, span) {
|
|
|
2916
2967
|
isError,
|
|
2917
2968
|
callSiteEvidence
|
|
2918
2969
|
);
|
|
2970
|
+
mergeObservedColumns(ctx.graph, tableId, span.dbColumns);
|
|
2919
2971
|
}
|
|
2920
2972
|
}
|
|
2921
2973
|
} else if (span.messagingSystem && span.messagingDestination && spanMintsMessagingEdge(span.kind)) {
|
|
@@ -3037,7 +3089,7 @@ async function handleSpan(ctx, span) {
|
|
|
3037
3089
|
}
|
|
3038
3090
|
}
|
|
3039
3091
|
}
|
|
3040
|
-
if (span.httpRoute && (span.kind === 2 || span.kind === 0 || span.kind === void 0)) {
|
|
3092
|
+
if (span.httpRoute && (span.kind === 2 || span.kind === 1 || span.kind === 0 || span.kind === void 0)) {
|
|
3041
3093
|
const routeNodeId = findRouteNodeByHttpRoute(
|
|
3042
3094
|
ctx.graph,
|
|
3043
3095
|
span.service,
|
|
@@ -3128,7 +3180,7 @@ function rewireFrontierEdges(graph, frontierId2, serviceId7) {
|
|
|
3128
3180
|
}
|
|
3129
3181
|
function rebuildEdge(graph, edge, newSource, newTarget, oldEdgeId) {
|
|
3130
3182
|
graph.dropEdge(oldEdgeId);
|
|
3131
|
-
const newId = edge.provenance ===
|
|
3183
|
+
const newId = edge.provenance === Provenance4.OBSERVED ? observedEdgeId(newSource, newTarget, edge.type) : edge.provenance === Provenance4.INFERRED ? inferredEdgeId(newSource, newTarget, edge.type) : extractedEdgeId4(newSource, newTarget, edge.type);
|
|
3132
3184
|
if (graph.hasEdge(newId)) {
|
|
3133
3185
|
const existing = graph.getEdgeAttributes(newId);
|
|
3134
3186
|
const merged = {
|
|
@@ -3162,12 +3214,12 @@ async function markStaleEdges(graph, options = {}) {
|
|
|
3162
3214
|
const project = options.project ?? DEFAULT_PROJECT;
|
|
3163
3215
|
graph.forEachEdge((id, attrs) => {
|
|
3164
3216
|
const e = attrs;
|
|
3165
|
-
if (e.provenance !==
|
|
3217
|
+
if (e.provenance !== Provenance4.OBSERVED) return;
|
|
3166
3218
|
if (!e.lastObserved) return;
|
|
3167
3219
|
const threshold = thresholdForEdgeType(e.type, thresholds);
|
|
3168
3220
|
const age = now - new Date(e.lastObserved).getTime();
|
|
3169
3221
|
if (age > threshold) {
|
|
3170
|
-
const updated = { ...e, provenance:
|
|
3222
|
+
const updated = { ...e, provenance: Provenance4.STALE, confidence: 0.3 };
|
|
3171
3223
|
graph.replaceEdgeAttributes(id, updated);
|
|
3172
3224
|
events.push({
|
|
3173
3225
|
edgeId: id,
|
|
@@ -3184,8 +3236,8 @@ async function markStaleEdges(graph, options = {}) {
|
|
|
3184
3236
|
project,
|
|
3185
3237
|
payload: {
|
|
3186
3238
|
edgeId: id,
|
|
3187
|
-
from:
|
|
3188
|
-
to:
|
|
3239
|
+
from: Provenance4.OBSERVED,
|
|
3240
|
+
to: Provenance4.STALE
|
|
3189
3241
|
}
|
|
3190
3242
|
});
|
|
3191
3243
|
}
|
|
@@ -3338,7 +3390,7 @@ import {
|
|
|
3338
3390
|
NodeType as NodeType5,
|
|
3339
3391
|
ObservedDependenciesResultSchema,
|
|
3340
3392
|
PROV_RANK,
|
|
3341
|
-
Provenance as
|
|
3393
|
+
Provenance as Provenance5,
|
|
3342
3394
|
RootCauseResultSchema,
|
|
3343
3395
|
TransitiveDependenciesResultSchema
|
|
3344
3396
|
} from "@neat.is/types";
|
|
@@ -3451,19 +3503,19 @@ function confidenceFromMix(edges, now = Date.now()) {
|
|
|
3451
3503
|
function longestIncomingWalk(graph, start, maxDepth) {
|
|
3452
3504
|
let best = { path: [start], edges: [] };
|
|
3453
3505
|
const visited = /* @__PURE__ */ new Set([start]);
|
|
3454
|
-
function step(node,
|
|
3455
|
-
if (
|
|
3456
|
-
best = { path: [...
|
|
3506
|
+
function step(node, path55, edges) {
|
|
3507
|
+
if (path55.length > best.path.length) {
|
|
3508
|
+
best = { path: [...path55], edges: [...edges] };
|
|
3457
3509
|
}
|
|
3458
|
-
if (
|
|
3510
|
+
if (path55.length - 1 >= maxDepth) return;
|
|
3459
3511
|
const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
|
|
3460
3512
|
for (const [srcId, edge] of incoming) {
|
|
3461
3513
|
if (visited.has(srcId)) continue;
|
|
3462
3514
|
visited.add(srcId);
|
|
3463
|
-
|
|
3515
|
+
path55.push(srcId);
|
|
3464
3516
|
edges.push(edge);
|
|
3465
|
-
step(srcId,
|
|
3466
|
-
|
|
3517
|
+
step(srcId, path55, edges);
|
|
3518
|
+
path55.pop();
|
|
3467
3519
|
edges.pop();
|
|
3468
3520
|
visited.delete(srcId);
|
|
3469
3521
|
}
|
|
@@ -3614,7 +3666,7 @@ function rootCauseFromIncidents(nodeId, incidents, errorEvent) {
|
|
|
3614
3666
|
const loc = localizeFromIncidents(nodeId, incidents, errorEvent);
|
|
3615
3667
|
if (!loc) return null;
|
|
3616
3668
|
const traversalPath = loc.fileNode ? [nodeId, loc.fileNode] : [nodeId];
|
|
3617
|
-
const edgeProvenances = loc.fileNode ? [
|
|
3669
|
+
const edgeProvenances = loc.fileNode ? [Provenance5.OBSERVED] : [];
|
|
3618
3670
|
return RootCauseResultSchema.parse({
|
|
3619
3671
|
rootCauseNode: loc.rootCauseNode,
|
|
3620
3672
|
rootCauseReason: loc.rootCauseReason,
|
|
@@ -3670,26 +3722,26 @@ function dominantFailingCall(graph, serviceId7, visited) {
|
|
|
3670
3722
|
return best;
|
|
3671
3723
|
}
|
|
3672
3724
|
function followFailingCallChain(graph, originServiceId, maxDepth) {
|
|
3673
|
-
const
|
|
3725
|
+
const path55 = [originServiceId];
|
|
3674
3726
|
const edges = [];
|
|
3675
3727
|
const visited = /* @__PURE__ */ new Set([originServiceId]);
|
|
3676
3728
|
let current = originServiceId;
|
|
3677
3729
|
for (let depth = 0; depth < maxDepth; depth++) {
|
|
3678
3730
|
const hop = dominantFailingCall(graph, current, visited);
|
|
3679
3731
|
if (!hop) break;
|
|
3680
|
-
|
|
3732
|
+
path55.push(hop.nextService);
|
|
3681
3733
|
edges.push(hop.edge);
|
|
3682
3734
|
visited.add(hop.nextService);
|
|
3683
3735
|
current = hop.nextService;
|
|
3684
3736
|
}
|
|
3685
3737
|
if (edges.length === 0) return null;
|
|
3686
|
-
return { path:
|
|
3738
|
+
return { path: path55, edges, culprit: current };
|
|
3687
3739
|
}
|
|
3688
3740
|
function crossServiceRootCause(graph, originId, incidents, errorEvent) {
|
|
3689
3741
|
const chain = followFailingCallChain(graph, originId, ROOT_CAUSE_MAX_DEPTH);
|
|
3690
3742
|
if (!chain) return null;
|
|
3691
3743
|
const culprit = chain.culprit;
|
|
3692
|
-
const
|
|
3744
|
+
const path55 = [...chain.path];
|
|
3693
3745
|
const edgeProvenances = chain.edges.map((e) => e.provenance);
|
|
3694
3746
|
const baseConfidence = confidenceFromMix(chain.edges);
|
|
3695
3747
|
const confidence = Math.max(0, Math.min(1, baseConfidence * INCIDENT_ROOT_CAUSE_CONFIDENCE));
|
|
@@ -3697,14 +3749,14 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
|
|
|
3697
3749
|
if (loc) {
|
|
3698
3750
|
let rootCauseNode = culprit;
|
|
3699
3751
|
if (loc.fileNode) {
|
|
3700
|
-
|
|
3701
|
-
edgeProvenances.push(
|
|
3752
|
+
path55.push(loc.fileNode);
|
|
3753
|
+
edgeProvenances.push(Provenance5.OBSERVED);
|
|
3702
3754
|
rootCauseNode = loc.fileNode;
|
|
3703
3755
|
}
|
|
3704
3756
|
return RootCauseResultSchema.parse({
|
|
3705
3757
|
rootCauseNode,
|
|
3706
3758
|
rootCauseReason: loc.rootCauseReason,
|
|
3707
|
-
traversalPath:
|
|
3759
|
+
traversalPath: path55,
|
|
3708
3760
|
edgeProvenances,
|
|
3709
3761
|
confidence,
|
|
3710
3762
|
...loc.fixRecommendation ? { fixRecommendation: loc.fixRecommendation } : {}
|
|
@@ -3716,7 +3768,7 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
|
|
|
3716
3768
|
return RootCauseResultSchema.parse({
|
|
3717
3769
|
rootCauseNode: culprit,
|
|
3718
3770
|
rootCauseReason: `${culpritName} is failing downstream calls (${errs} observed error${errs === 1 ? "" : "s"})`,
|
|
3719
|
-
traversalPath:
|
|
3771
|
+
traversalPath: path55,
|
|
3720
3772
|
edgeProvenances,
|
|
3721
3773
|
confidence,
|
|
3722
3774
|
fixRecommendation: `Inspect ${culpritName}'s failing handler`
|
|
@@ -3832,12 +3884,12 @@ function getObservedDependencies(graph, nodeId) {
|
|
|
3832
3884
|
for (const edgeId of graph.outboundEdges(src)) {
|
|
3833
3885
|
const e = graph.getEdgeAttributes(edgeId);
|
|
3834
3886
|
if (e.type === EdgeType5.CONTAINS) continue;
|
|
3835
|
-
if (e.provenance ===
|
|
3887
|
+
if (e.provenance === Provenance5.OBSERVED) {
|
|
3836
3888
|
if (!seenEdge.has(e.id)) {
|
|
3837
3889
|
seenEdge.add(e.id);
|
|
3838
3890
|
dependencies.push(e);
|
|
3839
3891
|
}
|
|
3840
|
-
} else if (e.provenance ===
|
|
3892
|
+
} else if (e.provenance === Provenance5.EXTRACTED) {
|
|
3841
3893
|
hasExtractedOutbound = true;
|
|
3842
3894
|
}
|
|
3843
3895
|
}
|
|
@@ -3847,7 +3899,7 @@ function getObservedDependencies(graph, nodeId) {
|
|
|
3847
3899
|
for (const edgeId of graph.inboundEdges(tgt)) {
|
|
3848
3900
|
const e = graph.getEdgeAttributes(edgeId);
|
|
3849
3901
|
if (e.type === EdgeType5.CONTAINS) continue;
|
|
3850
|
-
if (e.provenance ===
|
|
3902
|
+
if (e.provenance === Provenance5.OBSERVED) inboundObservedCount += 1;
|
|
3851
3903
|
}
|
|
3852
3904
|
}
|
|
3853
3905
|
dependencies.sort(
|
|
@@ -4457,7 +4509,7 @@ import TypeScript from "tree-sitter-typescript";
|
|
|
4457
4509
|
import {
|
|
4458
4510
|
EdgeType as EdgeType6,
|
|
4459
4511
|
NodeType as NodeType9,
|
|
4460
|
-
Provenance as
|
|
4512
|
+
Provenance as Provenance6,
|
|
4461
4513
|
confidenceForExtracted as confidenceForExtracted3,
|
|
4462
4514
|
extractedEdgeId as extractedEdgeId5,
|
|
4463
4515
|
symbolId as symbolId2
|
|
@@ -4552,7 +4604,7 @@ function disambiguate(defs) {
|
|
|
4552
4604
|
}
|
|
4553
4605
|
async function addSymbols(graph, services) {
|
|
4554
4606
|
const parsers = /* @__PURE__ */ new Map();
|
|
4555
|
-
const
|
|
4607
|
+
const parserForExt2 = (ext) => {
|
|
4556
4608
|
const grammar = GRAMMAR_BY_EXT[ext];
|
|
4557
4609
|
if (!grammar) return null;
|
|
4558
4610
|
let parser = parsers.get(ext);
|
|
@@ -4568,7 +4620,7 @@ async function addSymbols(graph, services) {
|
|
|
4568
4620
|
for (const service of services) {
|
|
4569
4621
|
const files = await loadSourceFiles(service.dir);
|
|
4570
4622
|
for (const file of files) {
|
|
4571
|
-
const parser =
|
|
4623
|
+
const parser = parserForExt2(path14.extname(file.path));
|
|
4572
4624
|
if (!parser) continue;
|
|
4573
4625
|
const relPath = toPosix(path14.relative(service.dir, file.path));
|
|
4574
4626
|
let defs;
|
|
@@ -4611,7 +4663,7 @@ async function addSymbols(graph, services) {
|
|
|
4611
4663
|
source: fileNodeId,
|
|
4612
4664
|
target: sid,
|
|
4613
4665
|
type: EdgeType6.CONTAINS,
|
|
4614
|
-
provenance:
|
|
4666
|
+
provenance: Provenance6.EXTRACTED,
|
|
4615
4667
|
confidence: confidenceForExtracted3("structural"),
|
|
4616
4668
|
evidence: {
|
|
4617
4669
|
file: relPath,
|
|
@@ -4634,7 +4686,7 @@ import Parser4 from "tree-sitter";
|
|
|
4634
4686
|
import {
|
|
4635
4687
|
EdgeType as EdgeType8,
|
|
4636
4688
|
NodeType as NodeType10,
|
|
4637
|
-
Provenance as
|
|
4689
|
+
Provenance as Provenance8,
|
|
4638
4690
|
confidenceForExtracted as confidenceForExtracted5,
|
|
4639
4691
|
extractedEdgeId as extractedEdgeId7,
|
|
4640
4692
|
symbolId as symbolId3
|
|
@@ -4649,7 +4701,7 @@ import Python2 from "tree-sitter-python";
|
|
|
4649
4701
|
import Go2 from "tree-sitter-go";
|
|
4650
4702
|
import {
|
|
4651
4703
|
EdgeType as EdgeType7,
|
|
4652
|
-
Provenance as
|
|
4704
|
+
Provenance as Provenance7,
|
|
4653
4705
|
confidenceForExtracted as confidenceForExtracted4,
|
|
4654
4706
|
extractedEdgeId as extractedEdgeId6,
|
|
4655
4707
|
fileId as fileId3
|
|
@@ -4936,7 +4988,7 @@ function emitImportEdge(graph, serviceName, importerFileId, importerRelPath, imp
|
|
|
4936
4988
|
source: importerFileId,
|
|
4937
4989
|
target: importeeFileId,
|
|
4938
4990
|
type: EdgeType7.IMPORTS,
|
|
4939
|
-
provenance:
|
|
4991
|
+
provenance: Provenance7.EXTRACTED,
|
|
4940
4992
|
confidence: confidenceForExtracted4("structural"),
|
|
4941
4993
|
evidence: { file: importerRelPath, line, snippet: snippet2 }
|
|
4942
4994
|
};
|
|
@@ -5115,7 +5167,7 @@ function stringInner(node) {
|
|
|
5115
5167
|
}
|
|
5116
5168
|
async function addSymbolEdges(graph, services) {
|
|
5117
5169
|
const parsers = /* @__PURE__ */ new Map();
|
|
5118
|
-
const
|
|
5170
|
+
const parserForExt2 = (ext) => {
|
|
5119
5171
|
const grammar = GRAMMAR_BY_EXT[ext];
|
|
5120
5172
|
if (!grammar) return null;
|
|
5121
5173
|
let parser = parsers.get(ext);
|
|
@@ -5132,7 +5184,7 @@ async function addSymbolEdges(graph, services) {
|
|
|
5132
5184
|
const tsPaths = await loadTsPathConfig(service.dir);
|
|
5133
5185
|
const files = await loadSourceFiles(service.dir);
|
|
5134
5186
|
for (const file of files) {
|
|
5135
|
-
const parser =
|
|
5187
|
+
const parser = parserForExt2(path16.extname(file.path));
|
|
5136
5188
|
if (!parser) continue;
|
|
5137
5189
|
const relPath = toPosix(path16.relative(service.dir, file.path));
|
|
5138
5190
|
const fileDir = path16.dirname(file.path);
|
|
@@ -5252,7 +5304,7 @@ async function addSymbolEdges(graph, services) {
|
|
|
5252
5304
|
source: req.sourceSid,
|
|
5253
5305
|
target: targetSid,
|
|
5254
5306
|
type: req.edgeType,
|
|
5255
|
-
provenance:
|
|
5307
|
+
provenance: Provenance8.EXTRACTED,
|
|
5256
5308
|
confidence: confidenceForExtracted5("structural"),
|
|
5257
5309
|
evidence: {
|
|
5258
5310
|
file: relPath,
|
|
@@ -5273,7 +5325,7 @@ import path24 from "path";
|
|
|
5273
5325
|
import {
|
|
5274
5326
|
EdgeType as EdgeType9,
|
|
5275
5327
|
NodeType as NodeType11,
|
|
5276
|
-
Provenance as
|
|
5328
|
+
Provenance as Provenance9,
|
|
5277
5329
|
configId,
|
|
5278
5330
|
databaseId as databaseId2,
|
|
5279
5331
|
confidenceForExtracted as confidenceForExtracted6
|
|
@@ -5917,7 +5969,7 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
|
|
|
5917
5969
|
source: fileNodeId,
|
|
5918
5970
|
target: dbNode.id,
|
|
5919
5971
|
type: EdgeType9.CONNECTS_TO,
|
|
5920
|
-
provenance:
|
|
5972
|
+
provenance: Provenance9.EXTRACTED,
|
|
5921
5973
|
confidence: confidenceForExtracted6("structural"),
|
|
5922
5974
|
evidence: { file: evidenceFile }
|
|
5923
5975
|
};
|
|
@@ -5947,7 +5999,7 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
|
|
|
5947
5999
|
source: service.node.id,
|
|
5948
6000
|
target: cfgId,
|
|
5949
6001
|
type: EdgeType9.CONFIGURED_BY,
|
|
5950
|
-
provenance:
|
|
6002
|
+
provenance: Provenance9.EXTRACTED,
|
|
5951
6003
|
confidence: confidenceForExtracted6("structural"),
|
|
5952
6004
|
evidence: { file: toPosix(relPath) }
|
|
5953
6005
|
};
|
|
@@ -5982,7 +6034,7 @@ import path25 from "path";
|
|
|
5982
6034
|
import {
|
|
5983
6035
|
EdgeType as EdgeType10,
|
|
5984
6036
|
NodeType as NodeType12,
|
|
5985
|
-
Provenance as
|
|
6037
|
+
Provenance as Provenance10,
|
|
5986
6038
|
configId as configId2,
|
|
5987
6039
|
confidenceForExtracted as confidenceForExtracted7
|
|
5988
6040
|
} from "@neat.is/types";
|
|
@@ -6036,7 +6088,7 @@ async function addConfigNodes(graph, services, scanPath) {
|
|
|
6036
6088
|
source: fileNodeId,
|
|
6037
6089
|
target: node.id,
|
|
6038
6090
|
type: EdgeType10.CONFIGURED_BY,
|
|
6039
|
-
provenance:
|
|
6091
|
+
provenance: Provenance10.EXTRACTED,
|
|
6040
6092
|
confidence: confidenceForExtracted7("structural"),
|
|
6041
6093
|
evidence: { file: relPath.split(path25.sep).join("/") }
|
|
6042
6094
|
};
|
|
@@ -6055,7 +6107,7 @@ import path26 from "path";
|
|
|
6055
6107
|
import {
|
|
6056
6108
|
EdgeType as EdgeType11,
|
|
6057
6109
|
NodeType as NodeType13,
|
|
6058
|
-
Provenance as
|
|
6110
|
+
Provenance as Provenance11,
|
|
6059
6111
|
confidenceForExtracted as confidenceForExtracted8,
|
|
6060
6112
|
extractedEdgeId as extractedEdgeId8,
|
|
6061
6113
|
grpcMethodId as grpcMethodId2
|
|
@@ -6159,7 +6211,7 @@ async function addGrpcMethods(graph, services) {
|
|
|
6159
6211
|
source: service.node.id,
|
|
6160
6212
|
target: mid,
|
|
6161
6213
|
type: EdgeType11.CONTAINS,
|
|
6162
|
-
provenance:
|
|
6214
|
+
provenance: Provenance11.EXTRACTED,
|
|
6163
6215
|
confidence: confidenceForExtracted8("structural"),
|
|
6164
6216
|
evidence: {
|
|
6165
6217
|
file: relFile,
|
|
@@ -6180,7 +6232,7 @@ async function addGrpcMethods(graph, services) {
|
|
|
6180
6232
|
import {
|
|
6181
6233
|
EdgeType as EdgeType14,
|
|
6182
6234
|
NodeType as NodeType15,
|
|
6183
|
-
Provenance as
|
|
6235
|
+
Provenance as Provenance14,
|
|
6184
6236
|
confidenceForExtracted as confidenceForExtracted11,
|
|
6185
6237
|
passesExtractedFloor as passesExtractedFloor3
|
|
6186
6238
|
} from "@neat.is/types";
|
|
@@ -6192,7 +6244,7 @@ import JavaScript4 from "tree-sitter-javascript";
|
|
|
6192
6244
|
import Python3 from "tree-sitter-python";
|
|
6193
6245
|
import {
|
|
6194
6246
|
EdgeType as EdgeType12,
|
|
6195
|
-
Provenance as
|
|
6247
|
+
Provenance as Provenance12,
|
|
6196
6248
|
confidenceForExtracted as confidenceForExtracted9,
|
|
6197
6249
|
passesExtractedFloor
|
|
6198
6250
|
} from "@neat.is/types";
|
|
@@ -6313,7 +6365,7 @@ async function addHttpCallEdges(graph, services) {
|
|
|
6313
6365
|
source: fileNodeId,
|
|
6314
6366
|
target: targetId,
|
|
6315
6367
|
type: EdgeType12.CALLS,
|
|
6316
|
-
provenance:
|
|
6368
|
+
provenance: Provenance12.EXTRACTED,
|
|
6317
6369
|
confidence,
|
|
6318
6370
|
evidence: ev
|
|
6319
6371
|
};
|
|
@@ -6333,7 +6385,7 @@ import JavaScript5 from "tree-sitter-javascript";
|
|
|
6333
6385
|
import {
|
|
6334
6386
|
EdgeType as EdgeType13,
|
|
6335
6387
|
NodeType as NodeType14,
|
|
6336
|
-
Provenance as
|
|
6388
|
+
Provenance as Provenance13,
|
|
6337
6389
|
confidenceForExtracted as confidenceForExtracted10,
|
|
6338
6390
|
passesExtractedFloor as passesExtractedFloor2,
|
|
6339
6391
|
serviceId as serviceId4
|
|
@@ -6588,7 +6640,7 @@ async function addRouteCallEdges(graph, services) {
|
|
|
6588
6640
|
source: fileNodeId,
|
|
6589
6641
|
target: match.routeNodeId,
|
|
6590
6642
|
type: EdgeType13.CALLS,
|
|
6591
|
-
provenance:
|
|
6643
|
+
provenance: Provenance13.EXTRACTED,
|
|
6592
6644
|
confidence,
|
|
6593
6645
|
evidence: ev
|
|
6594
6646
|
};
|
|
@@ -7260,12 +7312,48 @@ function walk3(node, visit) {
|
|
|
7260
7312
|
visit(node);
|
|
7261
7313
|
for (const c of namedChildren(node)) walk3(c, visit);
|
|
7262
7314
|
}
|
|
7315
|
+
var COLUMN_BUILDERS = /* @__PURE__ */ new Set(["Column", "mapped_column"]);
|
|
7316
|
+
function isColumnBuilder(call) {
|
|
7317
|
+
const fn = call.childForFieldName("function");
|
|
7318
|
+
const t = fn?.text;
|
|
7319
|
+
if (!t) return false;
|
|
7320
|
+
const base = t.includes(".") ? t.slice(t.lastIndexOf(".") + 1) : t;
|
|
7321
|
+
return COLUMN_BUILDERS.has(base);
|
|
7322
|
+
}
|
|
7323
|
+
function firstPositionalString(call) {
|
|
7324
|
+
const args = call.childForFieldName("arguments");
|
|
7325
|
+
if (!args) return null;
|
|
7326
|
+
for (const arg of namedChildren(args)) {
|
|
7327
|
+
if (arg.type === "keyword_argument") continue;
|
|
7328
|
+
return arg.type === "string" ? pyStaticStringText2(arg) : null;
|
|
7329
|
+
}
|
|
7330
|
+
return null;
|
|
7331
|
+
}
|
|
7332
|
+
function columnsFromClassBody(body) {
|
|
7333
|
+
const out = [];
|
|
7334
|
+
const seen = /* @__PURE__ */ new Set();
|
|
7335
|
+
for (const stmt of namedChildren(body)) {
|
|
7336
|
+
if (stmt.type !== "expression_statement") continue;
|
|
7337
|
+
const assign = stmt.namedChild(0);
|
|
7338
|
+
if (assign?.type !== "assignment") continue;
|
|
7339
|
+
const left = assign.childForFieldName("left");
|
|
7340
|
+
if (left?.type !== "identifier") continue;
|
|
7341
|
+
const right = assign.childForFieldName("right");
|
|
7342
|
+
if (right?.type !== "call" || !isColumnBuilder(right)) continue;
|
|
7343
|
+
const name = firstPositionalString(right) ?? left.text;
|
|
7344
|
+
if (name && !seen.has(name)) {
|
|
7345
|
+
seen.add(name);
|
|
7346
|
+
out.push(name);
|
|
7347
|
+
}
|
|
7348
|
+
}
|
|
7349
|
+
return out;
|
|
7350
|
+
}
|
|
7263
7351
|
function sqlalchemyEndpointsFromFile(file, serviceDir) {
|
|
7264
7352
|
if (!SQLALCHEMY_IMPORT_RE.test(file.content)) return [];
|
|
7265
7353
|
const tree = parseSource6(makePyParser4(), file.content);
|
|
7266
7354
|
const out = [];
|
|
7267
7355
|
const seen = /* @__PURE__ */ new Set();
|
|
7268
|
-
const push = (name, line) => {
|
|
7356
|
+
const push = (name, line, columns) => {
|
|
7269
7357
|
if (seen.has(name)) return;
|
|
7270
7358
|
seen.add(name);
|
|
7271
7359
|
out.push({
|
|
@@ -7274,6 +7362,7 @@ function sqlalchemyEndpointsFromFile(file, serviceDir) {
|
|
|
7274
7362
|
kind: "sql-table",
|
|
7275
7363
|
edgeType: "CALLS",
|
|
7276
7364
|
confidenceKind: "verified-call-site",
|
|
7365
|
+
...columns && columns.length > 0 ? { columns } : {},
|
|
7277
7366
|
evidence: {
|
|
7278
7367
|
file: path35.relative(serviceDir, file.path),
|
|
7279
7368
|
line,
|
|
@@ -7289,11 +7378,12 @@ function sqlalchemyEndpointsFromFile(file, serviceDir) {
|
|
|
7289
7378
|
const line = node.startPosition.row + 1;
|
|
7290
7379
|
const explicit = explicitTablename(body);
|
|
7291
7380
|
if (explicit === "computed") return;
|
|
7381
|
+
const columns = columnsFromClassBody(body);
|
|
7292
7382
|
if (explicit) {
|
|
7293
|
-
push(explicit.name, line);
|
|
7383
|
+
push(explicit.name, line, columns);
|
|
7294
7384
|
return;
|
|
7295
7385
|
}
|
|
7296
|
-
if (extendsFlaskModel(node)) push(flaskSqlalchemyTableName(nameNode.text), line);
|
|
7386
|
+
if (extendsFlaskModel(node)) push(flaskSqlalchemyTableName(nameNode.text), line, columns);
|
|
7297
7387
|
return;
|
|
7298
7388
|
}
|
|
7299
7389
|
if (node.type === "call") {
|
|
@@ -7486,11 +7576,127 @@ function djangoOrmEndpointsFromFile(file, serviceDir) {
|
|
|
7486
7576
|
return out;
|
|
7487
7577
|
}
|
|
7488
7578
|
|
|
7489
|
-
// src/extract/calls/
|
|
7579
|
+
// src/extract/calls/drizzle.ts
|
|
7490
7580
|
import path37 from "path";
|
|
7491
7581
|
import Parser9 from "tree-sitter";
|
|
7492
|
-
import
|
|
7582
|
+
import JavaScript6 from "tree-sitter-javascript";
|
|
7493
7583
|
import { infraId as infraId10 } from "@neat.is/types";
|
|
7584
|
+
var DRIZZLE_IMPORT_RE = /drizzle-orm/;
|
|
7585
|
+
var TABLE_BUILDERS = /* @__PURE__ */ new Set(["pgTable", "mysqlTable", "sqliteTable"]);
|
|
7586
|
+
function parserForExt(ext) {
|
|
7587
|
+
const p = new Parser9();
|
|
7588
|
+
p.setLanguage(GRAMMAR_BY_EXT[ext] ?? JavaScript6);
|
|
7589
|
+
return p;
|
|
7590
|
+
}
|
|
7591
|
+
function namedChildren3(node) {
|
|
7592
|
+
const out = [];
|
|
7593
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
7594
|
+
const c = node.namedChild(i);
|
|
7595
|
+
if (c) out.push(c);
|
|
7596
|
+
}
|
|
7597
|
+
return out;
|
|
7598
|
+
}
|
|
7599
|
+
function stringLiteralText2(node) {
|
|
7600
|
+
if (!node || node.type !== "string") return null;
|
|
7601
|
+
for (const child of namedChildren3(node)) {
|
|
7602
|
+
if (child.type === "string_fragment") return child.text;
|
|
7603
|
+
}
|
|
7604
|
+
return "";
|
|
7605
|
+
}
|
|
7606
|
+
function firstStringArg(call) {
|
|
7607
|
+
const args = call.childForFieldName("arguments");
|
|
7608
|
+
if (!args) return null;
|
|
7609
|
+
for (const arg of namedChildren3(args)) {
|
|
7610
|
+
if (arg.type === "string") return stringLiteralText2(arg);
|
|
7611
|
+
return null;
|
|
7612
|
+
}
|
|
7613
|
+
return null;
|
|
7614
|
+
}
|
|
7615
|
+
function builderColumnName(value) {
|
|
7616
|
+
let node = value;
|
|
7617
|
+
while (node) {
|
|
7618
|
+
if (node.type === "call_expression") {
|
|
7619
|
+
const fn = node.childForFieldName("function");
|
|
7620
|
+
if (fn?.type === "identifier") return firstStringArg(node);
|
|
7621
|
+
if (fn?.type === "member_expression") {
|
|
7622
|
+
node = fn.childForFieldName("object");
|
|
7623
|
+
continue;
|
|
7624
|
+
}
|
|
7625
|
+
return null;
|
|
7626
|
+
}
|
|
7627
|
+
if (node.type === "member_expression") {
|
|
7628
|
+
node = node.childForFieldName("object");
|
|
7629
|
+
continue;
|
|
7630
|
+
}
|
|
7631
|
+
return null;
|
|
7632
|
+
}
|
|
7633
|
+
return null;
|
|
7634
|
+
}
|
|
7635
|
+
function keyName(key) {
|
|
7636
|
+
if (!key) return null;
|
|
7637
|
+
if (key.type === "property_identifier") return key.text;
|
|
7638
|
+
if (key.type === "string") return stringLiteralText2(key);
|
|
7639
|
+
return null;
|
|
7640
|
+
}
|
|
7641
|
+
function columnsFromObject(obj) {
|
|
7642
|
+
const out = [];
|
|
7643
|
+
const seen = /* @__PURE__ */ new Set();
|
|
7644
|
+
for (const child of namedChildren3(obj)) {
|
|
7645
|
+
if (child.type !== "pair") continue;
|
|
7646
|
+
const value = child.childForFieldName("value");
|
|
7647
|
+
const key = keyName(child.childForFieldName("key"));
|
|
7648
|
+
const name = (value ? builderColumnName(value) : null) ?? key;
|
|
7649
|
+
if (name && !seen.has(name)) {
|
|
7650
|
+
seen.add(name);
|
|
7651
|
+
out.push(name);
|
|
7652
|
+
}
|
|
7653
|
+
}
|
|
7654
|
+
return out;
|
|
7655
|
+
}
|
|
7656
|
+
function drizzleEndpointsFromFile(file, serviceDir) {
|
|
7657
|
+
if (!DRIZZLE_IMPORT_RE.test(file.content)) return [];
|
|
7658
|
+
const tree = parseSource2(parserForExt(path37.extname(file.path)), file.content);
|
|
7659
|
+
const out = [];
|
|
7660
|
+
const seen = /* @__PURE__ */ new Set();
|
|
7661
|
+
const walk6 = (node) => {
|
|
7662
|
+
if (node.type === "call_expression") {
|
|
7663
|
+
const fn = node.childForFieldName("function");
|
|
7664
|
+
if (fn?.type === "identifier" && TABLE_BUILDERS.has(fn.text)) {
|
|
7665
|
+
const args = node.childForFieldName("arguments");
|
|
7666
|
+
const argNodes = args ? namedChildren3(args) : [];
|
|
7667
|
+
const tableName = stringLiteralText2(argNodes[0] ?? null);
|
|
7668
|
+
const obj = argNodes[1];
|
|
7669
|
+
if (tableName && obj?.type === "object" && !seen.has(tableName)) {
|
|
7670
|
+
seen.add(tableName);
|
|
7671
|
+
const columns = columnsFromObject(obj);
|
|
7672
|
+
const line = node.startPosition.row + 1;
|
|
7673
|
+
out.push({
|
|
7674
|
+
infraId: infraId10("sql-table", tableName),
|
|
7675
|
+
name: tableName,
|
|
7676
|
+
kind: "sql-table",
|
|
7677
|
+
edgeType: "CALLS",
|
|
7678
|
+
confidenceKind: "structural",
|
|
7679
|
+
columns,
|
|
7680
|
+
evidence: {
|
|
7681
|
+
file: path37.relative(serviceDir, file.path),
|
|
7682
|
+
line,
|
|
7683
|
+
snippet: snippet(file.content, line)
|
|
7684
|
+
}
|
|
7685
|
+
});
|
|
7686
|
+
}
|
|
7687
|
+
}
|
|
7688
|
+
}
|
|
7689
|
+
for (const c of namedChildren3(node)) walk6(c);
|
|
7690
|
+
};
|
|
7691
|
+
walk6(tree.rootNode);
|
|
7692
|
+
return out;
|
|
7693
|
+
}
|
|
7694
|
+
|
|
7695
|
+
// src/extract/calls/go.ts
|
|
7696
|
+
import path38 from "path";
|
|
7697
|
+
import Parser10 from "tree-sitter";
|
|
7698
|
+
import Go3 from "tree-sitter-go";
|
|
7699
|
+
import { infraId as infraId11 } from "@neat.is/types";
|
|
7494
7700
|
var SQL_METHODS = /* @__PURE__ */ new Set(["Exec", "ExecContext", "Query", "QueryContext", "QueryRow", "QueryRowContext"]);
|
|
7495
7701
|
var PARSE_CHUNK8 = 16384;
|
|
7496
7702
|
function walk5(node, visit) {
|
|
@@ -7501,8 +7707,8 @@ function walk5(node, visit) {
|
|
|
7501
7707
|
}
|
|
7502
7708
|
}
|
|
7503
7709
|
function goSqlEndpointsFromFile(file, serviceDir) {
|
|
7504
|
-
if (
|
|
7505
|
-
const parser = new
|
|
7710
|
+
if (path38.extname(file.path) !== ".go") return [];
|
|
7711
|
+
const parser = new Parser10();
|
|
7506
7712
|
parser.setLanguage(Go3);
|
|
7507
7713
|
const tree = parser.parse(
|
|
7508
7714
|
(index) => index >= file.content.length ? "" : file.content.slice(index, index + PARSE_CHUNK8)
|
|
@@ -7521,12 +7727,12 @@ function goSqlEndpointsFromFile(file, serviceDir) {
|
|
|
7521
7727
|
if (!table) return;
|
|
7522
7728
|
const line = node.startPosition.row + 1;
|
|
7523
7729
|
out.push({
|
|
7524
|
-
infraId:
|
|
7730
|
+
infraId: infraId11("sql-table", table),
|
|
7525
7731
|
name: table,
|
|
7526
7732
|
kind: "sql-table",
|
|
7527
7733
|
edgeType: "CALLS",
|
|
7528
7734
|
confidenceKind: "verified-call-site",
|
|
7529
|
-
evidence: { file: toPosix(
|
|
7735
|
+
evidence: { file: toPosix(path38.relative(serviceDir, file.path)), line, snippet: snippet(file.content, line) }
|
|
7530
7736
|
});
|
|
7531
7737
|
});
|
|
7532
7738
|
return out;
|
|
@@ -7566,6 +7772,7 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
7566
7772
|
endpoints.push(...mongooseEndpointsFromFile(maskedFile, service.dir));
|
|
7567
7773
|
endpoints.push(...sqlalchemyEndpointsFromFile(maskedFile, service.dir));
|
|
7568
7774
|
endpoints.push(...djangoOrmEndpointsFromFile(maskedFile, service.dir));
|
|
7775
|
+
endpoints.push(...drizzleEndpointsFromFile(maskedFile, service.dir));
|
|
7569
7776
|
try {
|
|
7570
7777
|
endpoints.push(...goSqlEndpointsFromFile(maskedFile, service.dir));
|
|
7571
7778
|
} catch (err) {
|
|
@@ -7591,6 +7798,20 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
7591
7798
|
graph.addNode(node.id, node);
|
|
7592
7799
|
nodesAdded++;
|
|
7593
7800
|
}
|
|
7801
|
+
if (ep.columns && ep.columns.length > 0) {
|
|
7802
|
+
const node = graph.getNodeAttributes(ep.infraId);
|
|
7803
|
+
if (node.type === NodeType15.InfraNode) {
|
|
7804
|
+
graph.replaceNodeAttributes(ep.infraId, {
|
|
7805
|
+
...node,
|
|
7806
|
+
columns: foldColumns(
|
|
7807
|
+
node.columns,
|
|
7808
|
+
ep.columns,
|
|
7809
|
+
Provenance14.EXTRACTED,
|
|
7810
|
+
confidenceForExtracted11(ep.confidenceKind)
|
|
7811
|
+
)
|
|
7812
|
+
});
|
|
7813
|
+
}
|
|
7814
|
+
}
|
|
7594
7815
|
const edgeType = edgeTypeFromEndpoint(ep);
|
|
7595
7816
|
const confidence = confidenceForExtracted11(ep.confidenceKind);
|
|
7596
7817
|
const relFile = toPosix(ep.evidence.file);
|
|
@@ -7622,7 +7843,7 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
7622
7843
|
source: fileNodeId,
|
|
7623
7844
|
target: ep.infraId,
|
|
7624
7845
|
type: edgeType,
|
|
7625
|
-
provenance:
|
|
7846
|
+
provenance: Provenance14.EXTRACTED,
|
|
7626
7847
|
confidence,
|
|
7627
7848
|
evidence: ep.evidence
|
|
7628
7849
|
};
|
|
@@ -7644,14 +7865,14 @@ async function addCallEdges(graph, services) {
|
|
|
7644
7865
|
}
|
|
7645
7866
|
|
|
7646
7867
|
// src/extract/infra/docker-compose.ts
|
|
7647
|
-
import
|
|
7648
|
-
import { EdgeType as EdgeType15, Provenance as
|
|
7868
|
+
import path39 from "path";
|
|
7869
|
+
import { EdgeType as EdgeType15, Provenance as Provenance16, confidenceForExtracted as confidenceForExtracted13 } from "@neat.is/types";
|
|
7649
7870
|
|
|
7650
7871
|
// src/extract/infra/shared.ts
|
|
7651
|
-
import { NodeType as NodeType16, Provenance as
|
|
7872
|
+
import { NodeType as NodeType16, Provenance as Provenance15, confidenceForExtracted as confidenceForExtracted12, infraId as infraId12 } from "@neat.is/types";
|
|
7652
7873
|
function makeInfraNode(kind, name, provider = "self", extras) {
|
|
7653
7874
|
return {
|
|
7654
|
-
id:
|
|
7875
|
+
id: infraId12(kind, name),
|
|
7655
7876
|
type: NodeType16.InfraNode,
|
|
7656
7877
|
name,
|
|
7657
7878
|
provider,
|
|
@@ -7696,7 +7917,7 @@ function emitPlatformResourceEdge(graph, anchorId, edgeType, kind, name, provide
|
|
|
7696
7917
|
source: anchorId,
|
|
7697
7918
|
target: node.id,
|
|
7698
7919
|
type: edgeType,
|
|
7699
|
-
provenance:
|
|
7920
|
+
provenance: Provenance15.EXTRACTED,
|
|
7700
7921
|
confidence: confidenceForExtracted12("structural"),
|
|
7701
7922
|
evidence: { file: evidenceFile, ...line !== void 0 ? { line } : {} }
|
|
7702
7923
|
};
|
|
@@ -7714,7 +7935,7 @@ function dependsOnList(value) {
|
|
|
7714
7935
|
}
|
|
7715
7936
|
function serviceNameToServiceNode(name, services) {
|
|
7716
7937
|
for (const s of services) {
|
|
7717
|
-
if (s.node.name === name ||
|
|
7938
|
+
if (s.node.name === name || path39.basename(s.dir) === name) return s.node.id;
|
|
7718
7939
|
}
|
|
7719
7940
|
return null;
|
|
7720
7941
|
}
|
|
@@ -7723,7 +7944,7 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
7723
7944
|
let edgesAdded = 0;
|
|
7724
7945
|
let composePath = null;
|
|
7725
7946
|
for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
|
|
7726
|
-
const abs =
|
|
7947
|
+
const abs = path39.join(scanPath, name);
|
|
7727
7948
|
if (await exists(abs)) {
|
|
7728
7949
|
composePath = abs;
|
|
7729
7950
|
break;
|
|
@@ -7736,13 +7957,13 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
7736
7957
|
} catch (err) {
|
|
7737
7958
|
recordExtractionError(
|
|
7738
7959
|
"infra docker-compose",
|
|
7739
|
-
|
|
7960
|
+
path39.relative(scanPath, composePath),
|
|
7740
7961
|
err
|
|
7741
7962
|
);
|
|
7742
7963
|
return { nodesAdded, edgesAdded };
|
|
7743
7964
|
}
|
|
7744
7965
|
if (!compose?.services) return { nodesAdded, edgesAdded };
|
|
7745
|
-
const evidenceFile =
|
|
7966
|
+
const evidenceFile = path39.relative(scanPath, composePath).split(path39.sep).join("/");
|
|
7746
7967
|
const composeNameToNodeId = /* @__PURE__ */ new Map();
|
|
7747
7968
|
for (const [composeName, svc] of Object.entries(compose.services)) {
|
|
7748
7969
|
const matchedServiceId = serviceNameToServiceNode(composeName, services);
|
|
@@ -7771,7 +7992,7 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
7771
7992
|
source: sourceId,
|
|
7772
7993
|
target: targetId,
|
|
7773
7994
|
type: EdgeType15.DEPENDS_ON,
|
|
7774
|
-
provenance:
|
|
7995
|
+
provenance: Provenance16.EXTRACTED,
|
|
7775
7996
|
confidence: confidenceForExtracted13("structural"),
|
|
7776
7997
|
evidence: { file: evidenceFile }
|
|
7777
7998
|
};
|
|
@@ -7783,9 +8004,9 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
7783
8004
|
}
|
|
7784
8005
|
|
|
7785
8006
|
// src/extract/infra/dockerfile.ts
|
|
7786
|
-
import
|
|
8007
|
+
import path40 from "path";
|
|
7787
8008
|
import { promises as fs17 } from "fs";
|
|
7788
|
-
import { EdgeType as EdgeType16, Provenance as
|
|
8009
|
+
import { EdgeType as EdgeType16, Provenance as Provenance17, confidenceForExtracted as confidenceForExtracted14 } from "@neat.is/types";
|
|
7789
8010
|
function readDockerfile(content) {
|
|
7790
8011
|
let image = null;
|
|
7791
8012
|
const ports = [];
|
|
@@ -7814,7 +8035,7 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
7814
8035
|
let nodesAdded = 0;
|
|
7815
8036
|
let edgesAdded = 0;
|
|
7816
8037
|
for (const service of services) {
|
|
7817
|
-
const dockerfilePath =
|
|
8038
|
+
const dockerfilePath = path40.join(service.dir, "Dockerfile");
|
|
7818
8039
|
if (!await exists(dockerfilePath)) continue;
|
|
7819
8040
|
let content;
|
|
7820
8041
|
try {
|
|
@@ -7822,7 +8043,7 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
7822
8043
|
} catch (err) {
|
|
7823
8044
|
recordExtractionError(
|
|
7824
8045
|
"infra dockerfile",
|
|
7825
|
-
|
|
8046
|
+
path40.relative(scanPath, dockerfilePath),
|
|
7826
8047
|
err
|
|
7827
8048
|
);
|
|
7828
8049
|
continue;
|
|
@@ -7834,8 +8055,8 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
7834
8055
|
graph.addNode(node.id, node);
|
|
7835
8056
|
nodesAdded++;
|
|
7836
8057
|
}
|
|
7837
|
-
const relDockerfile = toPosix(
|
|
7838
|
-
const evidenceFile = toPosix(
|
|
8058
|
+
const relDockerfile = toPosix(path40.relative(service.dir, dockerfilePath));
|
|
8059
|
+
const evidenceFile = toPosix(path40.relative(scanPath, dockerfilePath));
|
|
7839
8060
|
const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
|
|
7840
8061
|
graph,
|
|
7841
8062
|
service.pkg.name,
|
|
@@ -7851,7 +8072,7 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
7851
8072
|
source: fileNodeId,
|
|
7852
8073
|
target: node.id,
|
|
7853
8074
|
type: EdgeType16.RUNS_ON,
|
|
7854
|
-
provenance:
|
|
8075
|
+
provenance: Provenance17.EXTRACTED,
|
|
7855
8076
|
confidence: confidenceForExtracted14("structural"),
|
|
7856
8077
|
evidence: {
|
|
7857
8078
|
file: evidenceFile,
|
|
@@ -7874,7 +8095,7 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
7874
8095
|
source: fileNodeId,
|
|
7875
8096
|
target: portNode.id,
|
|
7876
8097
|
type: EdgeType16.CONNECTS_TO,
|
|
7877
|
-
provenance:
|
|
8098
|
+
provenance: Provenance17.EXTRACTED,
|
|
7878
8099
|
confidence: confidenceForExtracted14("structural"),
|
|
7879
8100
|
evidence: { file: evidenceFile, snippet: `EXPOSE ${port}` }
|
|
7880
8101
|
};
|
|
@@ -7887,8 +8108,8 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
7887
8108
|
|
|
7888
8109
|
// src/extract/infra/terraform.ts
|
|
7889
8110
|
import { promises as fs18 } from "fs";
|
|
7890
|
-
import
|
|
7891
|
-
import { EdgeType as EdgeType17, Provenance as
|
|
8111
|
+
import path41 from "path";
|
|
8112
|
+
import { EdgeType as EdgeType17, Provenance as Provenance18, confidenceForExtracted as confidenceForExtracted15 } from "@neat.is/types";
|
|
7892
8113
|
var RESOURCE_RE = /resource\s+"(aws_[A-Za-z0-9_]+)"\s+"([A-Za-z0-9_-]+)"/g;
|
|
7893
8114
|
var REFERENCE_RE = /(?<![\w.])(aws_[A-Za-z0-9_]+)\.([A-Za-z0-9_-]+)/g;
|
|
7894
8115
|
async function walkTfFiles(start, depth = 0, max = 5) {
|
|
@@ -7898,11 +8119,11 @@ async function walkTfFiles(start, depth = 0, max = 5) {
|
|
|
7898
8119
|
for (const entry of entries) {
|
|
7899
8120
|
if (entry.isDirectory()) {
|
|
7900
8121
|
if (IGNORED_DIRS.has(entry.name) || entry.name === ".terraform") continue;
|
|
7901
|
-
const child =
|
|
8122
|
+
const child = path41.join(start, entry.name);
|
|
7902
8123
|
if (await isPythonVenvDir(child)) continue;
|
|
7903
8124
|
out.push(...await walkTfFiles(child, depth + 1, max));
|
|
7904
8125
|
} else if (entry.isFile() && entry.name.endsWith(".tf")) {
|
|
7905
|
-
out.push(
|
|
8126
|
+
out.push(path41.join(start, entry.name));
|
|
7906
8127
|
}
|
|
7907
8128
|
}
|
|
7908
8129
|
return out;
|
|
@@ -7934,7 +8155,7 @@ async function addTerraformResources(graph, scanPath) {
|
|
|
7934
8155
|
const files = await walkTfFiles(scanPath);
|
|
7935
8156
|
for (const file of files) {
|
|
7936
8157
|
const content = await fs18.readFile(file, "utf8");
|
|
7937
|
-
const evidenceFile = toPosix(
|
|
8158
|
+
const evidenceFile = toPosix(path41.relative(scanPath, file));
|
|
7938
8159
|
const resources = [];
|
|
7939
8160
|
const byKey = /* @__PURE__ */ new Map();
|
|
7940
8161
|
RESOURCE_RE.lastIndex = 0;
|
|
@@ -7977,7 +8198,7 @@ async function addTerraformResources(graph, scanPath) {
|
|
|
7977
8198
|
source: resource.nodeId,
|
|
7978
8199
|
target: target.nodeId,
|
|
7979
8200
|
type: EdgeType17.DEPENDS_ON,
|
|
7980
|
-
provenance:
|
|
8201
|
+
provenance: Provenance18.EXTRACTED,
|
|
7981
8202
|
confidence: confidenceForExtracted15("structural"),
|
|
7982
8203
|
evidence: { file: evidenceFile, line, snippet: key }
|
|
7983
8204
|
};
|
|
@@ -7991,7 +8212,7 @@ async function addTerraformResources(graph, scanPath) {
|
|
|
7991
8212
|
|
|
7992
8213
|
// src/extract/infra/k8s.ts
|
|
7993
8214
|
import { promises as fs19 } from "fs";
|
|
7994
|
-
import
|
|
8215
|
+
import path42 from "path";
|
|
7995
8216
|
import { parseAllDocuments as parseAllDocuments2 } from "yaml";
|
|
7996
8217
|
var K8S_KIND_TO_INFRA_KIND = {
|
|
7997
8218
|
Service: "k8s-service",
|
|
@@ -8009,11 +8230,11 @@ async function walkYamlFiles2(start, depth = 0, max = 5) {
|
|
|
8009
8230
|
for (const entry of entries) {
|
|
8010
8231
|
if (entry.isDirectory()) {
|
|
8011
8232
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
8012
|
-
const child =
|
|
8233
|
+
const child = path42.join(start, entry.name);
|
|
8013
8234
|
if (await isPythonVenvDir(child)) continue;
|
|
8014
8235
|
out.push(...await walkYamlFiles2(child, depth + 1, max));
|
|
8015
|
-
} else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(
|
|
8016
|
-
out.push(
|
|
8236
|
+
} else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(path42.extname(entry.name))) {
|
|
8237
|
+
out.push(path42.join(start, entry.name));
|
|
8017
8238
|
}
|
|
8018
8239
|
}
|
|
8019
8240
|
return out;
|
|
@@ -8046,13 +8267,13 @@ async function addK8sResources(graph, scanPath) {
|
|
|
8046
8267
|
|
|
8047
8268
|
// src/extract/infra/cloudflare.ts
|
|
8048
8269
|
import { promises as fs20 } from "fs";
|
|
8049
|
-
import
|
|
8270
|
+
import path43 from "path";
|
|
8050
8271
|
import { parse as parseToml2 } from "smol-toml";
|
|
8051
|
-
import { EdgeType as EdgeType18, Provenance as
|
|
8272
|
+
import { EdgeType as EdgeType18, Provenance as Provenance19, confidenceForExtracted as confidenceForExtracted16 } from "@neat.is/types";
|
|
8052
8273
|
var WRANGLER_FILENAMES = ["wrangler.toml", "wrangler.jsonc", "wrangler.json"];
|
|
8053
8274
|
async function readWranglerConfig(dir) {
|
|
8054
8275
|
for (const filename of WRANGLER_FILENAMES) {
|
|
8055
|
-
const abs =
|
|
8276
|
+
const abs = path43.join(dir, filename);
|
|
8056
8277
|
if (!await exists(abs)) continue;
|
|
8057
8278
|
const raw = await fs20.readFile(abs, "utf8");
|
|
8058
8279
|
const config = filename === "wrangler.toml" ? parseToml2(raw) : JSON.parse(maskCommentsInSource(raw));
|
|
@@ -8096,7 +8317,7 @@ function addResourceEdge(graph, anchorId, edgeType, kind, name, evidenceFile, li
|
|
|
8096
8317
|
source: anchorId,
|
|
8097
8318
|
target: node.id,
|
|
8098
8319
|
type: edgeType,
|
|
8099
|
-
provenance:
|
|
8320
|
+
provenance: Provenance19.EXTRACTED,
|
|
8100
8321
|
confidence: confidenceForExtracted16("structural"),
|
|
8101
8322
|
evidence: { file: evidenceFile, ...line !== void 0 ? { line } : {} }
|
|
8102
8323
|
};
|
|
@@ -8115,11 +8336,11 @@ async function addCloudflareWorkers(graph, services, scanPath) {
|
|
|
8115
8336
|
try {
|
|
8116
8337
|
read = await readWranglerConfig(service.dir);
|
|
8117
8338
|
} catch (err) {
|
|
8118
|
-
recordExtractionError("infra cloudflare",
|
|
8339
|
+
recordExtractionError("infra cloudflare", path43.relative(scanPath, service.dir), err);
|
|
8119
8340
|
continue;
|
|
8120
8341
|
}
|
|
8121
8342
|
if (!read || !read.config.name) continue;
|
|
8122
|
-
const evidenceFile = toPosix(
|
|
8343
|
+
const evidenceFile = toPosix(path43.relative(scanPath, path43.join(service.dir, read.relFile)));
|
|
8123
8344
|
discovered.push({ service, config: read.config, relFile: read.relFile, raw: read.raw, evidenceFile });
|
|
8124
8345
|
}
|
|
8125
8346
|
for (const worker of discovered) {
|
|
@@ -8131,7 +8352,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
|
|
|
8131
8352
|
}
|
|
8132
8353
|
let anchorId = service.node.id;
|
|
8133
8354
|
if (config.main) {
|
|
8134
|
-
const entryRelPath = toPosix(
|
|
8355
|
+
const entryRelPath = toPosix(path43.normalize(config.main));
|
|
8135
8356
|
const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
|
|
8136
8357
|
graph,
|
|
8137
8358
|
service.pkg.name,
|
|
@@ -8165,7 +8386,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
|
|
|
8165
8386
|
source: anchorId,
|
|
8166
8387
|
target: runtimeNode.id,
|
|
8167
8388
|
type: EdgeType18.RUNS_ON,
|
|
8168
|
-
provenance:
|
|
8389
|
+
provenance: Provenance19.EXTRACTED,
|
|
8169
8390
|
confidence: confidenceForExtracted16("structural"),
|
|
8170
8391
|
evidence: {
|
|
8171
8392
|
file: evidenceFile,
|
|
@@ -8251,7 +8472,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
|
|
|
8251
8472
|
source: anchorId,
|
|
8252
8473
|
target: target.anchorId,
|
|
8253
8474
|
type: EdgeType18.CALLS,
|
|
8254
|
-
provenance:
|
|
8475
|
+
provenance: Provenance19.EXTRACTED,
|
|
8255
8476
|
confidence: confidenceForExtracted16("structural"),
|
|
8256
8477
|
evidence: { file: evidenceFile, line: lineContaining2(raw, svc.service) }
|
|
8257
8478
|
};
|
|
@@ -8278,12 +8499,12 @@ async function addCloudflareWorkers(graph, services, scanPath) {
|
|
|
8278
8499
|
|
|
8279
8500
|
// src/extract/infra/vercel.ts
|
|
8280
8501
|
import { promises as fs21 } from "fs";
|
|
8281
|
-
import
|
|
8502
|
+
import path44 from "path";
|
|
8282
8503
|
import { EdgeType as EdgeType19 } from "@neat.is/types";
|
|
8283
8504
|
var VERCEL_CONFIG_FILENAMES = ["vercel.json", "vercel.jsonc"];
|
|
8284
8505
|
async function readVercelConfig(dir) {
|
|
8285
8506
|
for (const filename of VERCEL_CONFIG_FILENAMES) {
|
|
8286
|
-
const abs =
|
|
8507
|
+
const abs = path44.join(dir, filename);
|
|
8287
8508
|
if (!await exists(abs)) continue;
|
|
8288
8509
|
const raw = await fs21.readFile(abs, "utf8");
|
|
8289
8510
|
const config = JSON.parse(maskCommentsInSource(raw));
|
|
@@ -8292,7 +8513,7 @@ async function readVercelConfig(dir) {
|
|
|
8292
8513
|
return null;
|
|
8293
8514
|
}
|
|
8294
8515
|
async function readLinkedProjectName(dir) {
|
|
8295
|
-
const abs =
|
|
8516
|
+
const abs = path44.join(dir, ".vercel", "project.json");
|
|
8296
8517
|
if (!await exists(abs)) return void 0;
|
|
8297
8518
|
const parsed = JSON.parse(await fs21.readFile(abs, "utf8"));
|
|
8298
8519
|
return typeof parsed.projectName === "string" ? parsed.projectName : void 0;
|
|
@@ -8310,7 +8531,7 @@ async function addVercelServices(graph, services, scanPath) {
|
|
|
8310
8531
|
read = await readVercelConfig(service.dir);
|
|
8311
8532
|
projectName = await readLinkedProjectName(service.dir);
|
|
8312
8533
|
} catch (err) {
|
|
8313
|
-
recordExtractionError("infra vercel",
|
|
8534
|
+
recordExtractionError("infra vercel", path44.relative(scanPath, service.dir), err);
|
|
8314
8535
|
continue;
|
|
8315
8536
|
}
|
|
8316
8537
|
if (!read && !projectName) continue;
|
|
@@ -8326,7 +8547,7 @@ async function addVercelServices(graph, services, scanPath) {
|
|
|
8326
8547
|
const anchorId = service.node.id;
|
|
8327
8548
|
if (!read) continue;
|
|
8328
8549
|
const { config, relFile, raw } = read;
|
|
8329
|
-
const evidenceFile = toPosix(
|
|
8550
|
+
const evidenceFile = toPosix(path44.relative(scanPath, path44.join(service.dir, relFile)));
|
|
8330
8551
|
const add = (edgeType, kind, name) => {
|
|
8331
8552
|
if (!name) return;
|
|
8332
8553
|
const result = emitPlatformResourceEdge(
|
|
@@ -8355,13 +8576,13 @@ async function addVercelServices(graph, services, scanPath) {
|
|
|
8355
8576
|
|
|
8356
8577
|
// src/extract/infra/railway.ts
|
|
8357
8578
|
import { promises as fs22 } from "fs";
|
|
8358
|
-
import
|
|
8579
|
+
import path45 from "path";
|
|
8359
8580
|
import { parse as parseToml3 } from "smol-toml";
|
|
8360
8581
|
import { EdgeType as EdgeType20 } from "@neat.is/types";
|
|
8361
8582
|
var RAILWAY_FILENAMES = ["railway.toml", "railway.json", "railway.jsonc"];
|
|
8362
8583
|
async function readRailwayConfig(dir) {
|
|
8363
8584
|
for (const filename of RAILWAY_FILENAMES) {
|
|
8364
|
-
const abs =
|
|
8585
|
+
const abs = path45.join(dir, filename);
|
|
8365
8586
|
if (!await exists(abs)) continue;
|
|
8366
8587
|
const raw = await fs22.readFile(abs, "utf8");
|
|
8367
8588
|
const config = filename === "railway.toml" ? parseToml3(raw) : JSON.parse(maskCommentsInSource(raw));
|
|
@@ -8377,7 +8598,7 @@ async function addRailwayServices(graph, services, scanPath) {
|
|
|
8377
8598
|
try {
|
|
8378
8599
|
read = await readRailwayConfig(service.dir);
|
|
8379
8600
|
} catch (err) {
|
|
8380
|
-
recordExtractionError("infra railway",
|
|
8601
|
+
recordExtractionError("infra railway", path45.relative(scanPath, service.dir), err);
|
|
8381
8602
|
continue;
|
|
8382
8603
|
}
|
|
8383
8604
|
if (!read) continue;
|
|
@@ -8387,7 +8608,7 @@ async function addRailwayServices(graph, services, scanPath) {
|
|
|
8387
8608
|
}
|
|
8388
8609
|
const anchorId = service.node.id;
|
|
8389
8610
|
const { config, relFile, raw } = read;
|
|
8390
|
-
const evidenceFile = toPosix(
|
|
8611
|
+
const evidenceFile = toPosix(path45.relative(scanPath, path45.join(service.dir, relFile)));
|
|
8391
8612
|
const add = (edgeType, kind, name) => {
|
|
8392
8613
|
if (!name) return;
|
|
8393
8614
|
const result = emitPlatformResourceEdge(
|
|
@@ -8412,12 +8633,12 @@ async function addRailwayServices(graph, services, scanPath) {
|
|
|
8412
8633
|
|
|
8413
8634
|
// src/extract/infra/supabase.ts
|
|
8414
8635
|
import { promises as fs23 } from "fs";
|
|
8415
|
-
import
|
|
8636
|
+
import path46 from "path";
|
|
8416
8637
|
import { parse as parseToml4 } from "smol-toml";
|
|
8417
8638
|
import { EdgeType as EdgeType21 } from "@neat.is/types";
|
|
8418
8639
|
async function readSupabaseConfig(dir) {
|
|
8419
|
-
const relFile =
|
|
8420
|
-
const abs =
|
|
8640
|
+
const relFile = path46.join("supabase", "config.toml");
|
|
8641
|
+
const abs = path46.join(dir, relFile);
|
|
8421
8642
|
if (!await exists(abs)) return null;
|
|
8422
8643
|
const raw = await fs23.readFile(abs, "utf8");
|
|
8423
8644
|
const config = parseToml4(raw);
|
|
@@ -8431,7 +8652,7 @@ async function addSupabaseProjects(graph, services, scanPath) {
|
|
|
8431
8652
|
try {
|
|
8432
8653
|
read = await readSupabaseConfig(service.dir);
|
|
8433
8654
|
} catch (err) {
|
|
8434
|
-
recordExtractionError("infra supabase",
|
|
8655
|
+
recordExtractionError("infra supabase", path46.relative(scanPath, service.dir), err);
|
|
8435
8656
|
continue;
|
|
8436
8657
|
}
|
|
8437
8658
|
if (!read) continue;
|
|
@@ -8446,7 +8667,7 @@ async function addSupabaseProjects(graph, services, scanPath) {
|
|
|
8446
8667
|
});
|
|
8447
8668
|
}
|
|
8448
8669
|
const anchorId = service.node.id;
|
|
8449
|
-
const evidenceFile = toPosix(
|
|
8670
|
+
const evidenceFile = toPosix(path46.relative(scanPath, path46.join(service.dir, relFile)));
|
|
8450
8671
|
const add = (edgeType, kind, name) => {
|
|
8451
8672
|
if (!name) return;
|
|
8452
8673
|
const result = emitPlatformResourceEdge(
|
|
@@ -8487,12 +8708,12 @@ async function addInfra(graph, scanPath, services) {
|
|
|
8487
8708
|
}
|
|
8488
8709
|
|
|
8489
8710
|
// src/extract/index.ts
|
|
8490
|
-
import
|
|
8711
|
+
import path48 from "path";
|
|
8491
8712
|
|
|
8492
8713
|
// src/extract/retire.ts
|
|
8493
8714
|
import { existsSync as existsSync2 } from "fs";
|
|
8494
|
-
import
|
|
8495
|
-
import { NodeType as NodeType17, Provenance as
|
|
8715
|
+
import path47 from "path";
|
|
8716
|
+
import { NodeType as NodeType17, Provenance as Provenance20 } from "@neat.is/types";
|
|
8496
8717
|
function dropOrphanedFileNodes(graph) {
|
|
8497
8718
|
const orphans = [];
|
|
8498
8719
|
graph.forEachNode((id, attrs) => {
|
|
@@ -8509,7 +8730,7 @@ function retireEdgesByFile(graph, file) {
|
|
|
8509
8730
|
const toDrop = [];
|
|
8510
8731
|
graph.forEachEdge((id, attrs) => {
|
|
8511
8732
|
const edge = attrs;
|
|
8512
|
-
if (edge.provenance !==
|
|
8733
|
+
if (edge.provenance !== Provenance20.EXTRACTED) return;
|
|
8513
8734
|
if (!edge.evidence?.file) return;
|
|
8514
8735
|
if (edge.evidence.file === normalized) toDrop.push(id);
|
|
8515
8736
|
});
|
|
@@ -8522,14 +8743,14 @@ function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = []) {
|
|
|
8522
8743
|
const bases = [scanPath, ...serviceDirs];
|
|
8523
8744
|
graph.forEachEdge((id, attrs) => {
|
|
8524
8745
|
const edge = attrs;
|
|
8525
|
-
if (edge.provenance !==
|
|
8746
|
+
if (edge.provenance !== Provenance20.EXTRACTED) return;
|
|
8526
8747
|
const evidenceFile = edge.evidence?.file;
|
|
8527
8748
|
if (!evidenceFile) return;
|
|
8528
|
-
if (
|
|
8749
|
+
if (path47.isAbsolute(evidenceFile)) {
|
|
8529
8750
|
if (!existsSync2(evidenceFile)) toDrop.push(id);
|
|
8530
8751
|
return;
|
|
8531
8752
|
}
|
|
8532
|
-
const found = bases.some((base) => existsSync2(
|
|
8753
|
+
const found = bases.some((base) => existsSync2(path47.join(base, evidenceFile)));
|
|
8533
8754
|
if (!found) toDrop.push(id);
|
|
8534
8755
|
});
|
|
8535
8756
|
for (const id of toDrop) graph.dropEdge(id);
|
|
@@ -8582,7 +8803,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
8582
8803
|
}
|
|
8583
8804
|
const droppedEntries = drainDroppedExtracted();
|
|
8584
8805
|
if (isRejectedLogEnabled() && opts.errorsPath && droppedEntries.length > 0) {
|
|
8585
|
-
const rejectedPath =
|
|
8806
|
+
const rejectedPath = path48.join(path48.dirname(opts.errorsPath), "rejected.ndjson");
|
|
8586
8807
|
try {
|
|
8587
8808
|
await writeRejectedExtracted(droppedEntries, rejectedPath);
|
|
8588
8809
|
} catch (err) {
|
|
@@ -8622,7 +8843,7 @@ import {
|
|
|
8622
8843
|
NodeType as NodeType18,
|
|
8623
8844
|
parseEdgeId,
|
|
8624
8845
|
parseFileId,
|
|
8625
|
-
Provenance as
|
|
8846
|
+
Provenance as Provenance21,
|
|
8626
8847
|
serviceId as serviceId5
|
|
8627
8848
|
} from "@neat.is/types";
|
|
8628
8849
|
function bucketKey(source, target, type) {
|
|
@@ -8646,17 +8867,17 @@ function bucketEdges(graph) {
|
|
|
8646
8867
|
const key = bucketKey(source, e.target, e.type);
|
|
8647
8868
|
const cur = buckets2.get(key) ?? { source, target: e.target, type: e.type };
|
|
8648
8869
|
switch (provenance) {
|
|
8649
|
-
case
|
|
8870
|
+
case Provenance21.EXTRACTED:
|
|
8650
8871
|
cur.extracted = e;
|
|
8651
8872
|
break;
|
|
8652
|
-
case
|
|
8873
|
+
case Provenance21.OBSERVED:
|
|
8653
8874
|
cur.observed = e;
|
|
8654
8875
|
break;
|
|
8655
|
-
case
|
|
8876
|
+
case Provenance21.INFERRED:
|
|
8656
8877
|
cur.inferred = e;
|
|
8657
8878
|
break;
|
|
8658
8879
|
default:
|
|
8659
|
-
if (e.provenance ===
|
|
8880
|
+
if (e.provenance === Provenance21.STALE) cur.stale = e;
|
|
8660
8881
|
}
|
|
8661
8882
|
buckets2.set(key, cur);
|
|
8662
8883
|
});
|
|
@@ -8744,7 +8965,7 @@ function declaredHostFor(svc) {
|
|
|
8744
8965
|
function hasExtractedConfiguredBy(graph, svcId) {
|
|
8745
8966
|
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
8746
8967
|
const e = graph.getEdgeAttributes(edgeId);
|
|
8747
|
-
if (e.type === EdgeType22.CONFIGURED_BY && e.provenance ===
|
|
8968
|
+
if (e.type === EdgeType22.CONFIGURED_BY && e.provenance === Provenance21.EXTRACTED) {
|
|
8748
8969
|
return true;
|
|
8749
8970
|
}
|
|
8750
8971
|
}
|
|
@@ -8758,7 +8979,7 @@ function detectHostMismatch(graph, svcId, svc) {
|
|
|
8758
8979
|
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
8759
8980
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
8760
8981
|
if (edge.type !== EdgeType22.CONNECTS_TO) continue;
|
|
8761
|
-
if (edge.provenance !==
|
|
8982
|
+
if (edge.provenance !== Provenance21.OBSERVED) continue;
|
|
8762
8983
|
const target = graph.getNodeAttributes(edge.target);
|
|
8763
8984
|
if (target.type !== NodeType18.DatabaseNode) continue;
|
|
8764
8985
|
const observedHost = target.host?.trim();
|
|
@@ -8783,7 +9004,7 @@ function detectCompatDivergences(graph, svcId, svc) {
|
|
|
8783
9004
|
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
8784
9005
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
8785
9006
|
if (edge.type !== EdgeType22.CONNECTS_TO) continue;
|
|
8786
|
-
if (edge.provenance !==
|
|
9007
|
+
if (edge.provenance !== Provenance21.OBSERVED) continue;
|
|
8787
9008
|
const target = graph.getNodeAttributes(edge.target);
|
|
8788
9009
|
if (target.type !== NodeType18.DatabaseNode) continue;
|
|
8789
9010
|
for (const pair of compatPairs()) {
|
|
@@ -8835,6 +9056,44 @@ function detectCompatDivergences(graph, svcId, svc) {
|
|
|
8835
9056
|
}
|
|
8836
9057
|
return out;
|
|
8837
9058
|
}
|
|
9059
|
+
var RECOMMENDATION_COLUMN_MISSING_OBSERVED = "Verify the column is exercised in production; a migration that renamed or dropped it may have left a writer declaring the old name.";
|
|
9060
|
+
var RECOMMENDATION_COLUMN_MISSING_EXTRACTED = "The schema or migration is likely behind the code \u2014 production writes a column the declared schema does not carry. Check for a field rename that updated the query but not the model.";
|
|
9061
|
+
function detectColumnDrift(node) {
|
|
9062
|
+
const columns = node.columns;
|
|
9063
|
+
if (!columns || columns.length === 0) return [];
|
|
9064
|
+
const anyDeclared = columns.some(columnIsDeclared);
|
|
9065
|
+
const anyObserved = columns.some(columnIsObserved);
|
|
9066
|
+
if (!anyDeclared || !anyObserved) return [];
|
|
9067
|
+
const out = [];
|
|
9068
|
+
for (const col of columns) {
|
|
9069
|
+
const declared = columnIsDeclared(col);
|
|
9070
|
+
const observed = columnIsObserved(col);
|
|
9071
|
+
if (declared && !observed) {
|
|
9072
|
+
out.push({
|
|
9073
|
+
type: "missing-observed",
|
|
9074
|
+
source: node.id,
|
|
9075
|
+
target: node.id,
|
|
9076
|
+
table: node.id,
|
|
9077
|
+
column: col.name,
|
|
9078
|
+
confidence: clampConfidence(col.confidence),
|
|
9079
|
+
reason: `Schema declares column ${node.name}.${col.name} but no production statement has touched it.`,
|
|
9080
|
+
recommendation: RECOMMENDATION_COLUMN_MISSING_OBSERVED
|
|
9081
|
+
});
|
|
9082
|
+
} else if (observed && !declared) {
|
|
9083
|
+
out.push({
|
|
9084
|
+
type: "missing-extracted",
|
|
9085
|
+
source: node.id,
|
|
9086
|
+
target: node.id,
|
|
9087
|
+
table: node.id,
|
|
9088
|
+
column: col.name,
|
|
9089
|
+
confidence: clampConfidence(col.confidence),
|
|
9090
|
+
reason: `Production touched column ${node.name}.${col.name} but the schema does not declare it.`,
|
|
9091
|
+
recommendation: RECOMMENDATION_COLUMN_MISSING_EXTRACTED
|
|
9092
|
+
});
|
|
9093
|
+
}
|
|
9094
|
+
}
|
|
9095
|
+
return out;
|
|
9096
|
+
}
|
|
8838
9097
|
function involvesNode(d, nodeId) {
|
|
8839
9098
|
return d.source === nodeId || d.target === nodeId;
|
|
8840
9099
|
}
|
|
@@ -8863,10 +9122,15 @@ function computeDivergences(graph, opts = {}) {
|
|
|
8863
9122
|
}
|
|
8864
9123
|
graph.forEachNode((nodeId, attrs) => {
|
|
8865
9124
|
const n = attrs;
|
|
8866
|
-
if (n.type
|
|
8867
|
-
|
|
8868
|
-
|
|
8869
|
-
|
|
9125
|
+
if (n.type === NodeType18.ServiceNode) {
|
|
9126
|
+
const svc = n;
|
|
9127
|
+
for (const d of detectHostMismatch(graph, nodeId, svc)) all.push(d);
|
|
9128
|
+
for (const d of detectCompatDivergences(graph, nodeId, svc)) all.push(d);
|
|
9129
|
+
return;
|
|
9130
|
+
}
|
|
9131
|
+
if (n.type === NodeType18.InfraNode && n.kind === "sql-table") {
|
|
9132
|
+
for (const d of detectColumnDrift(n)) all.push(d);
|
|
9133
|
+
}
|
|
8870
9134
|
});
|
|
8871
9135
|
const reconciled = suppressHostMismatchHalves(all);
|
|
8872
9136
|
let filtered = reconciled;
|
|
@@ -8895,7 +9159,10 @@ function computeDivergences(graph, opts = {}) {
|
|
|
8895
9159
|
if (lead !== 0) return lead;
|
|
8896
9160
|
if (a.type !== b.type) return a.type.localeCompare(b.type);
|
|
8897
9161
|
if (a.source !== b.source) return a.source.localeCompare(b.source);
|
|
8898
|
-
return a.target.localeCompare(b.target);
|
|
9162
|
+
if (a.target !== b.target) return a.target.localeCompare(b.target);
|
|
9163
|
+
const ac = "column" in a && a.column ? a.column : "";
|
|
9164
|
+
const bc = "column" in b && b.column ? b.column : "";
|
|
9165
|
+
return ac.localeCompare(bc);
|
|
8899
9166
|
});
|
|
8900
9167
|
return DivergenceResultSchema.parse({
|
|
8901
9168
|
divergences: filtered,
|
|
@@ -8906,9 +9173,9 @@ function computeDivergences(graph, opts = {}) {
|
|
|
8906
9173
|
|
|
8907
9174
|
// src/persist.ts
|
|
8908
9175
|
import { promises as fs24 } from "fs";
|
|
8909
|
-
import
|
|
8910
|
-
import { Provenance as
|
|
8911
|
-
var SCHEMA_VERSION =
|
|
9176
|
+
import path49 from "path";
|
|
9177
|
+
import { NodeType as NodeType19, Provenance as Provenance22, observedEdgeId as observedEdgeId2 } from "@neat.is/types";
|
|
9178
|
+
var SCHEMA_VERSION = 6;
|
|
8912
9179
|
function migrateV1ToV2(payload) {
|
|
8913
9180
|
const nodes = payload.graph.nodes;
|
|
8914
9181
|
if (Array.isArray(nodes)) {
|
|
@@ -8926,13 +9193,25 @@ function migrateV3ToV4(payload) {
|
|
|
8926
9193
|
function migrateV4ToV5(payload) {
|
|
8927
9194
|
return { ...payload, schemaVersion: 5 };
|
|
8928
9195
|
}
|
|
9196
|
+
function migrateV5ToV6(payload) {
|
|
9197
|
+
const nodes = payload.graph.nodes;
|
|
9198
|
+
if (Array.isArray(nodes)) {
|
|
9199
|
+
for (const node of nodes) {
|
|
9200
|
+
const attrs = node.attributes;
|
|
9201
|
+
if (!attrs || attrs.type !== NodeType19.InfraNode) continue;
|
|
9202
|
+
if (attrs.kind !== "sql-table" && attrs.kind !== "supabase-table") continue;
|
|
9203
|
+
if (!Array.isArray(attrs.columns)) attrs.columns = [];
|
|
9204
|
+
}
|
|
9205
|
+
}
|
|
9206
|
+
return { ...payload, schemaVersion: 6 };
|
|
9207
|
+
}
|
|
8929
9208
|
function migrateV2ToV3(payload) {
|
|
8930
9209
|
const edges = payload.graph.edges;
|
|
8931
9210
|
if (Array.isArray(edges)) {
|
|
8932
9211
|
for (const edge of edges) {
|
|
8933
9212
|
const attrs = edge.attributes;
|
|
8934
9213
|
if (!attrs || attrs.provenance !== "FRONTIER") continue;
|
|
8935
|
-
attrs.provenance =
|
|
9214
|
+
attrs.provenance = Provenance22.OBSERVED;
|
|
8936
9215
|
const type = typeof attrs.type === "string" ? attrs.type : void 0;
|
|
8937
9216
|
const source = typeof attrs.source === "string" ? attrs.source : void 0;
|
|
8938
9217
|
const target = typeof attrs.target === "string" ? attrs.target : void 0;
|
|
@@ -8946,7 +9225,7 @@ function migrateV2ToV3(payload) {
|
|
|
8946
9225
|
return { ...payload, schemaVersion: 3 };
|
|
8947
9226
|
}
|
|
8948
9227
|
async function ensureDir(filePath) {
|
|
8949
|
-
await fs24.mkdir(
|
|
9228
|
+
await fs24.mkdir(path49.dirname(filePath), { recursive: true });
|
|
8950
9229
|
}
|
|
8951
9230
|
async function saveGraphToDisk(graph, outPath) {
|
|
8952
9231
|
await ensureDir(outPath);
|
|
@@ -8980,6 +9259,9 @@ async function loadGraphFromDisk(graph, outPath) {
|
|
|
8980
9259
|
if (payload.schemaVersion === 4) {
|
|
8981
9260
|
payload = migrateV4ToV5(payload);
|
|
8982
9261
|
}
|
|
9262
|
+
if (payload.schemaVersion === 5) {
|
|
9263
|
+
payload = migrateV5ToV6(payload);
|
|
9264
|
+
}
|
|
8983
9265
|
if (payload.schemaVersion !== SCHEMA_VERSION) {
|
|
8984
9266
|
throw new Error(
|
|
8985
9267
|
`persist: unsupported snapshot schemaVersion ${payload.schemaVersion} (expected ${SCHEMA_VERSION})`
|
|
@@ -9105,23 +9387,23 @@ function canonicalJson(value) {
|
|
|
9105
9387
|
}
|
|
9106
9388
|
|
|
9107
9389
|
// src/projects.ts
|
|
9108
|
-
import
|
|
9390
|
+
import path50 from "path";
|
|
9109
9391
|
function pathsForProject(project, baseDir) {
|
|
9110
9392
|
if (project === DEFAULT_PROJECT) {
|
|
9111
9393
|
return {
|
|
9112
|
-
snapshotPath:
|
|
9113
|
-
errorsPath:
|
|
9114
|
-
staleEventsPath:
|
|
9115
|
-
embeddingsCachePath:
|
|
9116
|
-
policyViolationsPath:
|
|
9394
|
+
snapshotPath: path50.join(baseDir, "graph.json"),
|
|
9395
|
+
errorsPath: path50.join(baseDir, "errors.ndjson"),
|
|
9396
|
+
staleEventsPath: path50.join(baseDir, "stale-events.ndjson"),
|
|
9397
|
+
embeddingsCachePath: path50.join(baseDir, "embeddings.json"),
|
|
9398
|
+
policyViolationsPath: path50.join(baseDir, "policy-violations.ndjson")
|
|
9117
9399
|
};
|
|
9118
9400
|
}
|
|
9119
9401
|
return {
|
|
9120
|
-
snapshotPath:
|
|
9121
|
-
errorsPath:
|
|
9122
|
-
staleEventsPath:
|
|
9123
|
-
embeddingsCachePath:
|
|
9124
|
-
policyViolationsPath:
|
|
9402
|
+
snapshotPath: path50.join(baseDir, `${project}.json`),
|
|
9403
|
+
errorsPath: path50.join(baseDir, `errors.${project}.ndjson`),
|
|
9404
|
+
staleEventsPath: path50.join(baseDir, `stale-events.${project}.ndjson`),
|
|
9405
|
+
embeddingsCachePath: path50.join(baseDir, `embeddings.${project}.json`),
|
|
9406
|
+
policyViolationsPath: path50.join(baseDir, `policy-violations.${project}.ndjson`)
|
|
9125
9407
|
};
|
|
9126
9408
|
}
|
|
9127
9409
|
var Projects = class {
|
|
@@ -9162,7 +9444,7 @@ function parseExtraProjects(raw) {
|
|
|
9162
9444
|
// src/registry.ts
|
|
9163
9445
|
import { promises as fs26 } from "fs";
|
|
9164
9446
|
import os2 from "os";
|
|
9165
|
-
import
|
|
9447
|
+
import path51 from "path";
|
|
9166
9448
|
import {
|
|
9167
9449
|
RegistryFileSchema
|
|
9168
9450
|
} from "@neat.is/types";
|
|
@@ -9170,20 +9452,20 @@ var LOCK_TIMEOUT_MS = 5e3;
|
|
|
9170
9452
|
var LOCK_RETRY_MS = 50;
|
|
9171
9453
|
function neatHome() {
|
|
9172
9454
|
const override = process.env.NEAT_HOME;
|
|
9173
|
-
if (override && override.length > 0) return
|
|
9174
|
-
return
|
|
9455
|
+
if (override && override.length > 0) return path51.resolve(override);
|
|
9456
|
+
return path51.join(os2.homedir(), ".neat");
|
|
9175
9457
|
}
|
|
9176
9458
|
function registryPath() {
|
|
9177
|
-
return
|
|
9459
|
+
return path51.join(neatHome(), "projects.json");
|
|
9178
9460
|
}
|
|
9179
9461
|
function registryLockPath() {
|
|
9180
|
-
return
|
|
9462
|
+
return path51.join(neatHome(), "projects.json.lock");
|
|
9181
9463
|
}
|
|
9182
9464
|
function daemonPidPath() {
|
|
9183
|
-
return
|
|
9465
|
+
return path51.join(neatHome(), "neatd.pid");
|
|
9184
9466
|
}
|
|
9185
9467
|
function daemonsDir() {
|
|
9186
|
-
return
|
|
9468
|
+
return path51.join(neatHome(), "daemons");
|
|
9187
9469
|
}
|
|
9188
9470
|
function isFiniteInt(v) {
|
|
9189
9471
|
return typeof v === "number" && Number.isFinite(v);
|
|
@@ -9224,7 +9506,7 @@ async function discoverDaemons(probe = defaultDiscoveryProbe) {
|
|
|
9224
9506
|
const out = [];
|
|
9225
9507
|
for (const name of names) {
|
|
9226
9508
|
if (!name.endsWith(".json")) continue;
|
|
9227
|
-
const file =
|
|
9509
|
+
const file = path51.join(dir, name);
|
|
9228
9510
|
let raw;
|
|
9229
9511
|
try {
|
|
9230
9512
|
raw = await fs26.readFile(file, "utf8");
|
|
@@ -9345,7 +9627,7 @@ function lockHolderMessage(holder, lockPath, timeoutMs) {
|
|
|
9345
9627
|
}
|
|
9346
9628
|
}
|
|
9347
9629
|
async function normalizeProjectPath(input) {
|
|
9348
|
-
const resolved =
|
|
9630
|
+
const resolved = path51.resolve(input);
|
|
9349
9631
|
try {
|
|
9350
9632
|
return await fs26.realpath(resolved);
|
|
9351
9633
|
} catch {
|
|
@@ -9353,7 +9635,7 @@ async function normalizeProjectPath(input) {
|
|
|
9353
9635
|
}
|
|
9354
9636
|
}
|
|
9355
9637
|
async function writeAtomically(target, contents) {
|
|
9356
|
-
await fs26.mkdir(
|
|
9638
|
+
await fs26.mkdir(path51.dirname(target), { recursive: true });
|
|
9357
9639
|
const tmp = `${target}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
|
|
9358
9640
|
const fd = await fs26.open(tmp, "w");
|
|
9359
9641
|
try {
|
|
@@ -9366,7 +9648,7 @@ async function writeAtomically(target, contents) {
|
|
|
9366
9648
|
}
|
|
9367
9649
|
async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaultLockHolderProbe) {
|
|
9368
9650
|
const deadline = Date.now() + timeoutMs;
|
|
9369
|
-
await fs26.mkdir(
|
|
9651
|
+
await fs26.mkdir(path51.dirname(lockPath), { recursive: true });
|
|
9370
9652
|
let probedHolder = false;
|
|
9371
9653
|
while (true) {
|
|
9372
9654
|
try {
|
|
@@ -9562,13 +9844,13 @@ import { DivergenceTypeSchema, PoliciesCheckBodySchema, PolicySeveritySchema } f
|
|
|
9562
9844
|
|
|
9563
9845
|
// src/extend/index.ts
|
|
9564
9846
|
import { promises as fs28 } from "fs";
|
|
9565
|
-
import
|
|
9847
|
+
import path53 from "path";
|
|
9566
9848
|
import os3 from "os";
|
|
9567
9849
|
import { resolve as registryResolve, list as registryList } from "@neat.is/instrumentation-registry";
|
|
9568
9850
|
|
|
9569
9851
|
// src/installers/package-manager.ts
|
|
9570
9852
|
import { promises as fs27 } from "fs";
|
|
9571
|
-
import
|
|
9853
|
+
import path52 from "path";
|
|
9572
9854
|
import { spawn } from "child_process";
|
|
9573
9855
|
var LOCKFILE_PRIORITY = [
|
|
9574
9856
|
{ lockfile: "bun.lockb", pm: "bun", args: ["install", "--no-summary"] },
|
|
@@ -9590,22 +9872,22 @@ async function exists2(p) {
|
|
|
9590
9872
|
}
|
|
9591
9873
|
}
|
|
9592
9874
|
async function detectPackageManager(serviceDir) {
|
|
9593
|
-
let dir =
|
|
9875
|
+
let dir = path52.resolve(serviceDir);
|
|
9594
9876
|
const stops = /* @__PURE__ */ new Set();
|
|
9595
9877
|
for (let i = 0; i < 64; i++) {
|
|
9596
9878
|
if (stops.has(dir)) break;
|
|
9597
9879
|
stops.add(dir);
|
|
9598
9880
|
for (const candidate of LOCKFILE_PRIORITY) {
|
|
9599
|
-
const lockPath =
|
|
9881
|
+
const lockPath = path52.join(dir, candidate.lockfile);
|
|
9600
9882
|
if (await exists2(lockPath)) {
|
|
9601
9883
|
return { pm: candidate.pm, cwd: dir, args: [...candidate.args] };
|
|
9602
9884
|
}
|
|
9603
9885
|
}
|
|
9604
|
-
const parent =
|
|
9886
|
+
const parent = path52.dirname(dir);
|
|
9605
9887
|
if (parent === dir) break;
|
|
9606
9888
|
dir = parent;
|
|
9607
9889
|
}
|
|
9608
|
-
return { pm: "npm", cwd:
|
|
9890
|
+
return { pm: "npm", cwd: path52.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
|
|
9609
9891
|
}
|
|
9610
9892
|
async function runPackageManagerInstall(cmd) {
|
|
9611
9893
|
return new Promise((resolve) => {
|
|
@@ -9654,7 +9936,7 @@ async function fileExists2(p) {
|
|
|
9654
9936
|
}
|
|
9655
9937
|
}
|
|
9656
9938
|
async function readPackageJson(scanPath) {
|
|
9657
|
-
const pkgPath =
|
|
9939
|
+
const pkgPath = path53.join(scanPath, "package.json");
|
|
9658
9940
|
const raw = await fs28.readFile(pkgPath, "utf8");
|
|
9659
9941
|
return JSON.parse(raw);
|
|
9660
9942
|
}
|
|
@@ -9673,11 +9955,11 @@ async function findHookFiles(scanPath) {
|
|
|
9673
9955
|
for (const entry of entries) {
|
|
9674
9956
|
if (entry.isDirectory()) {
|
|
9675
9957
|
if (entry.name.startsWith(".") || HOOK_WALK_SKIP_DIRS.has(entry.name)) continue;
|
|
9676
|
-
await walk6(
|
|
9958
|
+
await walk6(path53.join(dir, entry.name));
|
|
9677
9959
|
} else if (entry.isFile()) {
|
|
9678
9960
|
if ((entry.name.startsWith("instrumentation") || entry.name.startsWith("otel-init")) && /\.(ts|js|cjs|mjs)$/.test(entry.name)) {
|
|
9679
|
-
const rel =
|
|
9680
|
-
found.push(rel.split(
|
|
9961
|
+
const rel = path53.relative(scanPath, path53.join(dir, entry.name));
|
|
9962
|
+
found.push(rel.split(path53.sep).join("/"));
|
|
9681
9963
|
}
|
|
9682
9964
|
}
|
|
9683
9965
|
}
|
|
@@ -9688,7 +9970,7 @@ async function findHookFiles(scanPath) {
|
|
|
9688
9970
|
async function pickPrimaryHookFile(scanPath, hookFiles, snippet2) {
|
|
9689
9971
|
let fallback = null;
|
|
9690
9972
|
for (const file of hookFiles) {
|
|
9691
|
-
const content = await fs28.readFile(
|
|
9973
|
+
const content = await fs28.readFile(path53.join(scanPath, file), "utf8");
|
|
9692
9974
|
const patched = splicedContent(content, snippet2);
|
|
9693
9975
|
if (patched !== null) return { file, content, patched };
|
|
9694
9976
|
if (fallback === null) fallback = { file, content };
|
|
@@ -9696,11 +9978,11 @@ async function pickPrimaryHookFile(scanPath, hookFiles, snippet2) {
|
|
|
9696
9978
|
return { file: fallback.file, content: fallback.content, patched: null };
|
|
9697
9979
|
}
|
|
9698
9980
|
function extendLogPath() {
|
|
9699
|
-
return process.env.NEAT_EXTEND_LOG ??
|
|
9981
|
+
return process.env.NEAT_EXTEND_LOG ?? path53.join(os3.homedir(), ".neat", "extend-log.ndjson");
|
|
9700
9982
|
}
|
|
9701
9983
|
async function appendExtendLog(entry) {
|
|
9702
9984
|
const logPath = extendLogPath();
|
|
9703
|
-
await fs28.mkdir(
|
|
9985
|
+
await fs28.mkdir(path53.dirname(logPath), { recursive: true });
|
|
9704
9986
|
await fs28.appendFile(logPath, JSON.stringify(entry) + "\n", "utf8");
|
|
9705
9987
|
}
|
|
9706
9988
|
function splicedContent(fileContent, snippet2) {
|
|
@@ -9759,7 +10041,7 @@ function lookupInstrumentation(library, installedVersion) {
|
|
|
9759
10041
|
}
|
|
9760
10042
|
async function describeProjectInstrumentation(ctx) {
|
|
9761
10043
|
const hookFiles = await findHookFiles(ctx.scanPath);
|
|
9762
|
-
const envNeat = await fileExists2(
|
|
10044
|
+
const envNeat = await fileExists2(path53.join(ctx.scanPath, ".env.neat"));
|
|
9763
10045
|
const registryInstrPackages = new Set(
|
|
9764
10046
|
registryList().map((e) => e.instrumentation_package).filter((p) => !!p)
|
|
9765
10047
|
);
|
|
@@ -9781,7 +10063,7 @@ async function applyExtension(ctx, args, options) {
|
|
|
9781
10063
|
);
|
|
9782
10064
|
}
|
|
9783
10065
|
for (const file of hookFiles) {
|
|
9784
|
-
const content = await fs28.readFile(
|
|
10066
|
+
const content = await fs28.readFile(path53.join(ctx.scanPath, file), "utf8");
|
|
9785
10067
|
if (content.includes(args.registration_snippet)) {
|
|
9786
10068
|
return { library: args.library, filesTouched: [], depsAdded: [], installOutput: "", alreadyApplied: true };
|
|
9787
10069
|
}
|
|
@@ -9793,10 +10075,10 @@ async function applyExtension(ctx, args, options) {
|
|
|
9793
10075
|
);
|
|
9794
10076
|
}
|
|
9795
10077
|
const primaryFile = primary.file;
|
|
9796
|
-
const primaryPath =
|
|
10078
|
+
const primaryPath = path53.join(ctx.scanPath, primaryFile);
|
|
9797
10079
|
const filesTouched = [];
|
|
9798
10080
|
const depsAdded = [];
|
|
9799
|
-
const pkgPath =
|
|
10081
|
+
const pkgPath = path53.join(ctx.scanPath, "package.json");
|
|
9800
10082
|
const pkg = await readPackageJson(ctx.scanPath);
|
|
9801
10083
|
if (!(pkg.dependencies ?? {})[args.instrumentation_package]) {
|
|
9802
10084
|
pkg.dependencies = { ...pkg.dependencies ?? {}, [args.instrumentation_package]: args.version };
|
|
@@ -9835,7 +10117,7 @@ async function dryRunExtension(ctx, args) {
|
|
|
9835
10117
|
};
|
|
9836
10118
|
}
|
|
9837
10119
|
for (const file of hookFiles) {
|
|
9838
|
-
const content = await fs28.readFile(
|
|
10120
|
+
const content = await fs28.readFile(path53.join(ctx.scanPath, file), "utf8");
|
|
9839
10121
|
if (content.includes(args.registration_snippet)) {
|
|
9840
10122
|
return {
|
|
9841
10123
|
library: args.library,
|
|
@@ -9876,7 +10158,7 @@ async function rollbackExtension(ctx, args) {
|
|
|
9876
10158
|
if (!match) {
|
|
9877
10159
|
return { undone: false, message: "no apply found for library" };
|
|
9878
10160
|
}
|
|
9879
|
-
const pkgPath =
|
|
10161
|
+
const pkgPath = path53.join(ctx.scanPath, "package.json");
|
|
9880
10162
|
if (await fileExists2(pkgPath)) {
|
|
9881
10163
|
const pkg = await readPackageJson(ctx.scanPath);
|
|
9882
10164
|
if (pkg.dependencies?.[match.instrumentation_package]) {
|
|
@@ -9887,7 +10169,7 @@ async function rollbackExtension(ctx, args) {
|
|
|
9887
10169
|
}
|
|
9888
10170
|
const hookFiles = await findHookFiles(ctx.scanPath);
|
|
9889
10171
|
for (const file of hookFiles) {
|
|
9890
|
-
const filePath =
|
|
10172
|
+
const filePath = path53.join(ctx.scanPath, file);
|
|
9891
10173
|
const content = await fs28.readFile(filePath, "utf8");
|
|
9892
10174
|
if (content.includes(match.registration_snippet)) {
|
|
9893
10175
|
const filtered = content.split("\n").filter((line) => !line.includes(match.registration_snippet)).join("\n");
|
|
@@ -10002,7 +10284,7 @@ data: ${JSON.stringify(envelope.payload)}
|
|
|
10002
10284
|
|
|
10003
10285
|
// src/connectors-config.ts
|
|
10004
10286
|
import os4 from "os";
|
|
10005
|
-
import
|
|
10287
|
+
import path54 from "path";
|
|
10006
10288
|
import { promises as fs29 } from "fs";
|
|
10007
10289
|
var CONNECTORS_CONFIG_VERSION = 1;
|
|
10008
10290
|
var EnvRefUnsetError = class extends Error {
|
|
@@ -10017,11 +10299,11 @@ var EnvRefUnsetError = class extends Error {
|
|
|
10017
10299
|
};
|
|
10018
10300
|
function neatHome2() {
|
|
10019
10301
|
const override = process.env.NEAT_HOME;
|
|
10020
|
-
if (override && override.length > 0) return
|
|
10021
|
-
return
|
|
10302
|
+
if (override && override.length > 0) return path54.resolve(override);
|
|
10303
|
+
return path54.join(os4.homedir(), ".neat");
|
|
10022
10304
|
}
|
|
10023
10305
|
function connectorsConfigPath(home = neatHome2()) {
|
|
10024
|
-
return
|
|
10306
|
+
return path54.join(home, "connectors.json");
|
|
10025
10307
|
}
|
|
10026
10308
|
var MODE_MASK_LOOSER_THAN_0600 = 63;
|
|
10027
10309
|
async function warnIfModeLooserThan0600(file) {
|
|
@@ -10152,7 +10434,7 @@ function connectorMatchesProject(entry, project) {
|
|
|
10152
10434
|
var CONNECTORS_LOCK_TIMEOUT_MS = 5e3;
|
|
10153
10435
|
var CONNECTORS_LOCK_RETRY_MS = 50;
|
|
10154
10436
|
function connectorsConfigLockPath(home = neatHome2()) {
|
|
10155
|
-
return
|
|
10437
|
+
return path54.join(home, "connectors.json.lock");
|
|
10156
10438
|
}
|
|
10157
10439
|
function isEnvRef(value) {
|
|
10158
10440
|
return value.length > 1 && value.startsWith("$");
|
|
@@ -10165,7 +10447,7 @@ function redactCredentialRef(ref) {
|
|
|
10165
10447
|
return out;
|
|
10166
10448
|
}
|
|
10167
10449
|
async function writeConfigAtomically0600(file, contents) {
|
|
10168
|
-
await fs29.mkdir(
|
|
10450
|
+
await fs29.mkdir(path54.dirname(file), { recursive: true });
|
|
10169
10451
|
const tmp = `${file}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
|
|
10170
10452
|
const fd = await fs29.open(tmp, "w", 384);
|
|
10171
10453
|
try {
|
|
@@ -10179,7 +10461,7 @@ async function writeConfigAtomically0600(file, contents) {
|
|
|
10179
10461
|
}
|
|
10180
10462
|
async function acquireConnectorsLock(lockPath, timeoutMs = CONNECTORS_LOCK_TIMEOUT_MS) {
|
|
10181
10463
|
const deadline = Date.now() + timeoutMs;
|
|
10182
|
-
await fs29.mkdir(
|
|
10464
|
+
await fs29.mkdir(path54.dirname(lockPath), { recursive: true });
|
|
10183
10465
|
for (; ; ) {
|
|
10184
10466
|
try {
|
|
10185
10467
|
const fd = await fs29.open(lockPath, "wx");
|
|
@@ -10327,14 +10609,14 @@ function getConnectorStatus(id, now = Date.now(), thresholdMs = CONNECTOR_STALE_
|
|
|
10327
10609
|
}
|
|
10328
10610
|
|
|
10329
10611
|
// src/connectors/index.ts
|
|
10330
|
-
import { NodeType as
|
|
10612
|
+
import { NodeType as NodeType20, parseFileId as parseFileId2, Provenance as Provenance23 } from "@neat.is/types";
|
|
10331
10613
|
var NO_ENV = "unknown";
|
|
10332
10614
|
function staticCallSiteFor(graph, serviceName, targetNodeId) {
|
|
10333
10615
|
if (!graph.hasNode(targetNodeId)) return void 0;
|
|
10334
10616
|
const sites = [];
|
|
10335
10617
|
for (const edgeId of graph.inboundEdges(targetNodeId)) {
|
|
10336
10618
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
10337
|
-
if (edge.provenance !==
|
|
10619
|
+
if (edge.provenance !== Provenance23.EXTRACTED) continue;
|
|
10338
10620
|
const parsed = parseFileId2(edge.source);
|
|
10339
10621
|
if (!parsed || parsed.service !== serviceName || !edge.evidence) continue;
|
|
10340
10622
|
const site = { relPath: edge.evidence.file };
|
|
@@ -10346,7 +10628,7 @@ function staticCallSiteFor(graph, serviceName, targetNodeId) {
|
|
|
10346
10628
|
function routeCallSiteFor(graph, targetNodeId) {
|
|
10347
10629
|
if (!graph.hasNode(targetNodeId)) return void 0;
|
|
10348
10630
|
const attrs = graph.getNodeAttributes(targetNodeId);
|
|
10349
|
-
if (attrs.type !==
|
|
10631
|
+
if (attrs.type !== NodeType20.RouteNode || !attrs.path) return void 0;
|
|
10350
10632
|
const site = { relPath: attrs.path };
|
|
10351
10633
|
if (attrs.line !== void 0) site.line = attrs.line;
|
|
10352
10634
|
return site;
|
|
@@ -10366,6 +10648,7 @@ async function runConnectorPoll(connector, ctx, graph, resolveTarget) {
|
|
|
10366
10648
|
const { kind, name, provider } = resolved.ensureInfraNode;
|
|
10367
10649
|
ensureInfraNode(graph, kind, name, provider);
|
|
10368
10650
|
}
|
|
10651
|
+
mergeObservedColumns(graph, resolved.targetNodeId, signal.columns);
|
|
10369
10652
|
const serviceNodeId = ensureServiceNode(graph, resolved.serviceName, NO_ENV);
|
|
10370
10653
|
const callSite = signal.callSite ? { relPath: signal.callSite.file, line: signal.callSite.line } : routeCallSiteFor(graph, resolved.targetNodeId) ?? staticCallSiteFor(graph, resolved.serviceName, resolved.targetNodeId);
|
|
10371
10654
|
const sourceId = callSite ? ensureObservedFileNode(graph, resolved.serviceName, serviceNodeId, callSite) : serviceNodeId;
|
|
@@ -10809,10 +11092,10 @@ var SUPABASE_RPC_TARGET_KIND = "supabase-rpc";
|
|
|
10809
11092
|
// src/connectors/supabase/map.ts
|
|
10810
11093
|
var REST_RPC_PATH_RE = /^\/rest\/v1\/rpc\/([^/?]+)/;
|
|
10811
11094
|
var REST_TABLE_PATH_RE = /^\/rest\/v1\/([^/?]+)/;
|
|
10812
|
-
function targetFromRestPath(
|
|
10813
|
-
const rpcMatch = REST_RPC_PATH_RE.exec(
|
|
11095
|
+
function targetFromRestPath(path55) {
|
|
11096
|
+
const rpcMatch = REST_RPC_PATH_RE.exec(path55);
|
|
10814
11097
|
if (rpcMatch) return { targetKind: SUPABASE_RPC_TARGET_KIND, name: rpcMatch[1] };
|
|
10815
|
-
const tableMatch = REST_TABLE_PATH_RE.exec(
|
|
11098
|
+
const tableMatch = REST_TABLE_PATH_RE.exec(path55);
|
|
10816
11099
|
if (tableMatch) return { targetKind: SUPABASE_TABLE_TARGET_KIND, name: tableMatch[1] };
|
|
10817
11100
|
return null;
|
|
10818
11101
|
}
|
|
@@ -10876,12 +11159,14 @@ function diffPgStatStatementsToSignals(rows, previous, nowIso2) {
|
|
|
10876
11159
|
if (delta <= 0) continue;
|
|
10877
11160
|
const table = tableNameFromQueryText(row.query);
|
|
10878
11161
|
if (!table) continue;
|
|
11162
|
+
const columns = columnsFromSqlStatement(row.query);
|
|
10879
11163
|
signals.push({
|
|
10880
11164
|
targetKind: SUPABASE_TABLE_TARGET_KIND,
|
|
10881
11165
|
targetName: table,
|
|
10882
11166
|
callCount: delta,
|
|
10883
11167
|
errorCount: 0,
|
|
10884
|
-
lastObservedIso: nowIso2
|
|
11168
|
+
lastObservedIso: nowIso2,
|
|
11169
|
+
...columns.length > 0 ? { columns } : {}
|
|
10885
11170
|
});
|
|
10886
11171
|
}
|
|
10887
11172
|
for (const queryid of [...previous.keys()]) {
|
|
@@ -10919,21 +11204,21 @@ async function fetchPgStatStatements(connectionString, limit = DEFAULT_STATEMENT
|
|
|
10919
11204
|
}
|
|
10920
11205
|
|
|
10921
11206
|
// src/connectors/supabase/resolve.ts
|
|
10922
|
-
import { EdgeType as EdgeType23, infraId as
|
|
11207
|
+
import { EdgeType as EdgeType23, infraId as infraId13 } from "@neat.is/types";
|
|
10923
11208
|
function createSupabaseResolveTarget(graph, config) {
|
|
10924
11209
|
return (signal, _ctx) => {
|
|
10925
11210
|
if (signal.targetKind !== SUPABASE_TABLE_TARGET_KIND && signal.targetKind !== SUPABASE_RPC_TARGET_KIND) {
|
|
10926
11211
|
return null;
|
|
10927
11212
|
}
|
|
10928
|
-
const subResourceId =
|
|
11213
|
+
const subResourceId = infraId13(signal.targetKind, `${config.nodeRef}/${signal.targetName}`);
|
|
10929
11214
|
if (graph.hasNode(subResourceId)) {
|
|
10930
11215
|
return { targetNodeId: subResourceId, serviceName: config.serviceName, edgeType: EdgeType23.CALLS };
|
|
10931
11216
|
}
|
|
10932
|
-
const bareResourceId =
|
|
11217
|
+
const bareResourceId = infraId13(signal.targetKind, signal.targetName);
|
|
10933
11218
|
if (graph.hasNode(bareResourceId)) {
|
|
10934
11219
|
return { targetNodeId: bareResourceId, serviceName: config.serviceName, edgeType: EdgeType23.CALLS };
|
|
10935
11220
|
}
|
|
10936
|
-
const projectLevelId =
|
|
11221
|
+
const projectLevelId = infraId13("supabase", config.nodeRef);
|
|
10937
11222
|
if (graph.hasNode(projectLevelId)) {
|
|
10938
11223
|
return { targetNodeId: projectLevelId, serviceName: config.serviceName, edgeType: EdgeType23.CALLS };
|
|
10939
11224
|
}
|
|
@@ -11027,7 +11312,7 @@ function createSupabaseConnector(graph, config, deps = {}) {
|
|
|
11027
11312
|
}
|
|
11028
11313
|
|
|
11029
11314
|
// src/connectors/railway/index.ts
|
|
11030
|
-
import { EdgeType as EdgeType24, NodeType as
|
|
11315
|
+
import { EdgeType as EdgeType24, NodeType as NodeType21, serviceId as serviceId6 } from "@neat.is/types";
|
|
11031
11316
|
|
|
11032
11317
|
// src/connectors/railway/client.ts
|
|
11033
11318
|
var DEFAULT_RAILWAY_API_URL = "https://backboard.railway.com/graphql/v2";
|
|
@@ -11177,7 +11462,7 @@ function buildRailwayRouteIndex(graph, serviceName) {
|
|
|
11177
11462
|
const out = [];
|
|
11178
11463
|
graph.forEachNode((_id, attrs) => {
|
|
11179
11464
|
const node = attrs;
|
|
11180
|
-
if (node.type !==
|
|
11465
|
+
if (node.type !== NodeType21.RouteNode) return;
|
|
11181
11466
|
const route = attrs;
|
|
11182
11467
|
if (route.service !== serviceName) return;
|
|
11183
11468
|
out.push({
|
|
@@ -11405,9 +11690,9 @@ function parseFirebaseTargetName(targetName) {
|
|
|
11405
11690
|
const secondSep = rest.indexOf(FIELD_SEP);
|
|
11406
11691
|
if (secondSep === -1) return null;
|
|
11407
11692
|
const method = rest.slice(0, secondSep);
|
|
11408
|
-
const
|
|
11409
|
-
if (!resourceName || !method || !
|
|
11410
|
-
return { resourceName, method, path:
|
|
11693
|
+
const path55 = rest.slice(secondSep + 1);
|
|
11694
|
+
if (!resourceName || !method || !path55) return null;
|
|
11695
|
+
return { resourceName, method, path: path55 };
|
|
11411
11696
|
}
|
|
11412
11697
|
function resourceNameFor(type, labels) {
|
|
11413
11698
|
if (!labels) return null;
|
|
@@ -11445,14 +11730,14 @@ function mapLogEntryToSignal(entry) {
|
|
|
11445
11730
|
if (!req) return null;
|
|
11446
11731
|
if (typeof req.requestMethod !== "string" || req.requestMethod.length === 0) return null;
|
|
11447
11732
|
const method = req.requestMethod.toUpperCase();
|
|
11448
|
-
const
|
|
11449
|
-
if (
|
|
11733
|
+
const path55 = pathFromRequestUrl(req.requestUrl);
|
|
11734
|
+
if (path55 === null) return null;
|
|
11450
11735
|
const timestamp = entry.timestamp;
|
|
11451
11736
|
if (typeof timestamp !== "string" || timestamp.length === 0) return null;
|
|
11452
11737
|
const isError = typeof req.status === "number" && req.status >= ERROR_STATUS_THRESHOLD2;
|
|
11453
11738
|
return {
|
|
11454
11739
|
targetKind: resourceType,
|
|
11455
|
-
targetName: packFirebaseTargetName({ resourceName, method, path:
|
|
11740
|
+
targetName: packFirebaseTargetName({ resourceName, method, path: path55 }),
|
|
11456
11741
|
callCount: 1,
|
|
11457
11742
|
errorCount: isError ? 1 : 0,
|
|
11458
11743
|
lastObservedIso: timestamp
|
|
@@ -11468,7 +11753,7 @@ function mapLogEntriesToSignals(entries) {
|
|
|
11468
11753
|
}
|
|
11469
11754
|
|
|
11470
11755
|
// src/connectors/firebase/resolve.ts
|
|
11471
|
-
import { NodeType as
|
|
11756
|
+
import { NodeType as NodeType22, EdgeType as EdgeType25 } from "@neat.is/types";
|
|
11472
11757
|
function neatServiceNameFor(resourceType, resourceName, serviceMap) {
|
|
11473
11758
|
switch (resourceType) {
|
|
11474
11759
|
case "cloud_function":
|
|
@@ -11483,7 +11768,7 @@ function routeEntriesFor(graph, serviceName) {
|
|
|
11483
11768
|
const entries = [];
|
|
11484
11769
|
graph.forEachNode((_id, attrs) => {
|
|
11485
11770
|
const node = attrs;
|
|
11486
|
-
if (node.type !==
|
|
11771
|
+
if (node.type !== NodeType22.RouteNode) return;
|
|
11487
11772
|
const route = attrs;
|
|
11488
11773
|
if (route.service !== serviceName) return;
|
|
11489
11774
|
entries.push({
|
|
@@ -11538,7 +11823,7 @@ function createFirebaseConnector(graph, serviceMap) {
|
|
|
11538
11823
|
}
|
|
11539
11824
|
|
|
11540
11825
|
// src/connectors/cloudflare/connector.ts
|
|
11541
|
-
import { EdgeType as EdgeType26, NodeType as
|
|
11826
|
+
import { EdgeType as EdgeType26, NodeType as NodeType23, fileId as fileId4, infraId as infraId14 } from "@neat.is/types";
|
|
11542
11827
|
|
|
11543
11828
|
// src/connectors/cloudflare/client.ts
|
|
11544
11829
|
import { randomUUID } from "crypto";
|
|
@@ -11649,7 +11934,7 @@ function mapEventToSignal(event) {
|
|
|
11649
11934
|
if (Number.isNaN(observedAt.getTime())) return null;
|
|
11650
11935
|
const statusCode = metadata?.statusCode;
|
|
11651
11936
|
const isError = typeof statusCode === "number" && statusCode >= ERROR_STATUS_THRESHOLD3;
|
|
11652
|
-
const
|
|
11937
|
+
const path55 = metadata?.trigger ? parsePathFromTrigger(metadata.trigger) : void 0;
|
|
11653
11938
|
return {
|
|
11654
11939
|
targetKind: CLOUDFLARE_TARGET_KIND,
|
|
11655
11940
|
targetName: scriptName,
|
|
@@ -11657,7 +11942,7 @@ function mapEventToSignal(event) {
|
|
|
11657
11942
|
errorCount: isError ? 1 : 0,
|
|
11658
11943
|
lastObservedIso: observedAt.toISOString(),
|
|
11659
11944
|
method,
|
|
11660
|
-
...
|
|
11945
|
+
...path55 ? { path: path55 } : {},
|
|
11661
11946
|
...typeof statusCode === "number" ? { statusCode } : {},
|
|
11662
11947
|
...typeof metadata?.duration === "number" ? { duration: metadata.duration } : {}
|
|
11663
11948
|
};
|
|
@@ -11697,19 +11982,19 @@ function findTaggedWorkerFileNode(graph, workerName) {
|
|
|
11697
11982
|
graph.forEachNode((id, attrs) => {
|
|
11698
11983
|
if (found) return;
|
|
11699
11984
|
const a = attrs;
|
|
11700
|
-
if (a.type ===
|
|
11985
|
+
if (a.type === NodeType23.FileNode && a.platform === "cloudflare" && a.platformName === workerName) {
|
|
11701
11986
|
found = id;
|
|
11702
11987
|
}
|
|
11703
11988
|
});
|
|
11704
11989
|
return found;
|
|
11705
11990
|
}
|
|
11706
|
-
function findMatchingRouteNode(graph, serviceName, method,
|
|
11707
|
-
const normalizedPath = normalizePathTemplate(
|
|
11991
|
+
function findMatchingRouteNode(graph, serviceName, method, path55) {
|
|
11992
|
+
const normalizedPath = normalizePathTemplate(path55);
|
|
11708
11993
|
let found = null;
|
|
11709
11994
|
graph.forEachNode((id, attrs) => {
|
|
11710
11995
|
if (found) return;
|
|
11711
11996
|
const a = attrs;
|
|
11712
|
-
if (a.type !==
|
|
11997
|
+
if (a.type !== NodeType23.RouteNode || a.service !== serviceName) return;
|
|
11713
11998
|
if (!a.pathTemplate || normalizePathTemplate(a.pathTemplate) !== normalizedPath) return;
|
|
11714
11999
|
const routeMethod = (a.method ?? "").toUpperCase();
|
|
11715
12000
|
if (routeMethod !== "ALL" && routeMethod !== method) return;
|
|
@@ -11721,10 +12006,10 @@ function createCloudflareResolveTarget(config, graph) {
|
|
|
11721
12006
|
return (signal) => {
|
|
11722
12007
|
if (signal.targetKind !== CLOUDFLARE_TARGET_KIND) return null;
|
|
11723
12008
|
const scriptName = signal.targetName;
|
|
11724
|
-
const { method, path:
|
|
12009
|
+
const { method, path: path55 } = signal;
|
|
11725
12010
|
const resolveRouteGrain = (serviceName, wholeFileId) => {
|
|
11726
|
-
if (!method || !
|
|
11727
|
-
return findMatchingRouteNode(graph, serviceName, method,
|
|
12011
|
+
if (!method || !path55) return wholeFileId;
|
|
12012
|
+
return findMatchingRouteNode(graph, serviceName, method, path55) ?? wholeFileId;
|
|
11728
12013
|
};
|
|
11729
12014
|
const mapping = config.workers?.[scriptName];
|
|
11730
12015
|
if (mapping) {
|
|
@@ -11745,7 +12030,7 @@ function createCloudflareResolveTarget(config, graph) {
|
|
|
11745
12030
|
};
|
|
11746
12031
|
}
|
|
11747
12032
|
return {
|
|
11748
|
-
targetNodeId:
|
|
12033
|
+
targetNodeId: infraId14("cloudflare-worker", scriptName),
|
|
11749
12034
|
serviceName: scriptName,
|
|
11750
12035
|
edgeType: EdgeType26.CALLS,
|
|
11751
12036
|
ensureInfraNode: { kind: "cloudflare-worker", name: scriptName, provider: "cloudflare" }
|
|
@@ -11912,12 +12197,14 @@ function diffNeonStatementsToSignals(rows, previous, observedAtIso) {
|
|
|
11912
12197
|
if (!prior || calls <= prior.calls) continue;
|
|
11913
12198
|
const table = tableFromSqlStatement(row.query);
|
|
11914
12199
|
if (!table) continue;
|
|
12200
|
+
const columns = columnsFromSqlStatement(row.query);
|
|
11915
12201
|
signals.push({
|
|
11916
12202
|
targetKind: NEON_SQL_TABLE_TARGET_KIND,
|
|
11917
12203
|
targetName: table,
|
|
11918
12204
|
callCount: calls - prior.calls,
|
|
11919
12205
|
errorCount: 0,
|
|
11920
|
-
lastObservedIso: observedAtIso
|
|
12206
|
+
lastObservedIso: observedAtIso,
|
|
12207
|
+
...columns.length > 0 ? { columns } : {}
|
|
11921
12208
|
});
|
|
11922
12209
|
}
|
|
11923
12210
|
for (const queryid of previous.keys()) {
|
|
@@ -11927,12 +12214,12 @@ function diffNeonStatementsToSignals(rows, previous, observedAtIso) {
|
|
|
11927
12214
|
}
|
|
11928
12215
|
|
|
11929
12216
|
// src/connectors/neon/resolve.ts
|
|
11930
|
-
import { EdgeType as EdgeType27, infraId as
|
|
12217
|
+
import { EdgeType as EdgeType27, infraId as infraId15 } from "@neat.is/types";
|
|
11931
12218
|
function createNeonResolveTarget(config) {
|
|
11932
12219
|
return (signal) => {
|
|
11933
12220
|
if (signal.targetKind !== NEON_SQL_TABLE_TARGET_KIND || !signal.targetName) return null;
|
|
11934
12221
|
return {
|
|
11935
|
-
targetNodeId:
|
|
12222
|
+
targetNodeId: infraId15("sql-table", signal.targetName),
|
|
11936
12223
|
serviceName: config.serviceName,
|
|
11937
12224
|
edgeType: EdgeType27.CALLS,
|
|
11938
12225
|
ensureInfraNode: { kind: "sql-table", name: signal.targetName, provider: "neon" }
|
|
@@ -13327,4 +13614,4 @@ export {
|
|
|
13327
13614
|
deprovisionConnector,
|
|
13328
13615
|
buildApi
|
|
13329
13616
|
};
|
|
13330
|
-
//# sourceMappingURL=chunk-
|
|
13617
|
+
//# sourceMappingURL=chunk-RR4LWQQB.js.map
|