@neat.is/core 0.4.28-dev.20260708 → 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-O4MRDWD7.js → chunk-5T6J3WKC.js} +369 -121
- package/dist/chunk-5T6J3WKC.js.map +1 -0
- package/dist/{chunk-NY5Q6NE2.js → chunk-UJ6WBLIE.js} +88 -15
- package/dist/chunk-UJ6WBLIE.js.map +1 -0
- package/dist/cli.cjs +712 -409
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +2 -2
- package/dist/index.cjs +536 -216
- 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 +2 -2
- package/dist/neatd.cjs +549 -229
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +2 -2
- package/dist/server.cjs +323 -88
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +1 -1
- package/package.json +2 -2
- package/dist/chunk-NY5Q6NE2.js.map +0 -1
- package/dist/chunk-O4MRDWD7.js.map +0 -1
|
@@ -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`
|
|
@@ -1879,6 +1879,19 @@ function ensureServiceNode(graph, serviceName, env) {
|
|
|
1879
1879
|
graph.addNode(id, node);
|
|
1880
1880
|
return id;
|
|
1881
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
|
+
}
|
|
1882
1895
|
function ensureDatabaseNode(graph, host, engine) {
|
|
1883
1896
|
const id = databaseId(host);
|
|
1884
1897
|
if (graph.hasNode(id)) return id;
|
|
@@ -4658,10 +4671,10 @@ function fastifyRouteMethods(objNode) {
|
|
|
4658
4671
|
}
|
|
4659
4672
|
return [];
|
|
4660
4673
|
}
|
|
4661
|
-
function serverRoutesFromSource(source, parser, hasExpress, hasFastify) {
|
|
4674
|
+
function serverRoutesFromSource(source, parser, hasExpress, hasFastify, hasHono = false) {
|
|
4662
4675
|
const tree = parseSource2(parser, source);
|
|
4663
4676
|
const out = [];
|
|
4664
|
-
const framework = hasExpress ? "express" : "fastify";
|
|
4677
|
+
const framework = hasExpress ? "express" : hasFastify ? "fastify" : hasHono ? "hono" : "unknown";
|
|
4665
4678
|
walk(tree.rootNode, (node) => {
|
|
4666
4679
|
if (node.type !== "call_expression") return;
|
|
4667
4680
|
const fn = node.childForFieldName("function");
|
|
@@ -4811,8 +4824,9 @@ async function addRoutes(graph, services) {
|
|
|
4811
4824
|
};
|
|
4812
4825
|
const hasExpress = deps["express"] !== void 0;
|
|
4813
4826
|
const hasFastify = deps["fastify"] !== void 0;
|
|
4827
|
+
const hasHono = deps["hono"] !== void 0;
|
|
4814
4828
|
const hasNext = deps["next"] !== void 0;
|
|
4815
|
-
if (!hasExpress && !hasFastify && !hasNext) continue;
|
|
4829
|
+
if (!hasExpress && !hasFastify && !hasHono && !hasNext) continue;
|
|
4816
4830
|
const files = await loadSourceFiles(service.dir);
|
|
4817
4831
|
for (const file of files) {
|
|
4818
4832
|
if (isTestPath(file.path)) continue;
|
|
@@ -4822,8 +4836,8 @@ async function addRoutes(graph, services) {
|
|
|
4822
4836
|
try {
|
|
4823
4837
|
if (hasNext && (isNextAppRouteFile(relFile) || isNextPagesApiFile(relFile))) {
|
|
4824
4838
|
routes = nextRoutesFromFile(file.content, relFile, jsParser);
|
|
4825
|
-
} else if (hasExpress || hasFastify) {
|
|
4826
|
-
routes = serverRoutesFromSource(file.content, jsParser, hasExpress, hasFastify);
|
|
4839
|
+
} else if (hasExpress || hasFastify || hasHono) {
|
|
4840
|
+
routes = serverRoutesFromSource(file.content, jsParser, hasExpress, hasFastify, hasHono);
|
|
4827
4841
|
} else {
|
|
4828
4842
|
routes = [];
|
|
4829
4843
|
}
|
|
@@ -6155,25 +6169,258 @@ async function addK8sResources(graph, scanPath) {
|
|
|
6155
6169
|
return { nodesAdded, edgesAdded: 0 };
|
|
6156
6170
|
}
|
|
6157
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
|
+
|
|
6158
6404
|
// src/extract/infra/index.ts
|
|
6159
6405
|
async function addInfra(graph, scanPath, services) {
|
|
6160
6406
|
const compose = await addComposeInfra(graph, scanPath, services);
|
|
6161
6407
|
const dockerfile = await addDockerfileRuntimes(graph, services, scanPath);
|
|
6162
6408
|
const terraform = await addTerraformResources(graph, scanPath);
|
|
6163
6409
|
const k8s = await addK8sResources(graph, scanPath);
|
|
6410
|
+
const cloudflare = await addCloudflareWorkers(graph, services, scanPath);
|
|
6164
6411
|
return {
|
|
6165
|
-
nodesAdded: compose.nodesAdded + dockerfile.nodesAdded + terraform.nodesAdded + k8s.nodesAdded,
|
|
6166
|
-
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
|
|
6167
6414
|
};
|
|
6168
6415
|
}
|
|
6169
6416
|
|
|
6170
6417
|
// src/extract/index.ts
|
|
6171
|
-
import
|
|
6418
|
+
import path37 from "path";
|
|
6172
6419
|
|
|
6173
6420
|
// src/extract/retire.ts
|
|
6174
6421
|
import { existsSync as existsSync2 } from "fs";
|
|
6175
|
-
import
|
|
6176
|
-
import { NodeType as NodeType14, Provenance as
|
|
6422
|
+
import path36 from "path";
|
|
6423
|
+
import { NodeType as NodeType14, Provenance as Provenance16 } from "@neat.is/types";
|
|
6177
6424
|
function dropOrphanedFileNodes(graph) {
|
|
6178
6425
|
const orphans = [];
|
|
6179
6426
|
graph.forEachNode((id, attrs) => {
|
|
@@ -6190,7 +6437,7 @@ function retireEdgesByFile(graph, file) {
|
|
|
6190
6437
|
const toDrop = [];
|
|
6191
6438
|
graph.forEachEdge((id, attrs) => {
|
|
6192
6439
|
const edge = attrs;
|
|
6193
|
-
if (edge.provenance !==
|
|
6440
|
+
if (edge.provenance !== Provenance16.EXTRACTED) return;
|
|
6194
6441
|
if (!edge.evidence?.file) return;
|
|
6195
6442
|
if (edge.evidence.file === normalized) toDrop.push(id);
|
|
6196
6443
|
});
|
|
@@ -6203,14 +6450,14 @@ function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = []) {
|
|
|
6203
6450
|
const bases = [scanPath, ...serviceDirs];
|
|
6204
6451
|
graph.forEachEdge((id, attrs) => {
|
|
6205
6452
|
const edge = attrs;
|
|
6206
|
-
if (edge.provenance !==
|
|
6453
|
+
if (edge.provenance !== Provenance16.EXTRACTED) return;
|
|
6207
6454
|
const evidenceFile = edge.evidence?.file;
|
|
6208
6455
|
if (!evidenceFile) return;
|
|
6209
|
-
if (
|
|
6456
|
+
if (path36.isAbsolute(evidenceFile)) {
|
|
6210
6457
|
if (!existsSync2(evidenceFile)) toDrop.push(id);
|
|
6211
6458
|
return;
|
|
6212
6459
|
}
|
|
6213
|
-
const found = bases.some((base) => existsSync2(
|
|
6460
|
+
const found = bases.some((base) => existsSync2(path36.join(base, evidenceFile)));
|
|
6214
6461
|
if (!found) toDrop.push(id);
|
|
6215
6462
|
});
|
|
6216
6463
|
for (const id of toDrop) graph.dropEdge(id);
|
|
@@ -6252,7 +6499,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
6252
6499
|
}
|
|
6253
6500
|
const droppedEntries = drainDroppedExtracted();
|
|
6254
6501
|
if (isRejectedLogEnabled() && opts.errorsPath && droppedEntries.length > 0) {
|
|
6255
|
-
const rejectedPath =
|
|
6502
|
+
const rejectedPath = path37.join(path37.dirname(opts.errorsPath), "rejected.ndjson");
|
|
6256
6503
|
try {
|
|
6257
6504
|
await writeRejectedExtracted(droppedEntries, rejectedPath);
|
|
6258
6505
|
} catch (err) {
|
|
@@ -6288,10 +6535,10 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
6288
6535
|
import {
|
|
6289
6536
|
databaseId as databaseId3,
|
|
6290
6537
|
DivergenceResultSchema,
|
|
6291
|
-
EdgeType as
|
|
6538
|
+
EdgeType as EdgeType17,
|
|
6292
6539
|
NodeType as NodeType15,
|
|
6293
6540
|
parseEdgeId,
|
|
6294
|
-
Provenance as
|
|
6541
|
+
Provenance as Provenance17
|
|
6295
6542
|
} from "@neat.is/types";
|
|
6296
6543
|
function bucketKey(source, target, type) {
|
|
6297
6544
|
return `${type}|${source}|${target}`;
|
|
@@ -6305,17 +6552,17 @@ function bucketEdges(graph) {
|
|
|
6305
6552
|
const key = bucketKey(e.source, e.target, e.type);
|
|
6306
6553
|
const cur = buckets.get(key) ?? { source: e.source, target: e.target, type: e.type };
|
|
6307
6554
|
switch (provenance) {
|
|
6308
|
-
case
|
|
6555
|
+
case Provenance17.EXTRACTED:
|
|
6309
6556
|
cur.extracted = e;
|
|
6310
6557
|
break;
|
|
6311
|
-
case
|
|
6558
|
+
case Provenance17.OBSERVED:
|
|
6312
6559
|
cur.observed = e;
|
|
6313
6560
|
break;
|
|
6314
|
-
case
|
|
6561
|
+
case Provenance17.INFERRED:
|
|
6315
6562
|
cur.inferred = e;
|
|
6316
6563
|
break;
|
|
6317
6564
|
default:
|
|
6318
|
-
if (e.provenance ===
|
|
6565
|
+
if (e.provenance === Provenance17.STALE) cur.stale = e;
|
|
6319
6566
|
}
|
|
6320
6567
|
buckets.set(key, cur);
|
|
6321
6568
|
});
|
|
@@ -6349,14 +6596,14 @@ function gradedConfidence(edge) {
|
|
|
6349
6596
|
return clampConfidence(confidenceForEdge(edge));
|
|
6350
6597
|
}
|
|
6351
6598
|
var OBSERVABLE_EDGE_TYPES = /* @__PURE__ */ new Set([
|
|
6352
|
-
|
|
6353
|
-
|
|
6354
|
-
|
|
6355
|
-
|
|
6599
|
+
EdgeType17.CALLS,
|
|
6600
|
+
EdgeType17.CONNECTS_TO,
|
|
6601
|
+
EdgeType17.PUBLISHES_TO,
|
|
6602
|
+
EdgeType17.CONSUMES_FROM
|
|
6356
6603
|
]);
|
|
6357
6604
|
function detectMissingDivergences(graph, bucket) {
|
|
6358
6605
|
const out = [];
|
|
6359
|
-
if (bucket.type ===
|
|
6606
|
+
if (bucket.type === EdgeType17.CONTAINS) return out;
|
|
6360
6607
|
if (bucket.extracted && !bucket.observed && OBSERVABLE_EDGE_TYPES.has(bucket.type)) {
|
|
6361
6608
|
if (!nodeIsFrontier(graph, bucket.target)) {
|
|
6362
6609
|
out.push({
|
|
@@ -6397,7 +6644,7 @@ function declaredHostFor(svc) {
|
|
|
6397
6644
|
function hasExtractedConfiguredBy(graph, svcId) {
|
|
6398
6645
|
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
6399
6646
|
const e = graph.getEdgeAttributes(edgeId);
|
|
6400
|
-
if (e.type ===
|
|
6647
|
+
if (e.type === EdgeType17.CONFIGURED_BY && e.provenance === Provenance17.EXTRACTED) {
|
|
6401
6648
|
return true;
|
|
6402
6649
|
}
|
|
6403
6650
|
}
|
|
@@ -6410,8 +6657,8 @@ function detectHostMismatch(graph, svcId, svc) {
|
|
|
6410
6657
|
const out = [];
|
|
6411
6658
|
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
6412
6659
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
6413
|
-
if (edge.type !==
|
|
6414
|
-
if (edge.provenance !==
|
|
6660
|
+
if (edge.type !== EdgeType17.CONNECTS_TO) continue;
|
|
6661
|
+
if (edge.provenance !== Provenance17.OBSERVED) continue;
|
|
6415
6662
|
const target = graph.getNodeAttributes(edge.target);
|
|
6416
6663
|
if (target.type !== NodeType15.DatabaseNode) continue;
|
|
6417
6664
|
const observedHost = target.host?.trim();
|
|
@@ -6435,8 +6682,8 @@ function detectCompatDivergences(graph, svcId, svc) {
|
|
|
6435
6682
|
const deps = svc.dependencies ?? {};
|
|
6436
6683
|
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
6437
6684
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
6438
|
-
if (edge.type !==
|
|
6439
|
-
if (edge.provenance !==
|
|
6685
|
+
if (edge.type !== EdgeType17.CONNECTS_TO) continue;
|
|
6686
|
+
if (edge.provenance !== Provenance17.OBSERVED) continue;
|
|
6440
6687
|
const target = graph.getNodeAttributes(edge.target);
|
|
6441
6688
|
if (target.type !== NodeType15.DatabaseNode) continue;
|
|
6442
6689
|
for (const pair of compatPairs()) {
|
|
@@ -6558,9 +6805,9 @@ function computeDivergences(graph, opts = {}) {
|
|
|
6558
6805
|
}
|
|
6559
6806
|
|
|
6560
6807
|
// src/persist.ts
|
|
6561
|
-
import { promises as
|
|
6562
|
-
import
|
|
6563
|
-
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";
|
|
6564
6811
|
var SCHEMA_VERSION = 4;
|
|
6565
6812
|
function migrateV1ToV2(payload) {
|
|
6566
6813
|
const nodes = payload.graph.nodes;
|
|
@@ -6582,7 +6829,7 @@ function migrateV2ToV3(payload) {
|
|
|
6582
6829
|
for (const edge of edges) {
|
|
6583
6830
|
const attrs = edge.attributes;
|
|
6584
6831
|
if (!attrs || attrs.provenance !== "FRONTIER") continue;
|
|
6585
|
-
attrs.provenance =
|
|
6832
|
+
attrs.provenance = Provenance18.OBSERVED;
|
|
6586
6833
|
const type = typeof attrs.type === "string" ? attrs.type : void 0;
|
|
6587
6834
|
const source = typeof attrs.source === "string" ? attrs.source : void 0;
|
|
6588
6835
|
const target = typeof attrs.target === "string" ? attrs.target : void 0;
|
|
@@ -6596,7 +6843,7 @@ function migrateV2ToV3(payload) {
|
|
|
6596
6843
|
return { ...payload, schemaVersion: 3 };
|
|
6597
6844
|
}
|
|
6598
6845
|
async function ensureDir(filePath) {
|
|
6599
|
-
await
|
|
6846
|
+
await fs20.mkdir(path38.dirname(filePath), { recursive: true });
|
|
6600
6847
|
}
|
|
6601
6848
|
async function saveGraphToDisk(graph, outPath) {
|
|
6602
6849
|
await ensureDir(outPath);
|
|
@@ -6606,13 +6853,13 @@ async function saveGraphToDisk(graph, outPath) {
|
|
|
6606
6853
|
graph: graph.export()
|
|
6607
6854
|
};
|
|
6608
6855
|
const tmp = `${outPath}.tmp`;
|
|
6609
|
-
await
|
|
6610
|
-
await
|
|
6856
|
+
await fs20.writeFile(tmp, JSON.stringify(payload), "utf8");
|
|
6857
|
+
await fs20.rename(tmp, outPath);
|
|
6611
6858
|
}
|
|
6612
6859
|
async function loadGraphFromDisk(graph, outPath) {
|
|
6613
6860
|
let raw;
|
|
6614
6861
|
try {
|
|
6615
|
-
raw = await
|
|
6862
|
+
raw = await fs20.readFile(outPath, "utf8");
|
|
6616
6863
|
} catch (err) {
|
|
6617
6864
|
if (err.code === "ENOENT") return;
|
|
6618
6865
|
throw err;
|
|
@@ -6676,7 +6923,7 @@ function startPersistLoop(graph, outPath, opts = {}) {
|
|
|
6676
6923
|
}
|
|
6677
6924
|
|
|
6678
6925
|
// src/diff.ts
|
|
6679
|
-
import { promises as
|
|
6926
|
+
import { promises as fs21 } from "fs";
|
|
6680
6927
|
async function loadSnapshotForDiff(target) {
|
|
6681
6928
|
if (/^https?:\/\//i.test(target)) {
|
|
6682
6929
|
const res = await fetch(target);
|
|
@@ -6685,7 +6932,7 @@ async function loadSnapshotForDiff(target) {
|
|
|
6685
6932
|
}
|
|
6686
6933
|
return await res.json();
|
|
6687
6934
|
}
|
|
6688
|
-
const raw = await
|
|
6935
|
+
const raw = await fs21.readFile(target, "utf8");
|
|
6689
6936
|
return JSON.parse(raw);
|
|
6690
6937
|
}
|
|
6691
6938
|
function indexEntries(entries) {
|
|
@@ -6752,23 +6999,23 @@ function canonicalJson(value) {
|
|
|
6752
6999
|
}
|
|
6753
7000
|
|
|
6754
7001
|
// src/projects.ts
|
|
6755
|
-
import
|
|
7002
|
+
import path39 from "path";
|
|
6756
7003
|
function pathsForProject(project, baseDir) {
|
|
6757
7004
|
if (project === DEFAULT_PROJECT) {
|
|
6758
7005
|
return {
|
|
6759
|
-
snapshotPath:
|
|
6760
|
-
errorsPath:
|
|
6761
|
-
staleEventsPath:
|
|
6762
|
-
embeddingsCachePath:
|
|
6763
|
-
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")
|
|
6764
7011
|
};
|
|
6765
7012
|
}
|
|
6766
7013
|
return {
|
|
6767
|
-
snapshotPath:
|
|
6768
|
-
errorsPath:
|
|
6769
|
-
staleEventsPath:
|
|
6770
|
-
embeddingsCachePath:
|
|
6771
|
-
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`)
|
|
6772
7019
|
};
|
|
6773
7020
|
}
|
|
6774
7021
|
var Projects = class {
|
|
@@ -6807,9 +7054,9 @@ function parseExtraProjects(raw) {
|
|
|
6807
7054
|
}
|
|
6808
7055
|
|
|
6809
7056
|
// src/registry.ts
|
|
6810
|
-
import { promises as
|
|
7057
|
+
import { promises as fs22 } from "fs";
|
|
6811
7058
|
import os2 from "os";
|
|
6812
|
-
import
|
|
7059
|
+
import path40 from "path";
|
|
6813
7060
|
import {
|
|
6814
7061
|
RegistryFileSchema
|
|
6815
7062
|
} from "@neat.is/types";
|
|
@@ -6817,20 +7064,20 @@ var LOCK_TIMEOUT_MS = 5e3;
|
|
|
6817
7064
|
var LOCK_RETRY_MS = 50;
|
|
6818
7065
|
function neatHome() {
|
|
6819
7066
|
const override = process.env.NEAT_HOME;
|
|
6820
|
-
if (override && override.length > 0) return
|
|
6821
|
-
return
|
|
7067
|
+
if (override && override.length > 0) return path40.resolve(override);
|
|
7068
|
+
return path40.join(os2.homedir(), ".neat");
|
|
6822
7069
|
}
|
|
6823
7070
|
function registryPath() {
|
|
6824
|
-
return
|
|
7071
|
+
return path40.join(neatHome(), "projects.json");
|
|
6825
7072
|
}
|
|
6826
7073
|
function registryLockPath() {
|
|
6827
|
-
return
|
|
7074
|
+
return path40.join(neatHome(), "projects.json.lock");
|
|
6828
7075
|
}
|
|
6829
7076
|
function daemonPidPath() {
|
|
6830
|
-
return
|
|
7077
|
+
return path40.join(neatHome(), "neatd.pid");
|
|
6831
7078
|
}
|
|
6832
7079
|
function daemonsDir() {
|
|
6833
|
-
return
|
|
7080
|
+
return path40.join(neatHome(), "daemons");
|
|
6834
7081
|
}
|
|
6835
7082
|
function isFiniteInt(v) {
|
|
6836
7083
|
return typeof v === "number" && Number.isFinite(v);
|
|
@@ -6863,7 +7110,7 @@ async function discoverDaemons(probe = defaultDiscoveryProbe) {
|
|
|
6863
7110
|
const dir = daemonsDir();
|
|
6864
7111
|
let names;
|
|
6865
7112
|
try {
|
|
6866
|
-
names = await
|
|
7113
|
+
names = await fs22.readdir(dir);
|
|
6867
7114
|
} catch (err) {
|
|
6868
7115
|
if (err.code === "ENOENT") return [];
|
|
6869
7116
|
throw err;
|
|
@@ -6871,10 +7118,10 @@ async function discoverDaemons(probe = defaultDiscoveryProbe) {
|
|
|
6871
7118
|
const out = [];
|
|
6872
7119
|
for (const name of names) {
|
|
6873
7120
|
if (!name.endsWith(".json")) continue;
|
|
6874
|
-
const file =
|
|
7121
|
+
const file = path40.join(dir, name);
|
|
6875
7122
|
let raw;
|
|
6876
7123
|
try {
|
|
6877
|
-
raw = await
|
|
7124
|
+
raw = await fs22.readFile(file, "utf8");
|
|
6878
7125
|
} catch {
|
|
6879
7126
|
continue;
|
|
6880
7127
|
}
|
|
@@ -6887,7 +7134,7 @@ async function discoverDaemons(probe = defaultDiscoveryProbe) {
|
|
|
6887
7134
|
return out;
|
|
6888
7135
|
}
|
|
6889
7136
|
async function removeDaemonRecord(source) {
|
|
6890
|
-
await
|
|
7137
|
+
await fs22.unlink(source).catch(() => {
|
|
6891
7138
|
});
|
|
6892
7139
|
}
|
|
6893
7140
|
async function listMachineProjects(probe = defaultDiscoveryProbe) {
|
|
@@ -6944,7 +7191,7 @@ function isPidAliveDefault(pid) {
|
|
|
6944
7191
|
}
|
|
6945
7192
|
async function readPidFile(file) {
|
|
6946
7193
|
try {
|
|
6947
|
-
const raw = await
|
|
7194
|
+
const raw = await fs22.readFile(file, "utf8");
|
|
6948
7195
|
const pid = Number.parseInt(raw.trim(), 10);
|
|
6949
7196
|
return Number.isInteger(pid) && pid > 0 ? pid : void 0;
|
|
6950
7197
|
} catch {
|
|
@@ -6992,32 +7239,32 @@ function lockHolderMessage(holder, lockPath, timeoutMs) {
|
|
|
6992
7239
|
}
|
|
6993
7240
|
}
|
|
6994
7241
|
async function normalizeProjectPath(input) {
|
|
6995
|
-
const resolved =
|
|
7242
|
+
const resolved = path40.resolve(input);
|
|
6996
7243
|
try {
|
|
6997
|
-
return await
|
|
7244
|
+
return await fs22.realpath(resolved);
|
|
6998
7245
|
} catch {
|
|
6999
7246
|
return resolved;
|
|
7000
7247
|
}
|
|
7001
7248
|
}
|
|
7002
7249
|
async function writeAtomically(target, contents) {
|
|
7003
|
-
await
|
|
7250
|
+
await fs22.mkdir(path40.dirname(target), { recursive: true });
|
|
7004
7251
|
const tmp = `${target}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
|
|
7005
|
-
const fd = await
|
|
7252
|
+
const fd = await fs22.open(tmp, "w");
|
|
7006
7253
|
try {
|
|
7007
7254
|
await fd.writeFile(contents, "utf8");
|
|
7008
7255
|
await fd.sync();
|
|
7009
7256
|
} finally {
|
|
7010
7257
|
await fd.close();
|
|
7011
7258
|
}
|
|
7012
|
-
await
|
|
7259
|
+
await fs22.rename(tmp, target);
|
|
7013
7260
|
}
|
|
7014
7261
|
async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaultLockHolderProbe) {
|
|
7015
7262
|
const deadline = Date.now() + timeoutMs;
|
|
7016
|
-
await
|
|
7263
|
+
await fs22.mkdir(path40.dirname(lockPath), { recursive: true });
|
|
7017
7264
|
let probedHolder = false;
|
|
7018
7265
|
while (true) {
|
|
7019
7266
|
try {
|
|
7020
|
-
const fd = await
|
|
7267
|
+
const fd = await fs22.open(lockPath, "wx");
|
|
7021
7268
|
try {
|
|
7022
7269
|
await fd.writeFile(`${process.pid}
|
|
7023
7270
|
`, "utf8");
|
|
@@ -7042,7 +7289,7 @@ async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaul
|
|
|
7042
7289
|
}
|
|
7043
7290
|
}
|
|
7044
7291
|
async function releaseLock(lockPath) {
|
|
7045
|
-
await
|
|
7292
|
+
await fs22.unlink(lockPath).catch(() => {
|
|
7046
7293
|
});
|
|
7047
7294
|
}
|
|
7048
7295
|
async function withLock(fn) {
|
|
@@ -7058,7 +7305,7 @@ async function readRegistry() {
|
|
|
7058
7305
|
const file = registryPath();
|
|
7059
7306
|
let raw;
|
|
7060
7307
|
try {
|
|
7061
|
-
raw = await
|
|
7308
|
+
raw = await fs22.readFile(file, "utf8");
|
|
7062
7309
|
} catch (err) {
|
|
7063
7310
|
if (err.code === "ENOENT") {
|
|
7064
7311
|
return { version: 1, projects: [] };
|
|
@@ -7163,7 +7410,7 @@ function pruneTtlMs() {
|
|
|
7163
7410
|
}
|
|
7164
7411
|
async function statPathStatus(p) {
|
|
7165
7412
|
try {
|
|
7166
|
-
const stat = await
|
|
7413
|
+
const stat = await fs22.stat(p);
|
|
7167
7414
|
return stat.isDirectory() ? "present" : "unknown";
|
|
7168
7415
|
} catch (err) {
|
|
7169
7416
|
return err.code === "ENOENT" ? "gone" : "unknown";
|
|
@@ -7208,14 +7455,14 @@ import cors from "@fastify/cors";
|
|
|
7208
7455
|
import { DivergenceTypeSchema, PoliciesCheckBodySchema, PolicySeveritySchema } from "@neat.is/types";
|
|
7209
7456
|
|
|
7210
7457
|
// src/extend/index.ts
|
|
7211
|
-
import { promises as
|
|
7212
|
-
import
|
|
7458
|
+
import { promises as fs24 } from "fs";
|
|
7459
|
+
import path42 from "path";
|
|
7213
7460
|
import os3 from "os";
|
|
7214
7461
|
import { resolve as registryResolve, list as registryList } from "@neat.is/instrumentation-registry";
|
|
7215
7462
|
|
|
7216
7463
|
// src/installers/package-manager.ts
|
|
7217
|
-
import { promises as
|
|
7218
|
-
import
|
|
7464
|
+
import { promises as fs23 } from "fs";
|
|
7465
|
+
import path41 from "path";
|
|
7219
7466
|
import { spawn } from "child_process";
|
|
7220
7467
|
var LOCKFILE_PRIORITY = [
|
|
7221
7468
|
{ lockfile: "bun.lockb", pm: "bun", args: ["install", "--no-summary"] },
|
|
@@ -7230,29 +7477,29 @@ var LOCKFILE_PRIORITY = [
|
|
|
7230
7477
|
var NPM_FALLBACK_ARGS = ["install", "--no-audit", "--no-fund", "--prefer-offline"];
|
|
7231
7478
|
async function exists2(p) {
|
|
7232
7479
|
try {
|
|
7233
|
-
await
|
|
7480
|
+
await fs23.access(p);
|
|
7234
7481
|
return true;
|
|
7235
7482
|
} catch {
|
|
7236
7483
|
return false;
|
|
7237
7484
|
}
|
|
7238
7485
|
}
|
|
7239
7486
|
async function detectPackageManager(serviceDir) {
|
|
7240
|
-
let dir =
|
|
7487
|
+
let dir = path41.resolve(serviceDir);
|
|
7241
7488
|
const stops = /* @__PURE__ */ new Set();
|
|
7242
7489
|
for (let i = 0; i < 64; i++) {
|
|
7243
7490
|
if (stops.has(dir)) break;
|
|
7244
7491
|
stops.add(dir);
|
|
7245
7492
|
for (const candidate of LOCKFILE_PRIORITY) {
|
|
7246
|
-
const lockPath =
|
|
7493
|
+
const lockPath = path41.join(dir, candidate.lockfile);
|
|
7247
7494
|
if (await exists2(lockPath)) {
|
|
7248
7495
|
return { pm: candidate.pm, cwd: dir, args: [...candidate.args] };
|
|
7249
7496
|
}
|
|
7250
7497
|
}
|
|
7251
|
-
const parent =
|
|
7498
|
+
const parent = path41.dirname(dir);
|
|
7252
7499
|
if (parent === dir) break;
|
|
7253
7500
|
dir = parent;
|
|
7254
7501
|
}
|
|
7255
|
-
return { pm: "npm", cwd:
|
|
7502
|
+
return { pm: "npm", cwd: path41.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
|
|
7256
7503
|
}
|
|
7257
7504
|
async function runPackageManagerInstall(cmd) {
|
|
7258
7505
|
return new Promise((resolve) => {
|
|
@@ -7294,30 +7541,30 @@ ${err.message}`
|
|
|
7294
7541
|
// src/extend/index.ts
|
|
7295
7542
|
async function fileExists2(p) {
|
|
7296
7543
|
try {
|
|
7297
|
-
await
|
|
7544
|
+
await fs24.access(p);
|
|
7298
7545
|
return true;
|
|
7299
7546
|
} catch {
|
|
7300
7547
|
return false;
|
|
7301
7548
|
}
|
|
7302
7549
|
}
|
|
7303
7550
|
async function readPackageJson(scanPath) {
|
|
7304
|
-
const pkgPath =
|
|
7305
|
-
const raw = await
|
|
7551
|
+
const pkgPath = path42.join(scanPath, "package.json");
|
|
7552
|
+
const raw = await fs24.readFile(pkgPath, "utf8");
|
|
7306
7553
|
return JSON.parse(raw);
|
|
7307
7554
|
}
|
|
7308
7555
|
async function findHookFiles(scanPath) {
|
|
7309
|
-
const entries = await
|
|
7556
|
+
const entries = await fs24.readdir(scanPath);
|
|
7310
7557
|
return entries.filter(
|
|
7311
7558
|
(e) => (e.startsWith("instrumentation") || e.startsWith("otel-init")) && /\.(ts|js)$/.test(e)
|
|
7312
7559
|
).sort();
|
|
7313
7560
|
}
|
|
7314
7561
|
function extendLogPath() {
|
|
7315
|
-
return process.env.NEAT_EXTEND_LOG ??
|
|
7562
|
+
return process.env.NEAT_EXTEND_LOG ?? path42.join(os3.homedir(), ".neat", "extend-log.ndjson");
|
|
7316
7563
|
}
|
|
7317
7564
|
async function appendExtendLog(entry) {
|
|
7318
7565
|
const logPath = extendLogPath();
|
|
7319
|
-
await
|
|
7320
|
-
await
|
|
7566
|
+
await fs24.mkdir(path42.dirname(logPath), { recursive: true });
|
|
7567
|
+
await fs24.appendFile(logPath, JSON.stringify(entry) + "\n", "utf8");
|
|
7321
7568
|
}
|
|
7322
7569
|
function splicedContent(fileContent, snippet2) {
|
|
7323
7570
|
if (fileContent.includes("__INSTRUMENTATION_BLOCK__")) {
|
|
@@ -7375,7 +7622,7 @@ function lookupInstrumentation(library, installedVersion) {
|
|
|
7375
7622
|
}
|
|
7376
7623
|
async function describeProjectInstrumentation(ctx) {
|
|
7377
7624
|
const hookFiles = await findHookFiles(ctx.scanPath);
|
|
7378
|
-
const envNeat = await fileExists2(
|
|
7625
|
+
const envNeat = await fileExists2(path42.join(ctx.scanPath, ".env.neat"));
|
|
7379
7626
|
const registryInstrPackages = new Set(
|
|
7380
7627
|
registryList().map((e) => e.instrumentation_package).filter((p) => !!p)
|
|
7381
7628
|
);
|
|
@@ -7397,31 +7644,31 @@ async function applyExtension(ctx, args, options) {
|
|
|
7397
7644
|
);
|
|
7398
7645
|
}
|
|
7399
7646
|
for (const file of hookFiles) {
|
|
7400
|
-
const content = await
|
|
7647
|
+
const content = await fs24.readFile(path42.join(ctx.scanPath, file), "utf8");
|
|
7401
7648
|
if (content.includes(args.registration_snippet)) {
|
|
7402
7649
|
return { library: args.library, filesTouched: [], depsAdded: [], installOutput: "", alreadyApplied: true };
|
|
7403
7650
|
}
|
|
7404
7651
|
}
|
|
7405
7652
|
const primaryFile = hookFiles[0];
|
|
7406
|
-
const primaryPath =
|
|
7653
|
+
const primaryPath = path42.join(ctx.scanPath, primaryFile);
|
|
7407
7654
|
const filesTouched = [];
|
|
7408
7655
|
const depsAdded = [];
|
|
7409
|
-
const pkgPath =
|
|
7656
|
+
const pkgPath = path42.join(ctx.scanPath, "package.json");
|
|
7410
7657
|
const pkg = await readPackageJson(ctx.scanPath);
|
|
7411
7658
|
if (!(pkg.dependencies ?? {})[args.instrumentation_package]) {
|
|
7412
7659
|
pkg.dependencies = { ...pkg.dependencies ?? {}, [args.instrumentation_package]: args.version };
|
|
7413
|
-
await
|
|
7660
|
+
await fs24.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
|
|
7414
7661
|
filesTouched.push("package.json");
|
|
7415
7662
|
depsAdded.push(`${args.instrumentation_package}@${args.version}`);
|
|
7416
7663
|
}
|
|
7417
|
-
const hookContent = await
|
|
7664
|
+
const hookContent = await fs24.readFile(primaryPath, "utf8");
|
|
7418
7665
|
const patched = splicedContent(hookContent, args.registration_snippet);
|
|
7419
7666
|
if (!patched) {
|
|
7420
7667
|
throw new Error(
|
|
7421
7668
|
`Could not find instrumentation insertion point in ${primaryFile}. Expected __INSTRUMENTATION_BLOCK__, instrumentations.push(, or new NodeSDK(.`
|
|
7422
7669
|
);
|
|
7423
7670
|
}
|
|
7424
|
-
await
|
|
7671
|
+
await fs24.writeFile(primaryPath, patched, "utf8");
|
|
7425
7672
|
filesTouched.push(primaryFile);
|
|
7426
7673
|
const cmd = await detectPackageManager(ctx.scanPath);
|
|
7427
7674
|
const installer = options?.runInstall ?? runPackageManagerInstall;
|
|
@@ -7452,7 +7699,7 @@ async function dryRunExtension(ctx, args) {
|
|
|
7452
7699
|
};
|
|
7453
7700
|
}
|
|
7454
7701
|
for (const file of hookFiles) {
|
|
7455
|
-
const content = await
|
|
7702
|
+
const content = await fs24.readFile(path42.join(ctx.scanPath, file), "utf8");
|
|
7456
7703
|
if (content.includes(args.registration_snippet)) {
|
|
7457
7704
|
return {
|
|
7458
7705
|
library: args.library,
|
|
@@ -7474,7 +7721,7 @@ async function dryRunExtension(ctx, args) {
|
|
|
7474
7721
|
depsToAdd.push(`${args.instrumentation_package}@${args.version}`);
|
|
7475
7722
|
filesTouched.push("package.json");
|
|
7476
7723
|
}
|
|
7477
|
-
const hookContent = await
|
|
7724
|
+
const hookContent = await fs24.readFile(path42.join(ctx.scanPath, primaryFile), "utf8");
|
|
7478
7725
|
const patched = splicedContent(hookContent, args.registration_snippet);
|
|
7479
7726
|
if (patched) {
|
|
7480
7727
|
filesTouched.push(primaryFile);
|
|
@@ -7489,28 +7736,28 @@ async function rollbackExtension(ctx, args) {
|
|
|
7489
7736
|
if (!await fileExists2(logPath)) {
|
|
7490
7737
|
return { undone: false, message: "no apply found for library" };
|
|
7491
7738
|
}
|
|
7492
|
-
const raw = await
|
|
7739
|
+
const raw = await fs24.readFile(logPath, "utf8");
|
|
7493
7740
|
const entries = raw.trim().split("\n").filter(Boolean).map((line) => JSON.parse(line));
|
|
7494
7741
|
const match = [...entries].reverse().find((e) => e.project === ctx.project && e.library === args.library);
|
|
7495
7742
|
if (!match) {
|
|
7496
7743
|
return { undone: false, message: "no apply found for library" };
|
|
7497
7744
|
}
|
|
7498
|
-
const pkgPath =
|
|
7745
|
+
const pkgPath = path42.join(ctx.scanPath, "package.json");
|
|
7499
7746
|
if (await fileExists2(pkgPath)) {
|
|
7500
7747
|
const pkg = await readPackageJson(ctx.scanPath);
|
|
7501
7748
|
if (pkg.dependencies?.[match.instrumentation_package]) {
|
|
7502
7749
|
const { [match.instrumentation_package]: _removed, ...rest } = pkg.dependencies;
|
|
7503
7750
|
pkg.dependencies = rest;
|
|
7504
|
-
await
|
|
7751
|
+
await fs24.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
|
|
7505
7752
|
}
|
|
7506
7753
|
}
|
|
7507
7754
|
const hookFiles = await findHookFiles(ctx.scanPath);
|
|
7508
7755
|
for (const file of hookFiles) {
|
|
7509
|
-
const filePath =
|
|
7510
|
-
const content = await
|
|
7756
|
+
const filePath = path42.join(ctx.scanPath, file);
|
|
7757
|
+
const content = await fs24.readFile(filePath, "utf8");
|
|
7511
7758
|
if (content.includes(match.registration_snippet)) {
|
|
7512
7759
|
const filtered = content.split("\n").filter((line) => !line.includes(match.registration_snippet)).join("\n");
|
|
7513
|
-
await
|
|
7760
|
+
await fs24.writeFile(filePath, filtered, "utf8");
|
|
7514
7761
|
break;
|
|
7515
7762
|
}
|
|
7516
7763
|
}
|
|
@@ -8339,6 +8586,7 @@ export {
|
|
|
8339
8586
|
reconcileObservedRelPath,
|
|
8340
8587
|
ensureObservedFileNode,
|
|
8341
8588
|
ensureServiceNode,
|
|
8589
|
+
ensureInfraNode,
|
|
8342
8590
|
upsertObservedEdge,
|
|
8343
8591
|
stitchTrace,
|
|
8344
8592
|
makeErrorSpanWriter,
|
|
@@ -8395,4 +8643,4 @@ export {
|
|
|
8395
8643
|
pruneRegistry,
|
|
8396
8644
|
buildApi
|
|
8397
8645
|
};
|
|
8398
|
-
//# sourceMappingURL=chunk-
|
|
8646
|
+
//# sourceMappingURL=chunk-5T6J3WKC.js.map
|