@neat.is/core 0.4.28-dev.20260707 → 0.4.28-dev.20260709
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-QM6BMPVJ.js → chunk-5T6J3WKC.js} +493 -132
- package/dist/chunk-5T6J3WKC.js.map +1 -0
- package/dist/{chunk-A3322JYS.js → chunk-CFDPIMRP.js} +13 -9
- package/dist/chunk-CFDPIMRP.js.map +1 -0
- package/dist/{chunk-UV5WSM7M.js → chunk-I72HTUOG.js} +2 -2
- package/dist/chunk-UJ6WBLIE.js +2639 -0
- package/dist/chunk-UJ6WBLIE.js.map +1 -0
- package/dist/cli.cjs +2851 -472
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +358 -4
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +2223 -259
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +14 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +4 -4
- package/dist/neatd.cjs +2281 -317
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +3 -3
- package/dist/{otel-grpc-ET5Z6KI6.js → otel-grpc-IDMIH6ZY.js} +3 -3
- package/dist/server.cjs +455 -105
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +3 -3
- package/package.json +3 -3
- package/dist/chunk-A3322JYS.js.map +0 -1
- package/dist/chunk-QM6BMPVJ.js.map +0 -1
- package/dist/chunk-XV4D7A3Z.js +0 -907
- package/dist/chunk-XV4D7A3Z.js.map +0 -1
- /package/dist/{chunk-UV5WSM7M.js.map → chunk-I72HTUOG.js.map} +0 -0
- /package/dist/{otel-grpc-ET5Z6KI6.js.map → otel-grpc-IDMIH6ZY.js.map} +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
mountBearerAuth,
|
|
3
3
|
readAuthEnv
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-CFDPIMRP.js";
|
|
5
5
|
|
|
6
6
|
// src/graph.ts
|
|
7
7
|
import GraphDefault from "graphology";
|
|
@@ -456,19 +456,19 @@ function confidenceFromMix(edges, now = Date.now()) {
|
|
|
456
456
|
function longestIncomingWalk(graph, start, maxDepth) {
|
|
457
457
|
let best = { path: [start], edges: [] };
|
|
458
458
|
const visited = /* @__PURE__ */ new Set([start]);
|
|
459
|
-
function step(node,
|
|
460
|
-
if (
|
|
461
|
-
best = { path: [...
|
|
459
|
+
function step(node, path43, edges) {
|
|
460
|
+
if (path43.length > best.path.length) {
|
|
461
|
+
best = { path: [...path43], edges: [...edges] };
|
|
462
462
|
}
|
|
463
|
-
if (
|
|
463
|
+
if (path43.length - 1 >= maxDepth) return;
|
|
464
464
|
const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
|
|
465
465
|
for (const [srcId, edge] of incoming) {
|
|
466
466
|
if (visited.has(srcId)) continue;
|
|
467
467
|
visited.add(srcId);
|
|
468
|
-
|
|
468
|
+
path43.push(srcId);
|
|
469
469
|
edges.push(edge);
|
|
470
|
-
step(srcId,
|
|
471
|
-
|
|
470
|
+
step(srcId, path43, edges);
|
|
471
|
+
path43.pop();
|
|
472
472
|
edges.pop();
|
|
473
473
|
visited.delete(srcId);
|
|
474
474
|
}
|
|
@@ -662,26 +662,26 @@ function dominantFailingCall(graph, serviceId4, visited) {
|
|
|
662
662
|
return best;
|
|
663
663
|
}
|
|
664
664
|
function followFailingCallChain(graph, originServiceId, maxDepth) {
|
|
665
|
-
const
|
|
665
|
+
const path43 = [originServiceId];
|
|
666
666
|
const edges = [];
|
|
667
667
|
const visited = /* @__PURE__ */ new Set([originServiceId]);
|
|
668
668
|
let current = originServiceId;
|
|
669
669
|
for (let depth = 0; depth < maxDepth; depth++) {
|
|
670
670
|
const hop = dominantFailingCall(graph, current, visited);
|
|
671
671
|
if (!hop) break;
|
|
672
|
-
|
|
672
|
+
path43.push(hop.nextService);
|
|
673
673
|
edges.push(hop.edge);
|
|
674
674
|
visited.add(hop.nextService);
|
|
675
675
|
current = hop.nextService;
|
|
676
676
|
}
|
|
677
677
|
if (edges.length === 0) return null;
|
|
678
|
-
return { path:
|
|
678
|
+
return { path: path43, edges, culprit: current };
|
|
679
679
|
}
|
|
680
680
|
function crossServiceRootCause(graph, originId, incidents, errorEvent) {
|
|
681
681
|
const chain = followFailingCallChain(graph, originId, ROOT_CAUSE_MAX_DEPTH);
|
|
682
682
|
if (!chain) return null;
|
|
683
683
|
const culprit = chain.culprit;
|
|
684
|
-
const
|
|
684
|
+
const path43 = [...chain.path];
|
|
685
685
|
const edgeProvenances = chain.edges.map((e) => e.provenance);
|
|
686
686
|
const baseConfidence = confidenceFromMix(chain.edges);
|
|
687
687
|
const confidence = Math.max(0, Math.min(1, baseConfidence * INCIDENT_ROOT_CAUSE_CONFIDENCE));
|
|
@@ -689,14 +689,14 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
|
|
|
689
689
|
if (loc) {
|
|
690
690
|
let rootCauseNode = culprit;
|
|
691
691
|
if (loc.fileNode) {
|
|
692
|
-
|
|
692
|
+
path43.push(loc.fileNode);
|
|
693
693
|
edgeProvenances.push(Provenance.OBSERVED);
|
|
694
694
|
rootCauseNode = loc.fileNode;
|
|
695
695
|
}
|
|
696
696
|
return RootCauseResultSchema.parse({
|
|
697
697
|
rootCauseNode,
|
|
698
698
|
rootCauseReason: loc.rootCauseReason,
|
|
699
|
-
traversalPath:
|
|
699
|
+
traversalPath: path43,
|
|
700
700
|
edgeProvenances,
|
|
701
701
|
confidence,
|
|
702
702
|
...loc.fixRecommendation ? { fixRecommendation: loc.fixRecommendation } : {}
|
|
@@ -708,7 +708,7 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
|
|
|
708
708
|
return RootCauseResultSchema.parse({
|
|
709
709
|
rootCauseNode: culprit,
|
|
710
710
|
rootCauseReason: `${culpritName} is failing downstream calls (${errs} observed error${errs === 1 ? "" : "s"})`,
|
|
711
|
-
traversalPath:
|
|
711
|
+
traversalPath: path43,
|
|
712
712
|
edgeProvenances,
|
|
713
713
|
confidence,
|
|
714
714
|
fixRecommendation: `Inspect ${culpritName}'s failing handler`
|
|
@@ -1373,6 +1373,8 @@ var PolicyViolationsLog = class {
|
|
|
1373
1373
|
// src/ingest.ts
|
|
1374
1374
|
import {
|
|
1375
1375
|
EdgeType as EdgeType3,
|
|
1376
|
+
GraphEdgeSchema,
|
|
1377
|
+
GraphNodeSchema,
|
|
1376
1378
|
NodeType as NodeType3,
|
|
1377
1379
|
Provenance as Provenance2,
|
|
1378
1380
|
confidenceForObservedSignal,
|
|
@@ -1877,6 +1879,19 @@ function ensureServiceNode(graph, serviceName, env) {
|
|
|
1877
1879
|
graph.addNode(id, node);
|
|
1878
1880
|
return id;
|
|
1879
1881
|
}
|
|
1882
|
+
function ensureInfraNode(graph, kind, name, provider) {
|
|
1883
|
+
const id = infraId(kind, name);
|
|
1884
|
+
if (graph.hasNode(id)) return id;
|
|
1885
|
+
const node = {
|
|
1886
|
+
id,
|
|
1887
|
+
type: NodeType3.InfraNode,
|
|
1888
|
+
name,
|
|
1889
|
+
provider,
|
|
1890
|
+
kind
|
|
1891
|
+
};
|
|
1892
|
+
graph.addNode(id, node);
|
|
1893
|
+
return id;
|
|
1894
|
+
}
|
|
1880
1895
|
function ensureDatabaseNode(graph, host, engine) {
|
|
1881
1896
|
const id = databaseId(host);
|
|
1882
1897
|
if (graph.hasNode(id)) return id;
|
|
@@ -2529,24 +2544,58 @@ function dedupeIncidents(events) {
|
|
|
2529
2544
|
return !hasRealFailure.has(groupKey(ev));
|
|
2530
2545
|
});
|
|
2531
2546
|
}
|
|
2547
|
+
var SnapshotValidationError = class extends Error {
|
|
2548
|
+
constructor(issues) {
|
|
2549
|
+
super(`snapshot failed validation (${issues.length} invalid ${issues.length === 1 ? "entry" : "entries"})`);
|
|
2550
|
+
this.issues = issues;
|
|
2551
|
+
this.name = "SnapshotValidationError";
|
|
2552
|
+
}
|
|
2553
|
+
issues;
|
|
2554
|
+
};
|
|
2555
|
+
function describeZodIssues(error) {
|
|
2556
|
+
return error.issues.map((issue) => issue.path.length > 0 ? `${issue.path.join(".")}: ${issue.message}` : issue.message).join("; ");
|
|
2557
|
+
}
|
|
2532
2558
|
function mergeSnapshot(graph, snapshot) {
|
|
2533
2559
|
const exported = snapshot.graph;
|
|
2560
|
+
const incomingNodes = Array.isArray(exported.nodes) ? exported.nodes : [];
|
|
2561
|
+
const incomingEdges = Array.isArray(exported.edges) ? exported.edges : [];
|
|
2562
|
+
const issues = [];
|
|
2563
|
+
const validNodes = [];
|
|
2564
|
+
const validEdges = [];
|
|
2565
|
+
for (const node of incomingNodes) {
|
|
2566
|
+
if (node.attributes === void 0) continue;
|
|
2567
|
+
const parsed = GraphNodeSchema.safeParse(node.attributes);
|
|
2568
|
+
if (!parsed.success) {
|
|
2569
|
+
issues.push(`node "${node.key}": ${describeZodIssues(parsed.error)}`);
|
|
2570
|
+
continue;
|
|
2571
|
+
}
|
|
2572
|
+
validNodes.push({ key: node.key, attributes: parsed.data });
|
|
2573
|
+
}
|
|
2574
|
+
for (const edge of incomingEdges) {
|
|
2575
|
+
if (edge.attributes === void 0) continue;
|
|
2576
|
+
const parsed = GraphEdgeSchema.safeParse(edge.attributes);
|
|
2577
|
+
if (!parsed.success) {
|
|
2578
|
+
const label = edge.key ?? `${edge.source}->${edge.target}`;
|
|
2579
|
+
issues.push(`edge "${label}": ${describeZodIssues(parsed.error)}`);
|
|
2580
|
+
continue;
|
|
2581
|
+
}
|
|
2582
|
+
const id = edge.key ?? parsed.data.id;
|
|
2583
|
+
validEdges.push({ key: id, source: edge.source, target: edge.target, attributes: parsed.data });
|
|
2584
|
+
}
|
|
2585
|
+
if (issues.length > 0) {
|
|
2586
|
+
throw new SnapshotValidationError(issues);
|
|
2587
|
+
}
|
|
2534
2588
|
let nodesAdded = 0;
|
|
2535
2589
|
let edgesAdded = 0;
|
|
2536
|
-
for (const node of
|
|
2590
|
+
for (const node of validNodes) {
|
|
2537
2591
|
if (graph.hasNode(node.key)) continue;
|
|
2538
|
-
if (!node.attributes) continue;
|
|
2539
2592
|
graph.addNode(node.key, node.attributes);
|
|
2540
2593
|
nodesAdded++;
|
|
2541
2594
|
}
|
|
2542
|
-
for (const edge of
|
|
2543
|
-
|
|
2544
|
-
if (!attrs) continue;
|
|
2545
|
-
const id = edge.key ?? attrs.id;
|
|
2546
|
-
if (!id) continue;
|
|
2547
|
-
if (graph.hasEdge(id)) continue;
|
|
2595
|
+
for (const edge of validEdges) {
|
|
2596
|
+
if (graph.hasEdge(edge.key)) continue;
|
|
2548
2597
|
if (!graph.hasNode(edge.source) || !graph.hasNode(edge.target)) continue;
|
|
2549
|
-
graph.addEdgeWithKey(
|
|
2598
|
+
graph.addEdgeWithKey(edge.key, edge.source, edge.target, edge.attributes);
|
|
2550
2599
|
edgesAdded++;
|
|
2551
2600
|
}
|
|
2552
2601
|
return { nodesAdded, edgesAdded };
|
|
@@ -4622,10 +4671,10 @@ function fastifyRouteMethods(objNode) {
|
|
|
4622
4671
|
}
|
|
4623
4672
|
return [];
|
|
4624
4673
|
}
|
|
4625
|
-
function serverRoutesFromSource(source, parser, hasExpress, hasFastify) {
|
|
4674
|
+
function serverRoutesFromSource(source, parser, hasExpress, hasFastify, hasHono = false) {
|
|
4626
4675
|
const tree = parseSource2(parser, source);
|
|
4627
4676
|
const out = [];
|
|
4628
|
-
const framework = hasExpress ? "express" : "fastify";
|
|
4677
|
+
const framework = hasExpress ? "express" : hasFastify ? "fastify" : hasHono ? "hono" : "unknown";
|
|
4629
4678
|
walk(tree.rootNode, (node) => {
|
|
4630
4679
|
if (node.type !== "call_expression") return;
|
|
4631
4680
|
const fn = node.childForFieldName("function");
|
|
@@ -4775,8 +4824,9 @@ async function addRoutes(graph, services) {
|
|
|
4775
4824
|
};
|
|
4776
4825
|
const hasExpress = deps["express"] !== void 0;
|
|
4777
4826
|
const hasFastify = deps["fastify"] !== void 0;
|
|
4827
|
+
const hasHono = deps["hono"] !== void 0;
|
|
4778
4828
|
const hasNext = deps["next"] !== void 0;
|
|
4779
|
-
if (!hasExpress && !hasFastify && !hasNext) continue;
|
|
4829
|
+
if (!hasExpress && !hasFastify && !hasHono && !hasNext) continue;
|
|
4780
4830
|
const files = await loadSourceFiles(service.dir);
|
|
4781
4831
|
for (const file of files) {
|
|
4782
4832
|
if (isTestPath(file.path)) continue;
|
|
@@ -4786,8 +4836,8 @@ async function addRoutes(graph, services) {
|
|
|
4786
4836
|
try {
|
|
4787
4837
|
if (hasNext && (isNextAppRouteFile(relFile) || isNextPagesApiFile(relFile))) {
|
|
4788
4838
|
routes = nextRoutesFromFile(file.content, relFile, jsParser);
|
|
4789
|
-
} else if (hasExpress || hasFastify) {
|
|
4790
|
-
routes = serverRoutesFromSource(file.content, jsParser, hasExpress, hasFastify);
|
|
4839
|
+
} else if (hasExpress || hasFastify || hasHono) {
|
|
4840
|
+
routes = serverRoutesFromSource(file.content, jsParser, hasExpress, hasFastify, hasHono);
|
|
4791
4841
|
} else {
|
|
4792
4842
|
routes = [];
|
|
4793
4843
|
}
|
|
@@ -6119,25 +6169,258 @@ async function addK8sResources(graph, scanPath) {
|
|
|
6119
6169
|
return { nodesAdded, edgesAdded: 0 };
|
|
6120
6170
|
}
|
|
6121
6171
|
|
|
6172
|
+
// src/extract/infra/cloudflare.ts
|
|
6173
|
+
import { promises as fs19 } from "fs";
|
|
6174
|
+
import path35 from "path";
|
|
6175
|
+
import { parse as parseToml2 } from "smol-toml";
|
|
6176
|
+
import { EdgeType as EdgeType16, Provenance as Provenance15, confidenceForExtracted as confidenceForExtracted13 } from "@neat.is/types";
|
|
6177
|
+
var WRANGLER_FILENAMES = ["wrangler.toml", "wrangler.jsonc", "wrangler.json"];
|
|
6178
|
+
async function readWranglerConfig(dir) {
|
|
6179
|
+
for (const filename of WRANGLER_FILENAMES) {
|
|
6180
|
+
const abs = path35.join(dir, filename);
|
|
6181
|
+
if (!await exists(abs)) continue;
|
|
6182
|
+
const raw = await fs19.readFile(abs, "utf8");
|
|
6183
|
+
const config = filename === "wrangler.toml" ? parseToml2(raw) : JSON.parse(maskCommentsInSource(raw));
|
|
6184
|
+
return { config, relFile: filename, raw };
|
|
6185
|
+
}
|
|
6186
|
+
return null;
|
|
6187
|
+
}
|
|
6188
|
+
function lineContaining(raw, needle) {
|
|
6189
|
+
if (!needle) return void 0;
|
|
6190
|
+
const idx = raw.indexOf(needle);
|
|
6191
|
+
if (idx === -1) return void 0;
|
|
6192
|
+
let line = 1;
|
|
6193
|
+
for (let i = 0; i < idx; i++) if (raw[i] === "\n") line++;
|
|
6194
|
+
return line;
|
|
6195
|
+
}
|
|
6196
|
+
function normalizeRoutes(config) {
|
|
6197
|
+
const out = [];
|
|
6198
|
+
const pushOne = (r) => {
|
|
6199
|
+
if (typeof r === "string") out.push(r);
|
|
6200
|
+
else if (r && typeof r === "object" && typeof r.pattern === "string") {
|
|
6201
|
+
out.push(r.pattern);
|
|
6202
|
+
}
|
|
6203
|
+
};
|
|
6204
|
+
if (Array.isArray(config.routes)) config.routes.forEach(pushOne);
|
|
6205
|
+
else if (config.route) pushOne(config.route);
|
|
6206
|
+
return out;
|
|
6207
|
+
}
|
|
6208
|
+
function addResourceEdge(graph, anchorId, edgeType, kind, name, evidenceFile, line) {
|
|
6209
|
+
let nodesAdded = 0;
|
|
6210
|
+
let edgesAdded = 0;
|
|
6211
|
+
const node = makeInfraNode(kind, name, "cloudflare");
|
|
6212
|
+
if (!graph.hasNode(node.id)) {
|
|
6213
|
+
graph.addNode(node.id, node);
|
|
6214
|
+
nodesAdded++;
|
|
6215
|
+
}
|
|
6216
|
+
if (node.id === anchorId) return { nodesAdded, edgesAdded };
|
|
6217
|
+
const edgeId = extractedEdgeId2(anchorId, node.id, edgeType);
|
|
6218
|
+
if (!graph.hasEdge(edgeId)) {
|
|
6219
|
+
const edge = {
|
|
6220
|
+
id: edgeId,
|
|
6221
|
+
source: anchorId,
|
|
6222
|
+
target: node.id,
|
|
6223
|
+
type: edgeType,
|
|
6224
|
+
provenance: Provenance15.EXTRACTED,
|
|
6225
|
+
confidence: confidenceForExtracted13("structural"),
|
|
6226
|
+
evidence: { file: evidenceFile, ...line !== void 0 ? { line } : {} }
|
|
6227
|
+
};
|
|
6228
|
+
graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
|
|
6229
|
+
edgesAdded++;
|
|
6230
|
+
}
|
|
6231
|
+
return { nodesAdded, edgesAdded };
|
|
6232
|
+
}
|
|
6233
|
+
async function addCloudflareWorkers(graph, services, scanPath) {
|
|
6234
|
+
let nodesAdded = 0;
|
|
6235
|
+
let edgesAdded = 0;
|
|
6236
|
+
const discovered = [];
|
|
6237
|
+
const workerIndex = /* @__PURE__ */ new Map();
|
|
6238
|
+
for (const service of services) {
|
|
6239
|
+
let read;
|
|
6240
|
+
try {
|
|
6241
|
+
read = await readWranglerConfig(service.dir);
|
|
6242
|
+
} catch (err) {
|
|
6243
|
+
recordExtractionError("infra cloudflare", path35.relative(scanPath, service.dir), err);
|
|
6244
|
+
continue;
|
|
6245
|
+
}
|
|
6246
|
+
if (!read || !read.config.name) continue;
|
|
6247
|
+
const evidenceFile = toPosix2(path35.relative(scanPath, path35.join(service.dir, read.relFile)));
|
|
6248
|
+
discovered.push({ service, config: read.config, relFile: read.relFile, raw: read.raw, evidenceFile });
|
|
6249
|
+
}
|
|
6250
|
+
for (const worker of discovered) {
|
|
6251
|
+
const { service, config } = worker;
|
|
6252
|
+
const serviceNode = graph.getNodeAttributes(service.node.id);
|
|
6253
|
+
if (serviceNode.platform !== "cloudflare") {
|
|
6254
|
+
const updated = { ...serviceNode, platform: "cloudflare" };
|
|
6255
|
+
graph.replaceNodeAttributes(service.node.id, updated);
|
|
6256
|
+
}
|
|
6257
|
+
let anchorId = service.node.id;
|
|
6258
|
+
if (config.main) {
|
|
6259
|
+
const entryRelPath = toPosix2(path35.normalize(config.main));
|
|
6260
|
+
const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
|
|
6261
|
+
graph,
|
|
6262
|
+
service.pkg.name,
|
|
6263
|
+
service.node.id,
|
|
6264
|
+
entryRelPath
|
|
6265
|
+
);
|
|
6266
|
+
nodesAdded += fn;
|
|
6267
|
+
edgesAdded += fe;
|
|
6268
|
+
const fileNode = graph.getNodeAttributes(fileNodeId);
|
|
6269
|
+
if (fileNode.platform !== "cloudflare" || fileNode.platformName !== config.name) {
|
|
6270
|
+
const updated = { ...fileNode, platform: "cloudflare", platformName: config.name };
|
|
6271
|
+
graph.replaceNodeAttributes(fileNodeId, updated);
|
|
6272
|
+
}
|
|
6273
|
+
anchorId = fileNodeId;
|
|
6274
|
+
}
|
|
6275
|
+
workerIndex.set(config.name, { anchorId });
|
|
6276
|
+
}
|
|
6277
|
+
for (const worker of discovered) {
|
|
6278
|
+
const { config, evidenceFile, raw } = worker;
|
|
6279
|
+
const anchorId = workerIndex.get(config.name).anchorId;
|
|
6280
|
+
const runtimeNode = makeInfraNode("workerd", "cloudflare", "cloudflare");
|
|
6281
|
+
if (!graph.hasNode(runtimeNode.id)) {
|
|
6282
|
+
graph.addNode(runtimeNode.id, runtimeNode);
|
|
6283
|
+
nodesAdded++;
|
|
6284
|
+
}
|
|
6285
|
+
if (runtimeNode.id !== anchorId) {
|
|
6286
|
+
const runsOnId = extractedEdgeId2(anchorId, runtimeNode.id, EdgeType16.RUNS_ON);
|
|
6287
|
+
if (!graph.hasEdge(runsOnId)) {
|
|
6288
|
+
const edge = {
|
|
6289
|
+
id: runsOnId,
|
|
6290
|
+
source: anchorId,
|
|
6291
|
+
target: runtimeNode.id,
|
|
6292
|
+
type: EdgeType16.RUNS_ON,
|
|
6293
|
+
provenance: Provenance15.EXTRACTED,
|
|
6294
|
+
confidence: confidenceForExtracted13("structural"),
|
|
6295
|
+
evidence: {
|
|
6296
|
+
file: evidenceFile,
|
|
6297
|
+
...config.compatibility_date ? { snippet: `compatibility_date = ${config.compatibility_date}`.slice(0, 120) } : {}
|
|
6298
|
+
}
|
|
6299
|
+
};
|
|
6300
|
+
graph.addEdgeWithKey(runsOnId, edge.source, edge.target, edge);
|
|
6301
|
+
edgesAdded++;
|
|
6302
|
+
}
|
|
6303
|
+
}
|
|
6304
|
+
for (const route of normalizeRoutes(config)) {
|
|
6305
|
+
const result = addResourceEdge(
|
|
6306
|
+
graph,
|
|
6307
|
+
anchorId,
|
|
6308
|
+
EdgeType16.CONNECTS_TO,
|
|
6309
|
+
"cloudflare-route",
|
|
6310
|
+
route,
|
|
6311
|
+
evidenceFile,
|
|
6312
|
+
lineContaining(raw, route)
|
|
6313
|
+
);
|
|
6314
|
+
nodesAdded += result.nodesAdded;
|
|
6315
|
+
edgesAdded += result.edgesAdded;
|
|
6316
|
+
}
|
|
6317
|
+
const bindingGroups = [
|
|
6318
|
+
{ kind: "cloudflare-kv", entries: config.kv_namespaces ?? [], nameOf: (b) => b.binding },
|
|
6319
|
+
{ kind: "cloudflare-d1", entries: config.d1_databases ?? [], nameOf: (b) => b.binding },
|
|
6320
|
+
{ kind: "cloudflare-r2", entries: config.r2_buckets ?? [], nameOf: (b) => b.binding },
|
|
6321
|
+
{ kind: "cloudflare-durable-object", entries: config.durable_objects?.bindings ?? [], nameOf: (b) => b.name },
|
|
6322
|
+
{ kind: "cloudflare-queue", entries: config.queues?.producers ?? [], nameOf: (b) => b.queue },
|
|
6323
|
+
{ kind: "cloudflare-queue", entries: config.queues?.consumers ?? [], nameOf: (b) => b.queue }
|
|
6324
|
+
];
|
|
6325
|
+
for (const group of bindingGroups) {
|
|
6326
|
+
for (const entry of group.entries) {
|
|
6327
|
+
const name = group.nameOf(entry);
|
|
6328
|
+
if (!name) continue;
|
|
6329
|
+
const result = addResourceEdge(
|
|
6330
|
+
graph,
|
|
6331
|
+
anchorId,
|
|
6332
|
+
EdgeType16.DEPENDS_ON,
|
|
6333
|
+
group.kind,
|
|
6334
|
+
name,
|
|
6335
|
+
evidenceFile,
|
|
6336
|
+
lineContaining(raw, name)
|
|
6337
|
+
);
|
|
6338
|
+
nodesAdded += result.nodesAdded;
|
|
6339
|
+
edgesAdded += result.edgesAdded;
|
|
6340
|
+
}
|
|
6341
|
+
}
|
|
6342
|
+
for (const cron of config.triggers?.crons ?? []) {
|
|
6343
|
+
const result = addResourceEdge(
|
|
6344
|
+
graph,
|
|
6345
|
+
anchorId,
|
|
6346
|
+
EdgeType16.DEPENDS_ON,
|
|
6347
|
+
"cloudflare-cron",
|
|
6348
|
+
cron,
|
|
6349
|
+
evidenceFile,
|
|
6350
|
+
lineContaining(raw, cron)
|
|
6351
|
+
);
|
|
6352
|
+
nodesAdded += result.nodesAdded;
|
|
6353
|
+
edgesAdded += result.edgesAdded;
|
|
6354
|
+
}
|
|
6355
|
+
for (const varName of Object.keys(config.vars ?? {})) {
|
|
6356
|
+
const result = addResourceEdge(
|
|
6357
|
+
graph,
|
|
6358
|
+
anchorId,
|
|
6359
|
+
EdgeType16.DEPENDS_ON,
|
|
6360
|
+
"cloudflare-env-var",
|
|
6361
|
+
varName,
|
|
6362
|
+
evidenceFile,
|
|
6363
|
+
lineContaining(raw, varName)
|
|
6364
|
+
);
|
|
6365
|
+
nodesAdded += result.nodesAdded;
|
|
6366
|
+
edgesAdded += result.edgesAdded;
|
|
6367
|
+
}
|
|
6368
|
+
for (const svc of config.services ?? []) {
|
|
6369
|
+
if (!svc.service) continue;
|
|
6370
|
+
const target = workerIndex.get(svc.service);
|
|
6371
|
+
if (target && target.anchorId !== anchorId) {
|
|
6372
|
+
const edgeId = extractedEdgeId2(anchorId, target.anchorId, EdgeType16.CALLS);
|
|
6373
|
+
if (!graph.hasEdge(edgeId)) {
|
|
6374
|
+
const edge = {
|
|
6375
|
+
id: edgeId,
|
|
6376
|
+
source: anchorId,
|
|
6377
|
+
target: target.anchorId,
|
|
6378
|
+
type: EdgeType16.CALLS,
|
|
6379
|
+
provenance: Provenance15.EXTRACTED,
|
|
6380
|
+
confidence: confidenceForExtracted13("structural"),
|
|
6381
|
+
evidence: { file: evidenceFile, line: lineContaining(raw, svc.service) }
|
|
6382
|
+
};
|
|
6383
|
+
graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
|
|
6384
|
+
edgesAdded++;
|
|
6385
|
+
}
|
|
6386
|
+
continue;
|
|
6387
|
+
}
|
|
6388
|
+
const result = addResourceEdge(
|
|
6389
|
+
graph,
|
|
6390
|
+
anchorId,
|
|
6391
|
+
EdgeType16.DEPENDS_ON,
|
|
6392
|
+
"cloudflare-service-binding",
|
|
6393
|
+
svc.service,
|
|
6394
|
+
evidenceFile,
|
|
6395
|
+
lineContaining(raw, svc.service)
|
|
6396
|
+
);
|
|
6397
|
+
nodesAdded += result.nodesAdded;
|
|
6398
|
+
edgesAdded += result.edgesAdded;
|
|
6399
|
+
}
|
|
6400
|
+
}
|
|
6401
|
+
return { nodesAdded, edgesAdded };
|
|
6402
|
+
}
|
|
6403
|
+
|
|
6122
6404
|
// src/extract/infra/index.ts
|
|
6123
6405
|
async function addInfra(graph, scanPath, services) {
|
|
6124
6406
|
const compose = await addComposeInfra(graph, scanPath, services);
|
|
6125
6407
|
const dockerfile = await addDockerfileRuntimes(graph, services, scanPath);
|
|
6126
6408
|
const terraform = await addTerraformResources(graph, scanPath);
|
|
6127
6409
|
const k8s = await addK8sResources(graph, scanPath);
|
|
6410
|
+
const cloudflare = await addCloudflareWorkers(graph, services, scanPath);
|
|
6128
6411
|
return {
|
|
6129
|
-
nodesAdded: compose.nodesAdded + dockerfile.nodesAdded + terraform.nodesAdded + k8s.nodesAdded,
|
|
6130
|
-
edgesAdded: compose.edgesAdded + dockerfile.edgesAdded + terraform.edgesAdded + k8s.edgesAdded
|
|
6412
|
+
nodesAdded: compose.nodesAdded + dockerfile.nodesAdded + terraform.nodesAdded + k8s.nodesAdded + cloudflare.nodesAdded,
|
|
6413
|
+
edgesAdded: compose.edgesAdded + dockerfile.edgesAdded + terraform.edgesAdded + k8s.edgesAdded + cloudflare.edgesAdded
|
|
6131
6414
|
};
|
|
6132
6415
|
}
|
|
6133
6416
|
|
|
6134
6417
|
// src/extract/index.ts
|
|
6135
|
-
import
|
|
6418
|
+
import path37 from "path";
|
|
6136
6419
|
|
|
6137
6420
|
// src/extract/retire.ts
|
|
6138
6421
|
import { existsSync as existsSync2 } from "fs";
|
|
6139
|
-
import
|
|
6140
|
-
import { NodeType as NodeType14, Provenance as
|
|
6422
|
+
import path36 from "path";
|
|
6423
|
+
import { NodeType as NodeType14, Provenance as Provenance16 } from "@neat.is/types";
|
|
6141
6424
|
function dropOrphanedFileNodes(graph) {
|
|
6142
6425
|
const orphans = [];
|
|
6143
6426
|
graph.forEachNode((id, attrs) => {
|
|
@@ -6154,7 +6437,7 @@ function retireEdgesByFile(graph, file) {
|
|
|
6154
6437
|
const toDrop = [];
|
|
6155
6438
|
graph.forEachEdge((id, attrs) => {
|
|
6156
6439
|
const edge = attrs;
|
|
6157
|
-
if (edge.provenance !==
|
|
6440
|
+
if (edge.provenance !== Provenance16.EXTRACTED) return;
|
|
6158
6441
|
if (!edge.evidence?.file) return;
|
|
6159
6442
|
if (edge.evidence.file === normalized) toDrop.push(id);
|
|
6160
6443
|
});
|
|
@@ -6167,14 +6450,14 @@ function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = []) {
|
|
|
6167
6450
|
const bases = [scanPath, ...serviceDirs];
|
|
6168
6451
|
graph.forEachEdge((id, attrs) => {
|
|
6169
6452
|
const edge = attrs;
|
|
6170
|
-
if (edge.provenance !==
|
|
6453
|
+
if (edge.provenance !== Provenance16.EXTRACTED) return;
|
|
6171
6454
|
const evidenceFile = edge.evidence?.file;
|
|
6172
6455
|
if (!evidenceFile) return;
|
|
6173
|
-
if (
|
|
6456
|
+
if (path36.isAbsolute(evidenceFile)) {
|
|
6174
6457
|
if (!existsSync2(evidenceFile)) toDrop.push(id);
|
|
6175
6458
|
return;
|
|
6176
6459
|
}
|
|
6177
|
-
const found = bases.some((base) => existsSync2(
|
|
6460
|
+
const found = bases.some((base) => existsSync2(path36.join(base, evidenceFile)));
|
|
6178
6461
|
if (!found) toDrop.push(id);
|
|
6179
6462
|
});
|
|
6180
6463
|
for (const id of toDrop) graph.dropEdge(id);
|
|
@@ -6216,7 +6499,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
6216
6499
|
}
|
|
6217
6500
|
const droppedEntries = drainDroppedExtracted();
|
|
6218
6501
|
if (isRejectedLogEnabled() && opts.errorsPath && droppedEntries.length > 0) {
|
|
6219
|
-
const rejectedPath =
|
|
6502
|
+
const rejectedPath = path37.join(path37.dirname(opts.errorsPath), "rejected.ndjson");
|
|
6220
6503
|
try {
|
|
6221
6504
|
await writeRejectedExtracted(droppedEntries, rejectedPath);
|
|
6222
6505
|
} catch (err) {
|
|
@@ -6252,10 +6535,10 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
6252
6535
|
import {
|
|
6253
6536
|
databaseId as databaseId3,
|
|
6254
6537
|
DivergenceResultSchema,
|
|
6255
|
-
EdgeType as
|
|
6538
|
+
EdgeType as EdgeType17,
|
|
6256
6539
|
NodeType as NodeType15,
|
|
6257
6540
|
parseEdgeId,
|
|
6258
|
-
Provenance as
|
|
6541
|
+
Provenance as Provenance17
|
|
6259
6542
|
} from "@neat.is/types";
|
|
6260
6543
|
function bucketKey(source, target, type) {
|
|
6261
6544
|
return `${type}|${source}|${target}`;
|
|
@@ -6269,17 +6552,17 @@ function bucketEdges(graph) {
|
|
|
6269
6552
|
const key = bucketKey(e.source, e.target, e.type);
|
|
6270
6553
|
const cur = buckets.get(key) ?? { source: e.source, target: e.target, type: e.type };
|
|
6271
6554
|
switch (provenance) {
|
|
6272
|
-
case
|
|
6555
|
+
case Provenance17.EXTRACTED:
|
|
6273
6556
|
cur.extracted = e;
|
|
6274
6557
|
break;
|
|
6275
|
-
case
|
|
6558
|
+
case Provenance17.OBSERVED:
|
|
6276
6559
|
cur.observed = e;
|
|
6277
6560
|
break;
|
|
6278
|
-
case
|
|
6561
|
+
case Provenance17.INFERRED:
|
|
6279
6562
|
cur.inferred = e;
|
|
6280
6563
|
break;
|
|
6281
6564
|
default:
|
|
6282
|
-
if (e.provenance ===
|
|
6565
|
+
if (e.provenance === Provenance17.STALE) cur.stale = e;
|
|
6283
6566
|
}
|
|
6284
6567
|
buckets.set(key, cur);
|
|
6285
6568
|
});
|
|
@@ -6313,14 +6596,14 @@ function gradedConfidence(edge) {
|
|
|
6313
6596
|
return clampConfidence(confidenceForEdge(edge));
|
|
6314
6597
|
}
|
|
6315
6598
|
var OBSERVABLE_EDGE_TYPES = /* @__PURE__ */ new Set([
|
|
6316
|
-
|
|
6317
|
-
|
|
6318
|
-
|
|
6319
|
-
|
|
6599
|
+
EdgeType17.CALLS,
|
|
6600
|
+
EdgeType17.CONNECTS_TO,
|
|
6601
|
+
EdgeType17.PUBLISHES_TO,
|
|
6602
|
+
EdgeType17.CONSUMES_FROM
|
|
6320
6603
|
]);
|
|
6321
6604
|
function detectMissingDivergences(graph, bucket) {
|
|
6322
6605
|
const out = [];
|
|
6323
|
-
if (bucket.type ===
|
|
6606
|
+
if (bucket.type === EdgeType17.CONTAINS) return out;
|
|
6324
6607
|
if (bucket.extracted && !bucket.observed && OBSERVABLE_EDGE_TYPES.has(bucket.type)) {
|
|
6325
6608
|
if (!nodeIsFrontier(graph, bucket.target)) {
|
|
6326
6609
|
out.push({
|
|
@@ -6361,7 +6644,7 @@ function declaredHostFor(svc) {
|
|
|
6361
6644
|
function hasExtractedConfiguredBy(graph, svcId) {
|
|
6362
6645
|
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
6363
6646
|
const e = graph.getEdgeAttributes(edgeId);
|
|
6364
|
-
if (e.type ===
|
|
6647
|
+
if (e.type === EdgeType17.CONFIGURED_BY && e.provenance === Provenance17.EXTRACTED) {
|
|
6365
6648
|
return true;
|
|
6366
6649
|
}
|
|
6367
6650
|
}
|
|
@@ -6374,8 +6657,8 @@ function detectHostMismatch(graph, svcId, svc) {
|
|
|
6374
6657
|
const out = [];
|
|
6375
6658
|
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
6376
6659
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
6377
|
-
if (edge.type !==
|
|
6378
|
-
if (edge.provenance !==
|
|
6660
|
+
if (edge.type !== EdgeType17.CONNECTS_TO) continue;
|
|
6661
|
+
if (edge.provenance !== Provenance17.OBSERVED) continue;
|
|
6379
6662
|
const target = graph.getNodeAttributes(edge.target);
|
|
6380
6663
|
if (target.type !== NodeType15.DatabaseNode) continue;
|
|
6381
6664
|
const observedHost = target.host?.trim();
|
|
@@ -6399,8 +6682,8 @@ function detectCompatDivergences(graph, svcId, svc) {
|
|
|
6399
6682
|
const deps = svc.dependencies ?? {};
|
|
6400
6683
|
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
6401
6684
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
6402
|
-
if (edge.type !==
|
|
6403
|
-
if (edge.provenance !==
|
|
6685
|
+
if (edge.type !== EdgeType17.CONNECTS_TO) continue;
|
|
6686
|
+
if (edge.provenance !== Provenance17.OBSERVED) continue;
|
|
6404
6687
|
const target = graph.getNodeAttributes(edge.target);
|
|
6405
6688
|
if (target.type !== NodeType15.DatabaseNode) continue;
|
|
6406
6689
|
for (const pair of compatPairs()) {
|
|
@@ -6522,9 +6805,9 @@ function computeDivergences(graph, opts = {}) {
|
|
|
6522
6805
|
}
|
|
6523
6806
|
|
|
6524
6807
|
// src/persist.ts
|
|
6525
|
-
import { promises as
|
|
6526
|
-
import
|
|
6527
|
-
import { Provenance as
|
|
6808
|
+
import { promises as fs20 } from "fs";
|
|
6809
|
+
import path38 from "path";
|
|
6810
|
+
import { Provenance as Provenance18, observedEdgeId as observedEdgeId2 } from "@neat.is/types";
|
|
6528
6811
|
var SCHEMA_VERSION = 4;
|
|
6529
6812
|
function migrateV1ToV2(payload) {
|
|
6530
6813
|
const nodes = payload.graph.nodes;
|
|
@@ -6546,7 +6829,7 @@ function migrateV2ToV3(payload) {
|
|
|
6546
6829
|
for (const edge of edges) {
|
|
6547
6830
|
const attrs = edge.attributes;
|
|
6548
6831
|
if (!attrs || attrs.provenance !== "FRONTIER") continue;
|
|
6549
|
-
attrs.provenance =
|
|
6832
|
+
attrs.provenance = Provenance18.OBSERVED;
|
|
6550
6833
|
const type = typeof attrs.type === "string" ? attrs.type : void 0;
|
|
6551
6834
|
const source = typeof attrs.source === "string" ? attrs.source : void 0;
|
|
6552
6835
|
const target = typeof attrs.target === "string" ? attrs.target : void 0;
|
|
@@ -6560,7 +6843,7 @@ function migrateV2ToV3(payload) {
|
|
|
6560
6843
|
return { ...payload, schemaVersion: 3 };
|
|
6561
6844
|
}
|
|
6562
6845
|
async function ensureDir(filePath) {
|
|
6563
|
-
await
|
|
6846
|
+
await fs20.mkdir(path38.dirname(filePath), { recursive: true });
|
|
6564
6847
|
}
|
|
6565
6848
|
async function saveGraphToDisk(graph, outPath) {
|
|
6566
6849
|
await ensureDir(outPath);
|
|
@@ -6570,13 +6853,13 @@ async function saveGraphToDisk(graph, outPath) {
|
|
|
6570
6853
|
graph: graph.export()
|
|
6571
6854
|
};
|
|
6572
6855
|
const tmp = `${outPath}.tmp`;
|
|
6573
|
-
await
|
|
6574
|
-
await
|
|
6856
|
+
await fs20.writeFile(tmp, JSON.stringify(payload), "utf8");
|
|
6857
|
+
await fs20.rename(tmp, outPath);
|
|
6575
6858
|
}
|
|
6576
6859
|
async function loadGraphFromDisk(graph, outPath) {
|
|
6577
6860
|
let raw;
|
|
6578
6861
|
try {
|
|
6579
|
-
raw = await
|
|
6862
|
+
raw = await fs20.readFile(outPath, "utf8");
|
|
6580
6863
|
} catch (err) {
|
|
6581
6864
|
if (err.code === "ENOENT") return;
|
|
6582
6865
|
throw err;
|
|
@@ -6640,7 +6923,7 @@ function startPersistLoop(graph, outPath, opts = {}) {
|
|
|
6640
6923
|
}
|
|
6641
6924
|
|
|
6642
6925
|
// src/diff.ts
|
|
6643
|
-
import { promises as
|
|
6926
|
+
import { promises as fs21 } from "fs";
|
|
6644
6927
|
async function loadSnapshotForDiff(target) {
|
|
6645
6928
|
if (/^https?:\/\//i.test(target)) {
|
|
6646
6929
|
const res = await fetch(target);
|
|
@@ -6649,7 +6932,7 @@ async function loadSnapshotForDiff(target) {
|
|
|
6649
6932
|
}
|
|
6650
6933
|
return await res.json();
|
|
6651
6934
|
}
|
|
6652
|
-
const raw = await
|
|
6935
|
+
const raw = await fs21.readFile(target, "utf8");
|
|
6653
6936
|
return JSON.parse(raw);
|
|
6654
6937
|
}
|
|
6655
6938
|
function indexEntries(entries) {
|
|
@@ -6716,23 +6999,23 @@ function canonicalJson(value) {
|
|
|
6716
6999
|
}
|
|
6717
7000
|
|
|
6718
7001
|
// src/projects.ts
|
|
6719
|
-
import
|
|
7002
|
+
import path39 from "path";
|
|
6720
7003
|
function pathsForProject(project, baseDir) {
|
|
6721
7004
|
if (project === DEFAULT_PROJECT) {
|
|
6722
7005
|
return {
|
|
6723
|
-
snapshotPath:
|
|
6724
|
-
errorsPath:
|
|
6725
|
-
staleEventsPath:
|
|
6726
|
-
embeddingsCachePath:
|
|
6727
|
-
policyViolationsPath:
|
|
7006
|
+
snapshotPath: path39.join(baseDir, "graph.json"),
|
|
7007
|
+
errorsPath: path39.join(baseDir, "errors.ndjson"),
|
|
7008
|
+
staleEventsPath: path39.join(baseDir, "stale-events.ndjson"),
|
|
7009
|
+
embeddingsCachePath: path39.join(baseDir, "embeddings.json"),
|
|
7010
|
+
policyViolationsPath: path39.join(baseDir, "policy-violations.ndjson")
|
|
6728
7011
|
};
|
|
6729
7012
|
}
|
|
6730
7013
|
return {
|
|
6731
|
-
snapshotPath:
|
|
6732
|
-
errorsPath:
|
|
6733
|
-
staleEventsPath:
|
|
6734
|
-
embeddingsCachePath:
|
|
6735
|
-
policyViolationsPath:
|
|
7014
|
+
snapshotPath: path39.join(baseDir, `${project}.json`),
|
|
7015
|
+
errorsPath: path39.join(baseDir, `errors.${project}.ndjson`),
|
|
7016
|
+
staleEventsPath: path39.join(baseDir, `stale-events.${project}.ndjson`),
|
|
7017
|
+
embeddingsCachePath: path39.join(baseDir, `embeddings.${project}.json`),
|
|
7018
|
+
policyViolationsPath: path39.join(baseDir, `policy-violations.${project}.ndjson`)
|
|
6736
7019
|
};
|
|
6737
7020
|
}
|
|
6738
7021
|
var Projects = class {
|
|
@@ -6771,9 +7054,9 @@ function parseExtraProjects(raw) {
|
|
|
6771
7054
|
}
|
|
6772
7055
|
|
|
6773
7056
|
// src/registry.ts
|
|
6774
|
-
import { promises as
|
|
7057
|
+
import { promises as fs22 } from "fs";
|
|
6775
7058
|
import os2 from "os";
|
|
6776
|
-
import
|
|
7059
|
+
import path40 from "path";
|
|
6777
7060
|
import {
|
|
6778
7061
|
RegistryFileSchema
|
|
6779
7062
|
} from "@neat.is/types";
|
|
@@ -6781,20 +7064,20 @@ var LOCK_TIMEOUT_MS = 5e3;
|
|
|
6781
7064
|
var LOCK_RETRY_MS = 50;
|
|
6782
7065
|
function neatHome() {
|
|
6783
7066
|
const override = process.env.NEAT_HOME;
|
|
6784
|
-
if (override && override.length > 0) return
|
|
6785
|
-
return
|
|
7067
|
+
if (override && override.length > 0) return path40.resolve(override);
|
|
7068
|
+
return path40.join(os2.homedir(), ".neat");
|
|
6786
7069
|
}
|
|
6787
7070
|
function registryPath() {
|
|
6788
|
-
return
|
|
7071
|
+
return path40.join(neatHome(), "projects.json");
|
|
6789
7072
|
}
|
|
6790
7073
|
function registryLockPath() {
|
|
6791
|
-
return
|
|
7074
|
+
return path40.join(neatHome(), "projects.json.lock");
|
|
6792
7075
|
}
|
|
6793
7076
|
function daemonPidPath() {
|
|
6794
|
-
return
|
|
7077
|
+
return path40.join(neatHome(), "neatd.pid");
|
|
6795
7078
|
}
|
|
6796
7079
|
function daemonsDir() {
|
|
6797
|
-
return
|
|
7080
|
+
return path40.join(neatHome(), "daemons");
|
|
6798
7081
|
}
|
|
6799
7082
|
function isFiniteInt(v) {
|
|
6800
7083
|
return typeof v === "number" && Number.isFinite(v);
|
|
@@ -6827,7 +7110,7 @@ async function discoverDaemons(probe = defaultDiscoveryProbe) {
|
|
|
6827
7110
|
const dir = daemonsDir();
|
|
6828
7111
|
let names;
|
|
6829
7112
|
try {
|
|
6830
|
-
names = await
|
|
7113
|
+
names = await fs22.readdir(dir);
|
|
6831
7114
|
} catch (err) {
|
|
6832
7115
|
if (err.code === "ENOENT") return [];
|
|
6833
7116
|
throw err;
|
|
@@ -6835,10 +7118,10 @@ async function discoverDaemons(probe = defaultDiscoveryProbe) {
|
|
|
6835
7118
|
const out = [];
|
|
6836
7119
|
for (const name of names) {
|
|
6837
7120
|
if (!name.endsWith(".json")) continue;
|
|
6838
|
-
const file =
|
|
7121
|
+
const file = path40.join(dir, name);
|
|
6839
7122
|
let raw;
|
|
6840
7123
|
try {
|
|
6841
|
-
raw = await
|
|
7124
|
+
raw = await fs22.readFile(file, "utf8");
|
|
6842
7125
|
} catch {
|
|
6843
7126
|
continue;
|
|
6844
7127
|
}
|
|
@@ -6851,7 +7134,7 @@ async function discoverDaemons(probe = defaultDiscoveryProbe) {
|
|
|
6851
7134
|
return out;
|
|
6852
7135
|
}
|
|
6853
7136
|
async function removeDaemonRecord(source) {
|
|
6854
|
-
await
|
|
7137
|
+
await fs22.unlink(source).catch(() => {
|
|
6855
7138
|
});
|
|
6856
7139
|
}
|
|
6857
7140
|
async function listMachineProjects(probe = defaultDiscoveryProbe) {
|
|
@@ -6908,7 +7191,7 @@ function isPidAliveDefault(pid) {
|
|
|
6908
7191
|
}
|
|
6909
7192
|
async function readPidFile(file) {
|
|
6910
7193
|
try {
|
|
6911
|
-
const raw = await
|
|
7194
|
+
const raw = await fs22.readFile(file, "utf8");
|
|
6912
7195
|
const pid = Number.parseInt(raw.trim(), 10);
|
|
6913
7196
|
return Number.isInteger(pid) && pid > 0 ? pid : void 0;
|
|
6914
7197
|
} catch {
|
|
@@ -6956,32 +7239,32 @@ function lockHolderMessage(holder, lockPath, timeoutMs) {
|
|
|
6956
7239
|
}
|
|
6957
7240
|
}
|
|
6958
7241
|
async function normalizeProjectPath(input) {
|
|
6959
|
-
const resolved =
|
|
7242
|
+
const resolved = path40.resolve(input);
|
|
6960
7243
|
try {
|
|
6961
|
-
return await
|
|
7244
|
+
return await fs22.realpath(resolved);
|
|
6962
7245
|
} catch {
|
|
6963
7246
|
return resolved;
|
|
6964
7247
|
}
|
|
6965
7248
|
}
|
|
6966
7249
|
async function writeAtomically(target, contents) {
|
|
6967
|
-
await
|
|
7250
|
+
await fs22.mkdir(path40.dirname(target), { recursive: true });
|
|
6968
7251
|
const tmp = `${target}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
|
|
6969
|
-
const fd = await
|
|
7252
|
+
const fd = await fs22.open(tmp, "w");
|
|
6970
7253
|
try {
|
|
6971
7254
|
await fd.writeFile(contents, "utf8");
|
|
6972
7255
|
await fd.sync();
|
|
6973
7256
|
} finally {
|
|
6974
7257
|
await fd.close();
|
|
6975
7258
|
}
|
|
6976
|
-
await
|
|
7259
|
+
await fs22.rename(tmp, target);
|
|
6977
7260
|
}
|
|
6978
7261
|
async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaultLockHolderProbe) {
|
|
6979
7262
|
const deadline = Date.now() + timeoutMs;
|
|
6980
|
-
await
|
|
7263
|
+
await fs22.mkdir(path40.dirname(lockPath), { recursive: true });
|
|
6981
7264
|
let probedHolder = false;
|
|
6982
7265
|
while (true) {
|
|
6983
7266
|
try {
|
|
6984
|
-
const fd = await
|
|
7267
|
+
const fd = await fs22.open(lockPath, "wx");
|
|
6985
7268
|
try {
|
|
6986
7269
|
await fd.writeFile(`${process.pid}
|
|
6987
7270
|
`, "utf8");
|
|
@@ -7006,7 +7289,7 @@ async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaul
|
|
|
7006
7289
|
}
|
|
7007
7290
|
}
|
|
7008
7291
|
async function releaseLock(lockPath) {
|
|
7009
|
-
await
|
|
7292
|
+
await fs22.unlink(lockPath).catch(() => {
|
|
7010
7293
|
});
|
|
7011
7294
|
}
|
|
7012
7295
|
async function withLock(fn) {
|
|
@@ -7022,7 +7305,7 @@ async function readRegistry() {
|
|
|
7022
7305
|
const file = registryPath();
|
|
7023
7306
|
let raw;
|
|
7024
7307
|
try {
|
|
7025
|
-
raw = await
|
|
7308
|
+
raw = await fs22.readFile(file, "utf8");
|
|
7026
7309
|
} catch (err) {
|
|
7027
7310
|
if (err.code === "ENOENT") {
|
|
7028
7311
|
return { version: 1, projects: [] };
|
|
@@ -7127,7 +7410,7 @@ function pruneTtlMs() {
|
|
|
7127
7410
|
}
|
|
7128
7411
|
async function statPathStatus(p) {
|
|
7129
7412
|
try {
|
|
7130
|
-
const stat = await
|
|
7413
|
+
const stat = await fs22.stat(p);
|
|
7131
7414
|
return stat.isDirectory() ? "present" : "unknown";
|
|
7132
7415
|
} catch (err) {
|
|
7133
7416
|
return err.code === "ENOENT" ? "gone" : "unknown";
|
|
@@ -7172,14 +7455,14 @@ import cors from "@fastify/cors";
|
|
|
7172
7455
|
import { DivergenceTypeSchema, PoliciesCheckBodySchema, PolicySeveritySchema } from "@neat.is/types";
|
|
7173
7456
|
|
|
7174
7457
|
// src/extend/index.ts
|
|
7175
|
-
import { promises as
|
|
7176
|
-
import
|
|
7458
|
+
import { promises as fs24 } from "fs";
|
|
7459
|
+
import path42 from "path";
|
|
7177
7460
|
import os3 from "os";
|
|
7178
7461
|
import { resolve as registryResolve, list as registryList } from "@neat.is/instrumentation-registry";
|
|
7179
7462
|
|
|
7180
7463
|
// src/installers/package-manager.ts
|
|
7181
|
-
import { promises as
|
|
7182
|
-
import
|
|
7464
|
+
import { promises as fs23 } from "fs";
|
|
7465
|
+
import path41 from "path";
|
|
7183
7466
|
import { spawn } from "child_process";
|
|
7184
7467
|
var LOCKFILE_PRIORITY = [
|
|
7185
7468
|
{ lockfile: "bun.lockb", pm: "bun", args: ["install", "--no-summary"] },
|
|
@@ -7194,29 +7477,29 @@ var LOCKFILE_PRIORITY = [
|
|
|
7194
7477
|
var NPM_FALLBACK_ARGS = ["install", "--no-audit", "--no-fund", "--prefer-offline"];
|
|
7195
7478
|
async function exists2(p) {
|
|
7196
7479
|
try {
|
|
7197
|
-
await
|
|
7480
|
+
await fs23.access(p);
|
|
7198
7481
|
return true;
|
|
7199
7482
|
} catch {
|
|
7200
7483
|
return false;
|
|
7201
7484
|
}
|
|
7202
7485
|
}
|
|
7203
7486
|
async function detectPackageManager(serviceDir) {
|
|
7204
|
-
let dir =
|
|
7487
|
+
let dir = path41.resolve(serviceDir);
|
|
7205
7488
|
const stops = /* @__PURE__ */ new Set();
|
|
7206
7489
|
for (let i = 0; i < 64; i++) {
|
|
7207
7490
|
if (stops.has(dir)) break;
|
|
7208
7491
|
stops.add(dir);
|
|
7209
7492
|
for (const candidate of LOCKFILE_PRIORITY) {
|
|
7210
|
-
const lockPath =
|
|
7493
|
+
const lockPath = path41.join(dir, candidate.lockfile);
|
|
7211
7494
|
if (await exists2(lockPath)) {
|
|
7212
7495
|
return { pm: candidate.pm, cwd: dir, args: [...candidate.args] };
|
|
7213
7496
|
}
|
|
7214
7497
|
}
|
|
7215
|
-
const parent =
|
|
7498
|
+
const parent = path41.dirname(dir);
|
|
7216
7499
|
if (parent === dir) break;
|
|
7217
7500
|
dir = parent;
|
|
7218
7501
|
}
|
|
7219
|
-
return { pm: "npm", cwd:
|
|
7502
|
+
return { pm: "npm", cwd: path41.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
|
|
7220
7503
|
}
|
|
7221
7504
|
async function runPackageManagerInstall(cmd) {
|
|
7222
7505
|
return new Promise((resolve) => {
|
|
@@ -7258,30 +7541,30 @@ ${err.message}`
|
|
|
7258
7541
|
// src/extend/index.ts
|
|
7259
7542
|
async function fileExists2(p) {
|
|
7260
7543
|
try {
|
|
7261
|
-
await
|
|
7544
|
+
await fs24.access(p);
|
|
7262
7545
|
return true;
|
|
7263
7546
|
} catch {
|
|
7264
7547
|
return false;
|
|
7265
7548
|
}
|
|
7266
7549
|
}
|
|
7267
7550
|
async function readPackageJson(scanPath) {
|
|
7268
|
-
const pkgPath =
|
|
7269
|
-
const raw = await
|
|
7551
|
+
const pkgPath = path42.join(scanPath, "package.json");
|
|
7552
|
+
const raw = await fs24.readFile(pkgPath, "utf8");
|
|
7270
7553
|
return JSON.parse(raw);
|
|
7271
7554
|
}
|
|
7272
7555
|
async function findHookFiles(scanPath) {
|
|
7273
|
-
const entries = await
|
|
7556
|
+
const entries = await fs24.readdir(scanPath);
|
|
7274
7557
|
return entries.filter(
|
|
7275
7558
|
(e) => (e.startsWith("instrumentation") || e.startsWith("otel-init")) && /\.(ts|js)$/.test(e)
|
|
7276
7559
|
).sort();
|
|
7277
7560
|
}
|
|
7278
7561
|
function extendLogPath() {
|
|
7279
|
-
return process.env.NEAT_EXTEND_LOG ??
|
|
7562
|
+
return process.env.NEAT_EXTEND_LOG ?? path42.join(os3.homedir(), ".neat", "extend-log.ndjson");
|
|
7280
7563
|
}
|
|
7281
7564
|
async function appendExtendLog(entry) {
|
|
7282
7565
|
const logPath = extendLogPath();
|
|
7283
|
-
await
|
|
7284
|
-
await
|
|
7566
|
+
await fs24.mkdir(path42.dirname(logPath), { recursive: true });
|
|
7567
|
+
await fs24.appendFile(logPath, JSON.stringify(entry) + "\n", "utf8");
|
|
7285
7568
|
}
|
|
7286
7569
|
function splicedContent(fileContent, snippet2) {
|
|
7287
7570
|
if (fileContent.includes("__INSTRUMENTATION_BLOCK__")) {
|
|
@@ -7339,7 +7622,7 @@ function lookupInstrumentation(library, installedVersion) {
|
|
|
7339
7622
|
}
|
|
7340
7623
|
async function describeProjectInstrumentation(ctx) {
|
|
7341
7624
|
const hookFiles = await findHookFiles(ctx.scanPath);
|
|
7342
|
-
const envNeat = await fileExists2(
|
|
7625
|
+
const envNeat = await fileExists2(path42.join(ctx.scanPath, ".env.neat"));
|
|
7343
7626
|
const registryInstrPackages = new Set(
|
|
7344
7627
|
registryList().map((e) => e.instrumentation_package).filter((p) => !!p)
|
|
7345
7628
|
);
|
|
@@ -7361,31 +7644,31 @@ async function applyExtension(ctx, args, options) {
|
|
|
7361
7644
|
);
|
|
7362
7645
|
}
|
|
7363
7646
|
for (const file of hookFiles) {
|
|
7364
|
-
const content = await
|
|
7647
|
+
const content = await fs24.readFile(path42.join(ctx.scanPath, file), "utf8");
|
|
7365
7648
|
if (content.includes(args.registration_snippet)) {
|
|
7366
7649
|
return { library: args.library, filesTouched: [], depsAdded: [], installOutput: "", alreadyApplied: true };
|
|
7367
7650
|
}
|
|
7368
7651
|
}
|
|
7369
7652
|
const primaryFile = hookFiles[0];
|
|
7370
|
-
const primaryPath =
|
|
7653
|
+
const primaryPath = path42.join(ctx.scanPath, primaryFile);
|
|
7371
7654
|
const filesTouched = [];
|
|
7372
7655
|
const depsAdded = [];
|
|
7373
|
-
const pkgPath =
|
|
7656
|
+
const pkgPath = path42.join(ctx.scanPath, "package.json");
|
|
7374
7657
|
const pkg = await readPackageJson(ctx.scanPath);
|
|
7375
7658
|
if (!(pkg.dependencies ?? {})[args.instrumentation_package]) {
|
|
7376
7659
|
pkg.dependencies = { ...pkg.dependencies ?? {}, [args.instrumentation_package]: args.version };
|
|
7377
|
-
await
|
|
7660
|
+
await fs24.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
|
|
7378
7661
|
filesTouched.push("package.json");
|
|
7379
7662
|
depsAdded.push(`${args.instrumentation_package}@${args.version}`);
|
|
7380
7663
|
}
|
|
7381
|
-
const hookContent = await
|
|
7664
|
+
const hookContent = await fs24.readFile(primaryPath, "utf8");
|
|
7382
7665
|
const patched = splicedContent(hookContent, args.registration_snippet);
|
|
7383
7666
|
if (!patched) {
|
|
7384
7667
|
throw new Error(
|
|
7385
7668
|
`Could not find instrumentation insertion point in ${primaryFile}. Expected __INSTRUMENTATION_BLOCK__, instrumentations.push(, or new NodeSDK(.`
|
|
7386
7669
|
);
|
|
7387
7670
|
}
|
|
7388
|
-
await
|
|
7671
|
+
await fs24.writeFile(primaryPath, patched, "utf8");
|
|
7389
7672
|
filesTouched.push(primaryFile);
|
|
7390
7673
|
const cmd = await detectPackageManager(ctx.scanPath);
|
|
7391
7674
|
const installer = options?.runInstall ?? runPackageManagerInstall;
|
|
@@ -7416,7 +7699,7 @@ async function dryRunExtension(ctx, args) {
|
|
|
7416
7699
|
};
|
|
7417
7700
|
}
|
|
7418
7701
|
for (const file of hookFiles) {
|
|
7419
|
-
const content = await
|
|
7702
|
+
const content = await fs24.readFile(path42.join(ctx.scanPath, file), "utf8");
|
|
7420
7703
|
if (content.includes(args.registration_snippet)) {
|
|
7421
7704
|
return {
|
|
7422
7705
|
library: args.library,
|
|
@@ -7438,7 +7721,7 @@ async function dryRunExtension(ctx, args) {
|
|
|
7438
7721
|
depsToAdd.push(`${args.instrumentation_package}@${args.version}`);
|
|
7439
7722
|
filesTouched.push("package.json");
|
|
7440
7723
|
}
|
|
7441
|
-
const hookContent = await
|
|
7724
|
+
const hookContent = await fs24.readFile(path42.join(ctx.scanPath, primaryFile), "utf8");
|
|
7442
7725
|
const patched = splicedContent(hookContent, args.registration_snippet);
|
|
7443
7726
|
if (patched) {
|
|
7444
7727
|
filesTouched.push(primaryFile);
|
|
@@ -7453,28 +7736,28 @@ async function rollbackExtension(ctx, args) {
|
|
|
7453
7736
|
if (!await fileExists2(logPath)) {
|
|
7454
7737
|
return { undone: false, message: "no apply found for library" };
|
|
7455
7738
|
}
|
|
7456
|
-
const raw = await
|
|
7739
|
+
const raw = await fs24.readFile(logPath, "utf8");
|
|
7457
7740
|
const entries = raw.trim().split("\n").filter(Boolean).map((line) => JSON.parse(line));
|
|
7458
7741
|
const match = [...entries].reverse().find((e) => e.project === ctx.project && e.library === args.library);
|
|
7459
7742
|
if (!match) {
|
|
7460
7743
|
return { undone: false, message: "no apply found for library" };
|
|
7461
7744
|
}
|
|
7462
|
-
const pkgPath =
|
|
7745
|
+
const pkgPath = path42.join(ctx.scanPath, "package.json");
|
|
7463
7746
|
if (await fileExists2(pkgPath)) {
|
|
7464
7747
|
const pkg = await readPackageJson(ctx.scanPath);
|
|
7465
7748
|
if (pkg.dependencies?.[match.instrumentation_package]) {
|
|
7466
7749
|
const { [match.instrumentation_package]: _removed, ...rest } = pkg.dependencies;
|
|
7467
7750
|
pkg.dependencies = rest;
|
|
7468
|
-
await
|
|
7751
|
+
await fs24.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
|
|
7469
7752
|
}
|
|
7470
7753
|
}
|
|
7471
7754
|
const hookFiles = await findHookFiles(ctx.scanPath);
|
|
7472
7755
|
for (const file of hookFiles) {
|
|
7473
|
-
const filePath =
|
|
7474
|
-
const content = await
|
|
7756
|
+
const filePath = path42.join(ctx.scanPath, file);
|
|
7757
|
+
const content = await fs24.readFile(filePath, "utf8");
|
|
7475
7758
|
if (content.includes(match.registration_snippet)) {
|
|
7476
7759
|
const filtered = content.split("\n").filter((line) => !line.includes(match.registration_snippet)).join("\n");
|
|
7477
|
-
await
|
|
7760
|
+
await fs24.writeFile(filePath, filtered, "utf8");
|
|
7478
7761
|
break;
|
|
7479
7762
|
}
|
|
7480
7763
|
}
|
|
@@ -7484,6 +7767,50 @@ async function rollbackExtension(ctx, args) {
|
|
|
7484
7767
|
};
|
|
7485
7768
|
}
|
|
7486
7769
|
|
|
7770
|
+
// src/logs-store.ts
|
|
7771
|
+
var LOGS_STORE_MAX_AGE_MS = 24 * 60 * 60 * 1e3;
|
|
7772
|
+
var LOGS_QUERY_DEFAULT_LIMIT = 100;
|
|
7773
|
+
var LOGS_QUERY_MAX_LIMIT = 1e3;
|
|
7774
|
+
var buffers = /* @__PURE__ */ new Map();
|
|
7775
|
+
var KEY_SEP = "::";
|
|
7776
|
+
function bufferKey(projectName, source) {
|
|
7777
|
+
return `${projectName}${KEY_SEP}${source}`;
|
|
7778
|
+
}
|
|
7779
|
+
function sourcesForProject(projectName) {
|
|
7780
|
+
const prefix = `${projectName}${KEY_SEP}`;
|
|
7781
|
+
const sources = [];
|
|
7782
|
+
for (const key of buffers.keys()) {
|
|
7783
|
+
if (key.startsWith(prefix)) sources.push(key.slice(prefix.length));
|
|
7784
|
+
}
|
|
7785
|
+
return sources;
|
|
7786
|
+
}
|
|
7787
|
+
function queryLogEntries(opts) {
|
|
7788
|
+
const { projectName, source, service, since, limit } = opts;
|
|
7789
|
+
const sources = source && source.length > 0 ? source : sourcesForProject(projectName);
|
|
7790
|
+
let merged = [];
|
|
7791
|
+
for (const src of sources) {
|
|
7792
|
+
const buf = buffers.get(bufferKey(projectName, src));
|
|
7793
|
+
if (buf) merged = merged.concat(buf);
|
|
7794
|
+
}
|
|
7795
|
+
if (service) {
|
|
7796
|
+
merged = merged.filter((e) => e.serviceName === service);
|
|
7797
|
+
}
|
|
7798
|
+
if (since) {
|
|
7799
|
+
const sinceMs = Date.parse(since);
|
|
7800
|
+
if (!Number.isNaN(sinceMs)) {
|
|
7801
|
+
merged = merged.filter((e) => {
|
|
7802
|
+
const t = Date.parse(e.timestamp);
|
|
7803
|
+
return Number.isNaN(t) || t >= sinceMs;
|
|
7804
|
+
});
|
|
7805
|
+
}
|
|
7806
|
+
}
|
|
7807
|
+
merged.sort((a, b) => Date.parse(b.timestamp) - Date.parse(a.timestamp));
|
|
7808
|
+
if (typeof limit === "number" && Number.isFinite(limit) && limit >= 0) {
|
|
7809
|
+
return merged.slice(0, limit);
|
|
7810
|
+
}
|
|
7811
|
+
return merged;
|
|
7812
|
+
}
|
|
7813
|
+
|
|
7487
7814
|
// src/streaming.ts
|
|
7488
7815
|
var SSE_HEARTBEAT_MS = 3e4;
|
|
7489
7816
|
var SSE_BACKPRESSURE_CAP = 1e3;
|
|
@@ -7742,6 +8069,23 @@ function registerRoutes(scope, ctx) {
|
|
|
7742
8069
|
const sliced = ordered.slice(0, Number.isFinite(limit) && limit > 0 ? limit : 50);
|
|
7743
8070
|
return { count: sliced.length, total, events: sliced };
|
|
7744
8071
|
});
|
|
8072
|
+
scope.get("/logs", async (req, reply) => {
|
|
8073
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
|
|
8074
|
+
if (!proj) return;
|
|
8075
|
+
const rawSource = req.query.source;
|
|
8076
|
+
const sources = rawSource === void 0 ? void 0 : Array.isArray(rawSource) ? rawSource : [rawSource];
|
|
8077
|
+
const filtered = queryLogEntries({
|
|
8078
|
+
projectName: proj.name,
|
|
8079
|
+
...sources && sources.length > 0 ? { source: sources } : {},
|
|
8080
|
+
...req.query.service ? { service: req.query.service } : {},
|
|
8081
|
+
...req.query.since ? { since: req.query.since } : {}
|
|
8082
|
+
});
|
|
8083
|
+
const total = filtered.length;
|
|
8084
|
+
const limit = req.query.limit ? Number(req.query.limit) : LOGS_QUERY_DEFAULT_LIMIT;
|
|
8085
|
+
const safeLimit = Number.isFinite(limit) && limit > 0 ? Math.min(limit, LOGS_QUERY_MAX_LIMIT) : LOGS_QUERY_DEFAULT_LIMIT;
|
|
8086
|
+
const sliced = filtered.slice(0, safeLimit);
|
|
8087
|
+
return { count: sliced.length, total, logs: sliced };
|
|
8088
|
+
});
|
|
7745
8089
|
const incidentHistoryHandler = async (req, reply) => {
|
|
7746
8090
|
const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
|
|
7747
8091
|
if (!proj) return;
|
|
@@ -7837,8 +8181,16 @@ function registerRoutes(scope, ctx) {
|
|
|
7837
8181
|
if (!against) {
|
|
7838
8182
|
return reply.code(400).send({ error: "query parameter `against` is required" });
|
|
7839
8183
|
}
|
|
8184
|
+
const targetProjectName = against === "self" ? proj.name : against;
|
|
8185
|
+
const targetProject = registry.get(targetProjectName);
|
|
8186
|
+
if (!targetProject) {
|
|
8187
|
+
return reply.code(400).send({
|
|
8188
|
+
error: "unknown snapshot id \u2014 `against` must be `self` or the name of a project this server has a snapshot for",
|
|
8189
|
+
against
|
|
8190
|
+
});
|
|
8191
|
+
}
|
|
7840
8192
|
try {
|
|
7841
|
-
const snapshot = await loadSnapshotForDiff(
|
|
8193
|
+
const snapshot = await loadSnapshotForDiff(targetProject.paths.snapshotPath);
|
|
7842
8194
|
return computeGraphDiff(proj.graph, snapshot);
|
|
7843
8195
|
} catch (err) {
|
|
7844
8196
|
return reply.code(400).send({ error: "failed to load snapshot", against, detail: err.message });
|
|
@@ -7868,6 +8220,13 @@ function registerRoutes(scope, ctx) {
|
|
|
7868
8220
|
edgeCount: proj.graph.size
|
|
7869
8221
|
};
|
|
7870
8222
|
} catch (err) {
|
|
8223
|
+
if (err instanceof SnapshotValidationError) {
|
|
8224
|
+
return reply.code(400).send({
|
|
8225
|
+
error: "snapshot merge failed",
|
|
8226
|
+
details: err.message,
|
|
8227
|
+
issues: err.issues
|
|
8228
|
+
});
|
|
8229
|
+
}
|
|
7871
8230
|
return reply.code(400).send({
|
|
7872
8231
|
error: "snapshot merge failed",
|
|
7873
8232
|
details: err.message
|
|
@@ -8227,6 +8586,7 @@ export {
|
|
|
8227
8586
|
reconcileObservedRelPath,
|
|
8228
8587
|
ensureObservedFileNode,
|
|
8229
8588
|
ensureServiceNode,
|
|
8589
|
+
ensureInfraNode,
|
|
8230
8590
|
upsertObservedEdge,
|
|
8231
8591
|
stitchTrace,
|
|
8232
8592
|
makeErrorSpanWriter,
|
|
@@ -8247,6 +8607,7 @@ export {
|
|
|
8247
8607
|
addImports,
|
|
8248
8608
|
addDatabasesAndCompat,
|
|
8249
8609
|
addConfigNodes,
|
|
8610
|
+
normalizePathTemplate,
|
|
8250
8611
|
addCallEdges,
|
|
8251
8612
|
addInfra,
|
|
8252
8613
|
retireEdgesByFile,
|
|
@@ -8282,4 +8643,4 @@ export {
|
|
|
8282
8643
|
pruneRegistry,
|
|
8283
8644
|
buildApi
|
|
8284
8645
|
};
|
|
8285
|
-
//# sourceMappingURL=chunk-
|
|
8646
|
+
//# sourceMappingURL=chunk-5T6J3WKC.js.map
|