@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
package/dist/server.cjs
CHANGED
|
@@ -53,15 +53,16 @@ function mountBearerAuth(app, opts) {
|
|
|
53
53
|
if (!opts.token || opts.token.length === 0) return;
|
|
54
54
|
if (opts.trustProxy) return;
|
|
55
55
|
const expected = Buffer.from(opts.token, "utf8");
|
|
56
|
-
const
|
|
56
|
+
const exactUnauthPaths = /* @__PURE__ */ new Set([
|
|
57
|
+
...DEFAULT_UNAUTH_PATHS,
|
|
58
|
+
...opts.extraUnauthenticatedSuffixes ?? []
|
|
59
|
+
]);
|
|
57
60
|
const publicRead = opts.publicRead === true;
|
|
58
61
|
app.addHook("preHandler", (req, reply, done) => {
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
return;
|
|
64
|
-
}
|
|
62
|
+
const path47 = (req.url.split("?")[0] ?? "").replace(/\/+$/, "");
|
|
63
|
+
if (exactUnauthPaths.has(path47) || PROJECT_SCOPED_UNAUTH_PATTERN.test(path47)) {
|
|
64
|
+
done();
|
|
65
|
+
return;
|
|
65
66
|
}
|
|
66
67
|
if (publicRead && PUBLIC_READ_METHODS.has(req.method)) {
|
|
67
68
|
done();
|
|
@@ -96,7 +97,7 @@ function readAuthEnv(env = process.env) {
|
|
|
96
97
|
publicRead: parseBoolEnv(env.NEAT_PUBLIC_READ)
|
|
97
98
|
};
|
|
98
99
|
}
|
|
99
|
-
var import_node_crypto, LOOPBACK_HOSTS, BindAuthorityError, PUBLIC_READ_METHODS,
|
|
100
|
+
var import_node_crypto, LOOPBACK_HOSTS, BindAuthorityError, PUBLIC_READ_METHODS, DEFAULT_UNAUTH_PATHS, PROJECT_SCOPED_UNAUTH_PATTERN;
|
|
100
101
|
var init_auth = __esm({
|
|
101
102
|
"src/auth.ts"() {
|
|
102
103
|
"use strict";
|
|
@@ -117,12 +118,15 @@ var init_auth = __esm({
|
|
|
117
118
|
}
|
|
118
119
|
};
|
|
119
120
|
PUBLIC_READ_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD", "OPTIONS"]);
|
|
120
|
-
|
|
121
|
+
DEFAULT_UNAUTH_PATHS = [
|
|
121
122
|
"/health",
|
|
122
123
|
"/healthz",
|
|
123
124
|
"/readyz",
|
|
124
125
|
"/api/config"
|
|
125
126
|
];
|
|
127
|
+
PROJECT_SCOPED_UNAUTH_PATTERN = new RegExp(
|
|
128
|
+
`^/projects/[^/]+/(?:${DEFAULT_UNAUTH_PATHS.map((p) => p.slice(1)).join("|")})$`
|
|
129
|
+
);
|
|
126
130
|
}
|
|
127
131
|
});
|
|
128
132
|
|
|
@@ -188,8 +192,8 @@ function reshapeGrpcRequest(req) {
|
|
|
188
192
|
};
|
|
189
193
|
}
|
|
190
194
|
function resolveProtoRoot() {
|
|
191
|
-
const here =
|
|
192
|
-
return
|
|
195
|
+
const here = import_node_path43.default.dirname((0, import_node_url.fileURLToPath)(importMetaUrl));
|
|
196
|
+
return import_node_path43.default.resolve(here, "..", "proto");
|
|
193
197
|
}
|
|
194
198
|
function loadTraceService() {
|
|
195
199
|
const protoRoot = resolveProtoRoot();
|
|
@@ -257,13 +261,13 @@ async function startOtelGrpcReceiver(opts) {
|
|
|
257
261
|
})
|
|
258
262
|
};
|
|
259
263
|
}
|
|
260
|
-
var import_node_url,
|
|
264
|
+
var import_node_url, import_node_path43, import_node_crypto2, grpc, protoLoader;
|
|
261
265
|
var init_otel_grpc = __esm({
|
|
262
266
|
"src/otel-grpc.ts"() {
|
|
263
267
|
"use strict";
|
|
264
268
|
init_cjs_shims();
|
|
265
269
|
import_node_url = require("url");
|
|
266
|
-
|
|
270
|
+
import_node_path43 = __toESM(require("path"), 1);
|
|
267
271
|
import_node_crypto2 = require("crypto");
|
|
268
272
|
grpc = __toESM(require("@grpc/grpc-js"), 1);
|
|
269
273
|
protoLoader = __toESM(require("@grpc/proto-loader"), 1);
|
|
@@ -356,8 +360,8 @@ function websocketChannelPathOf(attrs) {
|
|
|
356
360
|
const v = attrs[key];
|
|
357
361
|
if (typeof v === "string" && v.length > 0) {
|
|
358
362
|
const q = v.indexOf("?");
|
|
359
|
-
const
|
|
360
|
-
if (
|
|
363
|
+
const path47 = q === -1 ? v : v.slice(0, q);
|
|
364
|
+
if (path47.length > 0) return path47;
|
|
361
365
|
}
|
|
362
366
|
}
|
|
363
367
|
return void 0;
|
|
@@ -411,10 +415,10 @@ function parseOtlpRequest(body) {
|
|
|
411
415
|
return out;
|
|
412
416
|
}
|
|
413
417
|
function loadProtoRoot() {
|
|
414
|
-
const here =
|
|
415
|
-
const protoRoot =
|
|
418
|
+
const here = import_node_path44.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
|
|
419
|
+
const protoRoot = import_node_path44.default.resolve(here, "..", "proto");
|
|
416
420
|
const root = new import_protobufjs.default.Root();
|
|
417
|
-
root.resolvePath = (_origin, target) =>
|
|
421
|
+
root.resolvePath = (_origin, target) => import_node_path44.default.resolve(protoRoot, target);
|
|
418
422
|
root.loadSync(
|
|
419
423
|
"opentelemetry/proto/collector/trace/v1/trace_service.proto",
|
|
420
424
|
{ keepCase: true }
|
|
@@ -626,12 +630,12 @@ async function buildOtelReceiver(opts) {
|
|
|
626
630
|
};
|
|
627
631
|
return decorated;
|
|
628
632
|
}
|
|
629
|
-
var
|
|
633
|
+
var import_node_path44, import_node_url2, import_fastify2, import_protobufjs, ENV_ATTR_CANONICAL, ENV_ATTR_COMPAT, ENV_FALLBACK, exportTraceServiceRequestType, exportTraceServiceResponseType, cachedProtobufResponseBody;
|
|
630
634
|
var init_otel = __esm({
|
|
631
635
|
"src/otel.ts"() {
|
|
632
636
|
"use strict";
|
|
633
637
|
init_cjs_shims();
|
|
634
|
-
|
|
638
|
+
import_node_path44 = __toESM(require("path"), 1);
|
|
635
639
|
import_node_url2 = require("url");
|
|
636
640
|
import_fastify2 = __toESM(require("fastify"), 1);
|
|
637
641
|
import_protobufjs = __toESM(require("protobufjs"), 1);
|
|
@@ -647,7 +651,7 @@ var init_otel = __esm({
|
|
|
647
651
|
|
|
648
652
|
// src/server.ts
|
|
649
653
|
init_cjs_shims();
|
|
650
|
-
var
|
|
654
|
+
var import_node_path46 = __toESM(require("path"), 1);
|
|
651
655
|
|
|
652
656
|
// src/graph.ts
|
|
653
657
|
init_cjs_shims();
|
|
@@ -671,7 +675,7 @@ function getGraph(project = DEFAULT_PROJECT) {
|
|
|
671
675
|
init_cjs_shims();
|
|
672
676
|
var import_fastify = __toESM(require("fastify"), 1);
|
|
673
677
|
var import_cors = __toESM(require("@fastify/cors"), 1);
|
|
674
|
-
var
|
|
678
|
+
var import_types30 = require("@neat.is/types");
|
|
675
679
|
|
|
676
680
|
// src/extend/index.ts
|
|
677
681
|
init_cjs_shims();
|
|
@@ -1414,19 +1418,19 @@ function confidenceFromMix(edges, now = Date.now()) {
|
|
|
1414
1418
|
function longestIncomingWalk(graph, start, maxDepth) {
|
|
1415
1419
|
let best = { path: [start], edges: [] };
|
|
1416
1420
|
const visited = /* @__PURE__ */ new Set([start]);
|
|
1417
|
-
function step(node,
|
|
1418
|
-
if (
|
|
1419
|
-
best = { path: [...
|
|
1421
|
+
function step(node, path47, edges) {
|
|
1422
|
+
if (path47.length > best.path.length) {
|
|
1423
|
+
best = { path: [...path47], edges: [...edges] };
|
|
1420
1424
|
}
|
|
1421
|
-
if (
|
|
1425
|
+
if (path47.length - 1 >= maxDepth) return;
|
|
1422
1426
|
const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
|
|
1423
1427
|
for (const [srcId, edge] of incoming) {
|
|
1424
1428
|
if (visited.has(srcId)) continue;
|
|
1425
1429
|
visited.add(srcId);
|
|
1426
|
-
|
|
1430
|
+
path47.push(srcId);
|
|
1427
1431
|
edges.push(edge);
|
|
1428
|
-
step(srcId,
|
|
1429
|
-
|
|
1432
|
+
step(srcId, path47, edges);
|
|
1433
|
+
path47.pop();
|
|
1430
1434
|
edges.pop();
|
|
1431
1435
|
visited.delete(srcId);
|
|
1432
1436
|
}
|
|
@@ -1620,26 +1624,26 @@ function dominantFailingCall(graph, serviceId4, visited) {
|
|
|
1620
1624
|
return best;
|
|
1621
1625
|
}
|
|
1622
1626
|
function followFailingCallChain(graph, originServiceId, maxDepth) {
|
|
1623
|
-
const
|
|
1627
|
+
const path47 = [originServiceId];
|
|
1624
1628
|
const edges = [];
|
|
1625
1629
|
const visited = /* @__PURE__ */ new Set([originServiceId]);
|
|
1626
1630
|
let current = originServiceId;
|
|
1627
1631
|
for (let depth = 0; depth < maxDepth; depth++) {
|
|
1628
1632
|
const hop = dominantFailingCall(graph, current, visited);
|
|
1629
1633
|
if (!hop) break;
|
|
1630
|
-
|
|
1634
|
+
path47.push(hop.nextService);
|
|
1631
1635
|
edges.push(hop.edge);
|
|
1632
1636
|
visited.add(hop.nextService);
|
|
1633
1637
|
current = hop.nextService;
|
|
1634
1638
|
}
|
|
1635
1639
|
if (edges.length === 0) return null;
|
|
1636
|
-
return { path:
|
|
1640
|
+
return { path: path47, edges, culprit: current };
|
|
1637
1641
|
}
|
|
1638
1642
|
function crossServiceRootCause(graph, originId, incidents, errorEvent) {
|
|
1639
1643
|
const chain = followFailingCallChain(graph, originId, ROOT_CAUSE_MAX_DEPTH);
|
|
1640
1644
|
if (!chain) return null;
|
|
1641
1645
|
const culprit = chain.culprit;
|
|
1642
|
-
const
|
|
1646
|
+
const path47 = [...chain.path];
|
|
1643
1647
|
const edgeProvenances = chain.edges.map((e) => e.provenance);
|
|
1644
1648
|
const baseConfidence = confidenceFromMix(chain.edges);
|
|
1645
1649
|
const confidence = Math.max(0, Math.min(1, baseConfidence * INCIDENT_ROOT_CAUSE_CONFIDENCE));
|
|
@@ -1647,14 +1651,14 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
|
|
|
1647
1651
|
if (loc) {
|
|
1648
1652
|
let rootCauseNode = culprit;
|
|
1649
1653
|
if (loc.fileNode) {
|
|
1650
|
-
|
|
1654
|
+
path47.push(loc.fileNode);
|
|
1651
1655
|
edgeProvenances.push(import_types.Provenance.OBSERVED);
|
|
1652
1656
|
rootCauseNode = loc.fileNode;
|
|
1653
1657
|
}
|
|
1654
1658
|
return import_types.RootCauseResultSchema.parse({
|
|
1655
1659
|
rootCauseNode,
|
|
1656
1660
|
rootCauseReason: loc.rootCauseReason,
|
|
1657
|
-
traversalPath:
|
|
1661
|
+
traversalPath: path47,
|
|
1658
1662
|
edgeProvenances,
|
|
1659
1663
|
confidence,
|
|
1660
1664
|
...loc.fixRecommendation ? { fixRecommendation: loc.fixRecommendation } : {}
|
|
@@ -1666,7 +1670,7 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
|
|
|
1666
1670
|
return import_types.RootCauseResultSchema.parse({
|
|
1667
1671
|
rootCauseNode: culprit,
|
|
1668
1672
|
rootCauseReason: `${culpritName} is failing downstream calls (${errs} observed error${errs === 1 ? "" : "s"})`,
|
|
1669
|
-
traversalPath:
|
|
1673
|
+
traversalPath: path47,
|
|
1670
1674
|
edgeProvenances,
|
|
1671
1675
|
confidence,
|
|
1672
1676
|
fixRecommendation: `Inspect ${culpritName}'s failing handler`
|
|
@@ -2077,6 +2081,51 @@ function computeDivergences(graph, opts = {}) {
|
|
|
2077
2081
|
});
|
|
2078
2082
|
}
|
|
2079
2083
|
|
|
2084
|
+
// src/logs-store.ts
|
|
2085
|
+
init_cjs_shims();
|
|
2086
|
+
var LOGS_STORE_MAX_AGE_MS = 24 * 60 * 60 * 1e3;
|
|
2087
|
+
var LOGS_QUERY_DEFAULT_LIMIT = 100;
|
|
2088
|
+
var LOGS_QUERY_MAX_LIMIT = 1e3;
|
|
2089
|
+
var buffers = /* @__PURE__ */ new Map();
|
|
2090
|
+
var KEY_SEP = "::";
|
|
2091
|
+
function bufferKey(projectName, source) {
|
|
2092
|
+
return `${projectName}${KEY_SEP}${source}`;
|
|
2093
|
+
}
|
|
2094
|
+
function sourcesForProject(projectName) {
|
|
2095
|
+
const prefix = `${projectName}${KEY_SEP}`;
|
|
2096
|
+
const sources = [];
|
|
2097
|
+
for (const key of buffers.keys()) {
|
|
2098
|
+
if (key.startsWith(prefix)) sources.push(key.slice(prefix.length));
|
|
2099
|
+
}
|
|
2100
|
+
return sources;
|
|
2101
|
+
}
|
|
2102
|
+
function queryLogEntries(opts) {
|
|
2103
|
+
const { projectName, source, service, since, limit } = opts;
|
|
2104
|
+
const sources = source && source.length > 0 ? source : sourcesForProject(projectName);
|
|
2105
|
+
let merged = [];
|
|
2106
|
+
for (const src of sources) {
|
|
2107
|
+
const buf = buffers.get(bufferKey(projectName, src));
|
|
2108
|
+
if (buf) merged = merged.concat(buf);
|
|
2109
|
+
}
|
|
2110
|
+
if (service) {
|
|
2111
|
+
merged = merged.filter((e) => e.serviceName === service);
|
|
2112
|
+
}
|
|
2113
|
+
if (since) {
|
|
2114
|
+
const sinceMs = Date.parse(since);
|
|
2115
|
+
if (!Number.isNaN(sinceMs)) {
|
|
2116
|
+
merged = merged.filter((e) => {
|
|
2117
|
+
const t = Date.parse(e.timestamp);
|
|
2118
|
+
return Number.isNaN(t) || t >= sinceMs;
|
|
2119
|
+
});
|
|
2120
|
+
}
|
|
2121
|
+
}
|
|
2122
|
+
merged.sort((a, b) => Date.parse(b.timestamp) - Date.parse(a.timestamp));
|
|
2123
|
+
if (typeof limit === "number" && Number.isFinite(limit) && limit >= 0) {
|
|
2124
|
+
return merged.slice(0, limit);
|
|
2125
|
+
}
|
|
2126
|
+
return merged;
|
|
2127
|
+
}
|
|
2128
|
+
|
|
2080
2129
|
// src/policy.ts
|
|
2081
2130
|
init_cjs_shims();
|
|
2082
2131
|
var import_node_fs4 = require("fs");
|
|
@@ -3663,24 +3712,58 @@ function dedupeIncidents(events) {
|
|
|
3663
3712
|
return !hasRealFailure.has(groupKey(ev));
|
|
3664
3713
|
});
|
|
3665
3714
|
}
|
|
3715
|
+
var SnapshotValidationError = class extends Error {
|
|
3716
|
+
constructor(issues) {
|
|
3717
|
+
super(`snapshot failed validation (${issues.length} invalid ${issues.length === 1 ? "entry" : "entries"})`);
|
|
3718
|
+
this.issues = issues;
|
|
3719
|
+
this.name = "SnapshotValidationError";
|
|
3720
|
+
}
|
|
3721
|
+
issues;
|
|
3722
|
+
};
|
|
3723
|
+
function describeZodIssues(error) {
|
|
3724
|
+
return error.issues.map((issue) => issue.path.length > 0 ? `${issue.path.join(".")}: ${issue.message}` : issue.message).join("; ");
|
|
3725
|
+
}
|
|
3666
3726
|
function mergeSnapshot(graph, snapshot) {
|
|
3667
3727
|
const exported = snapshot.graph;
|
|
3728
|
+
const incomingNodes = Array.isArray(exported.nodes) ? exported.nodes : [];
|
|
3729
|
+
const incomingEdges = Array.isArray(exported.edges) ? exported.edges : [];
|
|
3730
|
+
const issues = [];
|
|
3731
|
+
const validNodes = [];
|
|
3732
|
+
const validEdges = [];
|
|
3733
|
+
for (const node of incomingNodes) {
|
|
3734
|
+
if (node.attributes === void 0) continue;
|
|
3735
|
+
const parsed = import_types4.GraphNodeSchema.safeParse(node.attributes);
|
|
3736
|
+
if (!parsed.success) {
|
|
3737
|
+
issues.push(`node "${node.key}": ${describeZodIssues(parsed.error)}`);
|
|
3738
|
+
continue;
|
|
3739
|
+
}
|
|
3740
|
+
validNodes.push({ key: node.key, attributes: parsed.data });
|
|
3741
|
+
}
|
|
3742
|
+
for (const edge of incomingEdges) {
|
|
3743
|
+
if (edge.attributes === void 0) continue;
|
|
3744
|
+
const parsed = import_types4.GraphEdgeSchema.safeParse(edge.attributes);
|
|
3745
|
+
if (!parsed.success) {
|
|
3746
|
+
const label = edge.key ?? `${edge.source}->${edge.target}`;
|
|
3747
|
+
issues.push(`edge "${label}": ${describeZodIssues(parsed.error)}`);
|
|
3748
|
+
continue;
|
|
3749
|
+
}
|
|
3750
|
+
const id = edge.key ?? parsed.data.id;
|
|
3751
|
+
validEdges.push({ key: id, source: edge.source, target: edge.target, attributes: parsed.data });
|
|
3752
|
+
}
|
|
3753
|
+
if (issues.length > 0) {
|
|
3754
|
+
throw new SnapshotValidationError(issues);
|
|
3755
|
+
}
|
|
3668
3756
|
let nodesAdded = 0;
|
|
3669
3757
|
let edgesAdded = 0;
|
|
3670
|
-
for (const node of
|
|
3758
|
+
for (const node of validNodes) {
|
|
3671
3759
|
if (graph.hasNode(node.key)) continue;
|
|
3672
|
-
if (!node.attributes) continue;
|
|
3673
3760
|
graph.addNode(node.key, node.attributes);
|
|
3674
3761
|
nodesAdded++;
|
|
3675
3762
|
}
|
|
3676
|
-
for (const edge of
|
|
3677
|
-
|
|
3678
|
-
if (!attrs) continue;
|
|
3679
|
-
const id = edge.key ?? attrs.id;
|
|
3680
|
-
if (!id) continue;
|
|
3681
|
-
if (graph.hasEdge(id)) continue;
|
|
3763
|
+
for (const edge of validEdges) {
|
|
3764
|
+
if (graph.hasEdge(edge.key)) continue;
|
|
3682
3765
|
if (!graph.hasNode(edge.source) || !graph.hasNode(edge.target)) continue;
|
|
3683
|
-
graph.addEdgeWithKey(
|
|
3766
|
+
graph.addEdgeWithKey(edge.key, edge.source, edge.target, edge.attributes);
|
|
3684
3767
|
edgesAdded++;
|
|
3685
3768
|
}
|
|
3686
3769
|
return { nodesAdded, edgesAdded };
|
|
@@ -5735,10 +5818,10 @@ function fastifyRouteMethods(objNode) {
|
|
|
5735
5818
|
}
|
|
5736
5819
|
return [];
|
|
5737
5820
|
}
|
|
5738
|
-
function serverRoutesFromSource(source, parser, hasExpress, hasFastify) {
|
|
5821
|
+
function serverRoutesFromSource(source, parser, hasExpress, hasFastify, hasHono = false) {
|
|
5739
5822
|
const tree = parseSource2(parser, source);
|
|
5740
5823
|
const out = [];
|
|
5741
|
-
const framework = hasExpress ? "express" : "fastify";
|
|
5824
|
+
const framework = hasExpress ? "express" : hasFastify ? "fastify" : hasHono ? "hono" : "unknown";
|
|
5742
5825
|
walk(tree.rootNode, (node) => {
|
|
5743
5826
|
if (node.type !== "call_expression") return;
|
|
5744
5827
|
const fn = node.childForFieldName("function");
|
|
@@ -5888,8 +5971,9 @@ async function addRoutes(graph, services) {
|
|
|
5888
5971
|
};
|
|
5889
5972
|
const hasExpress = deps["express"] !== void 0;
|
|
5890
5973
|
const hasFastify = deps["fastify"] !== void 0;
|
|
5974
|
+
const hasHono = deps["hono"] !== void 0;
|
|
5891
5975
|
const hasNext = deps["next"] !== void 0;
|
|
5892
|
-
if (!hasExpress && !hasFastify && !hasNext) continue;
|
|
5976
|
+
if (!hasExpress && !hasFastify && !hasHono && !hasNext) continue;
|
|
5893
5977
|
const files = await loadSourceFiles(service.dir);
|
|
5894
5978
|
for (const file of files) {
|
|
5895
5979
|
if (isTestPath(file.path)) continue;
|
|
@@ -5899,8 +5983,8 @@ async function addRoutes(graph, services) {
|
|
|
5899
5983
|
try {
|
|
5900
5984
|
if (hasNext && (isNextAppRouteFile(relFile) || isNextPagesApiFile(relFile))) {
|
|
5901
5985
|
routes = nextRoutesFromFile(file.content, relFile, jsParser);
|
|
5902
|
-
} else if (hasExpress || hasFastify) {
|
|
5903
|
-
routes = serverRoutesFromSource(file.content, jsParser, hasExpress, hasFastify);
|
|
5986
|
+
} else if (hasExpress || hasFastify || hasHono) {
|
|
5987
|
+
routes = serverRoutesFromSource(file.content, jsParser, hasExpress, hasFastify, hasHono);
|
|
5904
5988
|
} else {
|
|
5905
5989
|
routes = [];
|
|
5906
5990
|
}
|
|
@@ -7224,30 +7308,264 @@ async function addK8sResources(graph, scanPath) {
|
|
|
7224
7308
|
return { nodesAdded, edgesAdded: 0 };
|
|
7225
7309
|
}
|
|
7226
7310
|
|
|
7311
|
+
// src/extract/infra/cloudflare.ts
|
|
7312
|
+
init_cjs_shims();
|
|
7313
|
+
var import_node_fs21 = require("fs");
|
|
7314
|
+
var import_node_path37 = __toESM(require("path"), 1);
|
|
7315
|
+
var import_smol_toml2 = require("smol-toml");
|
|
7316
|
+
var import_types26 = require("@neat.is/types");
|
|
7317
|
+
var WRANGLER_FILENAMES = ["wrangler.toml", "wrangler.jsonc", "wrangler.json"];
|
|
7318
|
+
async function readWranglerConfig(dir) {
|
|
7319
|
+
for (const filename of WRANGLER_FILENAMES) {
|
|
7320
|
+
const abs = import_node_path37.default.join(dir, filename);
|
|
7321
|
+
if (!await exists2(abs)) continue;
|
|
7322
|
+
const raw = await import_node_fs21.promises.readFile(abs, "utf8");
|
|
7323
|
+
const config = filename === "wrangler.toml" ? (0, import_smol_toml2.parse)(raw) : JSON.parse(maskCommentsInSource(raw));
|
|
7324
|
+
return { config, relFile: filename, raw };
|
|
7325
|
+
}
|
|
7326
|
+
return null;
|
|
7327
|
+
}
|
|
7328
|
+
function lineContaining(raw, needle) {
|
|
7329
|
+
if (!needle) return void 0;
|
|
7330
|
+
const idx = raw.indexOf(needle);
|
|
7331
|
+
if (idx === -1) return void 0;
|
|
7332
|
+
let line = 1;
|
|
7333
|
+
for (let i = 0; i < idx; i++) if (raw[i] === "\n") line++;
|
|
7334
|
+
return line;
|
|
7335
|
+
}
|
|
7336
|
+
function normalizeRoutes(config) {
|
|
7337
|
+
const out = [];
|
|
7338
|
+
const pushOne = (r) => {
|
|
7339
|
+
if (typeof r === "string") out.push(r);
|
|
7340
|
+
else if (r && typeof r === "object" && typeof r.pattern === "string") {
|
|
7341
|
+
out.push(r.pattern);
|
|
7342
|
+
}
|
|
7343
|
+
};
|
|
7344
|
+
if (Array.isArray(config.routes)) config.routes.forEach(pushOne);
|
|
7345
|
+
else if (config.route) pushOne(config.route);
|
|
7346
|
+
return out;
|
|
7347
|
+
}
|
|
7348
|
+
function addResourceEdge(graph, anchorId, edgeType, kind, name, evidenceFile, line) {
|
|
7349
|
+
let nodesAdded = 0;
|
|
7350
|
+
let edgesAdded = 0;
|
|
7351
|
+
const node = makeInfraNode(kind, name, "cloudflare");
|
|
7352
|
+
if (!graph.hasNode(node.id)) {
|
|
7353
|
+
graph.addNode(node.id, node);
|
|
7354
|
+
nodesAdded++;
|
|
7355
|
+
}
|
|
7356
|
+
if (node.id === anchorId) return { nodesAdded, edgesAdded };
|
|
7357
|
+
const edgeId = (0, import_types5.extractedEdgeId)(anchorId, node.id, edgeType);
|
|
7358
|
+
if (!graph.hasEdge(edgeId)) {
|
|
7359
|
+
const edge = {
|
|
7360
|
+
id: edgeId,
|
|
7361
|
+
source: anchorId,
|
|
7362
|
+
target: node.id,
|
|
7363
|
+
type: edgeType,
|
|
7364
|
+
provenance: import_types26.Provenance.EXTRACTED,
|
|
7365
|
+
confidence: (0, import_types26.confidenceForExtracted)("structural"),
|
|
7366
|
+
evidence: { file: evidenceFile, ...line !== void 0 ? { line } : {} }
|
|
7367
|
+
};
|
|
7368
|
+
graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
|
|
7369
|
+
edgesAdded++;
|
|
7370
|
+
}
|
|
7371
|
+
return { nodesAdded, edgesAdded };
|
|
7372
|
+
}
|
|
7373
|
+
async function addCloudflareWorkers(graph, services, scanPath) {
|
|
7374
|
+
let nodesAdded = 0;
|
|
7375
|
+
let edgesAdded = 0;
|
|
7376
|
+
const discovered = [];
|
|
7377
|
+
const workerIndex = /* @__PURE__ */ new Map();
|
|
7378
|
+
for (const service of services) {
|
|
7379
|
+
let read;
|
|
7380
|
+
try {
|
|
7381
|
+
read = await readWranglerConfig(service.dir);
|
|
7382
|
+
} catch (err) {
|
|
7383
|
+
recordExtractionError("infra cloudflare", import_node_path37.default.relative(scanPath, service.dir), err);
|
|
7384
|
+
continue;
|
|
7385
|
+
}
|
|
7386
|
+
if (!read || !read.config.name) continue;
|
|
7387
|
+
const evidenceFile = toPosix2(import_node_path37.default.relative(scanPath, import_node_path37.default.join(service.dir, read.relFile)));
|
|
7388
|
+
discovered.push({ service, config: read.config, relFile: read.relFile, raw: read.raw, evidenceFile });
|
|
7389
|
+
}
|
|
7390
|
+
for (const worker of discovered) {
|
|
7391
|
+
const { service, config } = worker;
|
|
7392
|
+
const serviceNode = graph.getNodeAttributes(service.node.id);
|
|
7393
|
+
if (serviceNode.platform !== "cloudflare") {
|
|
7394
|
+
const updated = { ...serviceNode, platform: "cloudflare" };
|
|
7395
|
+
graph.replaceNodeAttributes(service.node.id, updated);
|
|
7396
|
+
}
|
|
7397
|
+
let anchorId = service.node.id;
|
|
7398
|
+
if (config.main) {
|
|
7399
|
+
const entryRelPath = toPosix2(import_node_path37.default.normalize(config.main));
|
|
7400
|
+
const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
|
|
7401
|
+
graph,
|
|
7402
|
+
service.pkg.name,
|
|
7403
|
+
service.node.id,
|
|
7404
|
+
entryRelPath
|
|
7405
|
+
);
|
|
7406
|
+
nodesAdded += fn;
|
|
7407
|
+
edgesAdded += fe;
|
|
7408
|
+
const fileNode = graph.getNodeAttributes(fileNodeId);
|
|
7409
|
+
if (fileNode.platform !== "cloudflare" || fileNode.platformName !== config.name) {
|
|
7410
|
+
const updated = { ...fileNode, platform: "cloudflare", platformName: config.name };
|
|
7411
|
+
graph.replaceNodeAttributes(fileNodeId, updated);
|
|
7412
|
+
}
|
|
7413
|
+
anchorId = fileNodeId;
|
|
7414
|
+
}
|
|
7415
|
+
workerIndex.set(config.name, { anchorId });
|
|
7416
|
+
}
|
|
7417
|
+
for (const worker of discovered) {
|
|
7418
|
+
const { config, evidenceFile, raw } = worker;
|
|
7419
|
+
const anchorId = workerIndex.get(config.name).anchorId;
|
|
7420
|
+
const runtimeNode = makeInfraNode("workerd", "cloudflare", "cloudflare");
|
|
7421
|
+
if (!graph.hasNode(runtimeNode.id)) {
|
|
7422
|
+
graph.addNode(runtimeNode.id, runtimeNode);
|
|
7423
|
+
nodesAdded++;
|
|
7424
|
+
}
|
|
7425
|
+
if (runtimeNode.id !== anchorId) {
|
|
7426
|
+
const runsOnId = (0, import_types5.extractedEdgeId)(anchorId, runtimeNode.id, import_types26.EdgeType.RUNS_ON);
|
|
7427
|
+
if (!graph.hasEdge(runsOnId)) {
|
|
7428
|
+
const edge = {
|
|
7429
|
+
id: runsOnId,
|
|
7430
|
+
source: anchorId,
|
|
7431
|
+
target: runtimeNode.id,
|
|
7432
|
+
type: import_types26.EdgeType.RUNS_ON,
|
|
7433
|
+
provenance: import_types26.Provenance.EXTRACTED,
|
|
7434
|
+
confidence: (0, import_types26.confidenceForExtracted)("structural"),
|
|
7435
|
+
evidence: {
|
|
7436
|
+
file: evidenceFile,
|
|
7437
|
+
...config.compatibility_date ? { snippet: `compatibility_date = ${config.compatibility_date}`.slice(0, 120) } : {}
|
|
7438
|
+
}
|
|
7439
|
+
};
|
|
7440
|
+
graph.addEdgeWithKey(runsOnId, edge.source, edge.target, edge);
|
|
7441
|
+
edgesAdded++;
|
|
7442
|
+
}
|
|
7443
|
+
}
|
|
7444
|
+
for (const route of normalizeRoutes(config)) {
|
|
7445
|
+
const result = addResourceEdge(
|
|
7446
|
+
graph,
|
|
7447
|
+
anchorId,
|
|
7448
|
+
import_types26.EdgeType.CONNECTS_TO,
|
|
7449
|
+
"cloudflare-route",
|
|
7450
|
+
route,
|
|
7451
|
+
evidenceFile,
|
|
7452
|
+
lineContaining(raw, route)
|
|
7453
|
+
);
|
|
7454
|
+
nodesAdded += result.nodesAdded;
|
|
7455
|
+
edgesAdded += result.edgesAdded;
|
|
7456
|
+
}
|
|
7457
|
+
const bindingGroups = [
|
|
7458
|
+
{ kind: "cloudflare-kv", entries: config.kv_namespaces ?? [], nameOf: (b) => b.binding },
|
|
7459
|
+
{ kind: "cloudflare-d1", entries: config.d1_databases ?? [], nameOf: (b) => b.binding },
|
|
7460
|
+
{ kind: "cloudflare-r2", entries: config.r2_buckets ?? [], nameOf: (b) => b.binding },
|
|
7461
|
+
{ kind: "cloudflare-durable-object", entries: config.durable_objects?.bindings ?? [], nameOf: (b) => b.name },
|
|
7462
|
+
{ kind: "cloudflare-queue", entries: config.queues?.producers ?? [], nameOf: (b) => b.queue },
|
|
7463
|
+
{ kind: "cloudflare-queue", entries: config.queues?.consumers ?? [], nameOf: (b) => b.queue }
|
|
7464
|
+
];
|
|
7465
|
+
for (const group of bindingGroups) {
|
|
7466
|
+
for (const entry of group.entries) {
|
|
7467
|
+
const name = group.nameOf(entry);
|
|
7468
|
+
if (!name) continue;
|
|
7469
|
+
const result = addResourceEdge(
|
|
7470
|
+
graph,
|
|
7471
|
+
anchorId,
|
|
7472
|
+
import_types26.EdgeType.DEPENDS_ON,
|
|
7473
|
+
group.kind,
|
|
7474
|
+
name,
|
|
7475
|
+
evidenceFile,
|
|
7476
|
+
lineContaining(raw, name)
|
|
7477
|
+
);
|
|
7478
|
+
nodesAdded += result.nodesAdded;
|
|
7479
|
+
edgesAdded += result.edgesAdded;
|
|
7480
|
+
}
|
|
7481
|
+
}
|
|
7482
|
+
for (const cron of config.triggers?.crons ?? []) {
|
|
7483
|
+
const result = addResourceEdge(
|
|
7484
|
+
graph,
|
|
7485
|
+
anchorId,
|
|
7486
|
+
import_types26.EdgeType.DEPENDS_ON,
|
|
7487
|
+
"cloudflare-cron",
|
|
7488
|
+
cron,
|
|
7489
|
+
evidenceFile,
|
|
7490
|
+
lineContaining(raw, cron)
|
|
7491
|
+
);
|
|
7492
|
+
nodesAdded += result.nodesAdded;
|
|
7493
|
+
edgesAdded += result.edgesAdded;
|
|
7494
|
+
}
|
|
7495
|
+
for (const varName of Object.keys(config.vars ?? {})) {
|
|
7496
|
+
const result = addResourceEdge(
|
|
7497
|
+
graph,
|
|
7498
|
+
anchorId,
|
|
7499
|
+
import_types26.EdgeType.DEPENDS_ON,
|
|
7500
|
+
"cloudflare-env-var",
|
|
7501
|
+
varName,
|
|
7502
|
+
evidenceFile,
|
|
7503
|
+
lineContaining(raw, varName)
|
|
7504
|
+
);
|
|
7505
|
+
nodesAdded += result.nodesAdded;
|
|
7506
|
+
edgesAdded += result.edgesAdded;
|
|
7507
|
+
}
|
|
7508
|
+
for (const svc of config.services ?? []) {
|
|
7509
|
+
if (!svc.service) continue;
|
|
7510
|
+
const target = workerIndex.get(svc.service);
|
|
7511
|
+
if (target && target.anchorId !== anchorId) {
|
|
7512
|
+
const edgeId = (0, import_types5.extractedEdgeId)(anchorId, target.anchorId, import_types26.EdgeType.CALLS);
|
|
7513
|
+
if (!graph.hasEdge(edgeId)) {
|
|
7514
|
+
const edge = {
|
|
7515
|
+
id: edgeId,
|
|
7516
|
+
source: anchorId,
|
|
7517
|
+
target: target.anchorId,
|
|
7518
|
+
type: import_types26.EdgeType.CALLS,
|
|
7519
|
+
provenance: import_types26.Provenance.EXTRACTED,
|
|
7520
|
+
confidence: (0, import_types26.confidenceForExtracted)("structural"),
|
|
7521
|
+
evidence: { file: evidenceFile, line: lineContaining(raw, svc.service) }
|
|
7522
|
+
};
|
|
7523
|
+
graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
|
|
7524
|
+
edgesAdded++;
|
|
7525
|
+
}
|
|
7526
|
+
continue;
|
|
7527
|
+
}
|
|
7528
|
+
const result = addResourceEdge(
|
|
7529
|
+
graph,
|
|
7530
|
+
anchorId,
|
|
7531
|
+
import_types26.EdgeType.DEPENDS_ON,
|
|
7532
|
+
"cloudflare-service-binding",
|
|
7533
|
+
svc.service,
|
|
7534
|
+
evidenceFile,
|
|
7535
|
+
lineContaining(raw, svc.service)
|
|
7536
|
+
);
|
|
7537
|
+
nodesAdded += result.nodesAdded;
|
|
7538
|
+
edgesAdded += result.edgesAdded;
|
|
7539
|
+
}
|
|
7540
|
+
}
|
|
7541
|
+
return { nodesAdded, edgesAdded };
|
|
7542
|
+
}
|
|
7543
|
+
|
|
7227
7544
|
// src/extract/infra/index.ts
|
|
7228
7545
|
async function addInfra(graph, scanPath, services) {
|
|
7229
7546
|
const compose = await addComposeInfra(graph, scanPath, services);
|
|
7230
7547
|
const dockerfile = await addDockerfileRuntimes(graph, services, scanPath);
|
|
7231
7548
|
const terraform = await addTerraformResources(graph, scanPath);
|
|
7232
7549
|
const k8s = await addK8sResources(graph, scanPath);
|
|
7550
|
+
const cloudflare = await addCloudflareWorkers(graph, services, scanPath);
|
|
7233
7551
|
return {
|
|
7234
|
-
nodesAdded: compose.nodesAdded + dockerfile.nodesAdded + terraform.nodesAdded + k8s.nodesAdded,
|
|
7235
|
-
edgesAdded: compose.edgesAdded + dockerfile.edgesAdded + terraform.edgesAdded + k8s.edgesAdded
|
|
7552
|
+
nodesAdded: compose.nodesAdded + dockerfile.nodesAdded + terraform.nodesAdded + k8s.nodesAdded + cloudflare.nodesAdded,
|
|
7553
|
+
edgesAdded: compose.edgesAdded + dockerfile.edgesAdded + terraform.edgesAdded + k8s.edgesAdded + cloudflare.edgesAdded
|
|
7236
7554
|
};
|
|
7237
7555
|
}
|
|
7238
7556
|
|
|
7239
7557
|
// src/extract/index.ts
|
|
7240
|
-
var
|
|
7558
|
+
var import_node_path39 = __toESM(require("path"), 1);
|
|
7241
7559
|
|
|
7242
7560
|
// src/extract/retire.ts
|
|
7243
7561
|
init_cjs_shims();
|
|
7244
|
-
var
|
|
7245
|
-
var
|
|
7246
|
-
var
|
|
7562
|
+
var import_node_fs22 = require("fs");
|
|
7563
|
+
var import_node_path38 = __toESM(require("path"), 1);
|
|
7564
|
+
var import_types27 = require("@neat.is/types");
|
|
7247
7565
|
function dropOrphanedFileNodes(graph) {
|
|
7248
7566
|
const orphans = [];
|
|
7249
7567
|
graph.forEachNode((id, attrs) => {
|
|
7250
|
-
if (attrs.type !==
|
|
7568
|
+
if (attrs.type !== import_types27.NodeType.FileNode) return;
|
|
7251
7569
|
if (graph.inboundEdges(id).length === 0 && graph.outboundEdges(id).length === 0) {
|
|
7252
7570
|
orphans.push(id);
|
|
7253
7571
|
}
|
|
@@ -7260,14 +7578,14 @@ function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = []) {
|
|
|
7260
7578
|
const bases = [scanPath, ...serviceDirs];
|
|
7261
7579
|
graph.forEachEdge((id, attrs) => {
|
|
7262
7580
|
const edge = attrs;
|
|
7263
|
-
if (edge.provenance !==
|
|
7581
|
+
if (edge.provenance !== import_types27.Provenance.EXTRACTED) return;
|
|
7264
7582
|
const evidenceFile = edge.evidence?.file;
|
|
7265
7583
|
if (!evidenceFile) return;
|
|
7266
|
-
if (
|
|
7267
|
-
if (!(0,
|
|
7584
|
+
if (import_node_path38.default.isAbsolute(evidenceFile)) {
|
|
7585
|
+
if (!(0, import_node_fs22.existsSync)(evidenceFile)) toDrop.push(id);
|
|
7268
7586
|
return;
|
|
7269
7587
|
}
|
|
7270
|
-
const found = bases.some((base) => (0,
|
|
7588
|
+
const found = bases.some((base) => (0, import_node_fs22.existsSync)(import_node_path38.default.join(base, evidenceFile)));
|
|
7271
7589
|
if (!found) toDrop.push(id);
|
|
7272
7590
|
});
|
|
7273
7591
|
for (const id of toDrop) graph.dropEdge(id);
|
|
@@ -7309,7 +7627,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
7309
7627
|
}
|
|
7310
7628
|
const droppedEntries = drainDroppedExtracted();
|
|
7311
7629
|
if (isRejectedLogEnabled() && opts.errorsPath && droppedEntries.length > 0) {
|
|
7312
|
-
const rejectedPath =
|
|
7630
|
+
const rejectedPath = import_node_path39.default.join(import_node_path39.default.dirname(opts.errorsPath), "rejected.ndjson");
|
|
7313
7631
|
try {
|
|
7314
7632
|
await writeRejectedExtracted(droppedEntries, rejectedPath);
|
|
7315
7633
|
} catch (err) {
|
|
@@ -7343,7 +7661,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
7343
7661
|
|
|
7344
7662
|
// src/diff.ts
|
|
7345
7663
|
init_cjs_shims();
|
|
7346
|
-
var
|
|
7664
|
+
var import_node_fs23 = require("fs");
|
|
7347
7665
|
async function loadSnapshotForDiff(target) {
|
|
7348
7666
|
if (/^https?:\/\//i.test(target)) {
|
|
7349
7667
|
const res = await fetch(target);
|
|
@@ -7352,7 +7670,7 @@ async function loadSnapshotForDiff(target) {
|
|
|
7352
7670
|
}
|
|
7353
7671
|
return await res.json();
|
|
7354
7672
|
}
|
|
7355
|
-
const raw = await
|
|
7673
|
+
const raw = await import_node_fs23.promises.readFile(target, "utf8");
|
|
7356
7674
|
return JSON.parse(raw);
|
|
7357
7675
|
}
|
|
7358
7676
|
function indexEntries(entries) {
|
|
@@ -7420,9 +7738,9 @@ function canonicalJson(value) {
|
|
|
7420
7738
|
|
|
7421
7739
|
// src/persist.ts
|
|
7422
7740
|
init_cjs_shims();
|
|
7423
|
-
var
|
|
7424
|
-
var
|
|
7425
|
-
var
|
|
7741
|
+
var import_node_fs24 = require("fs");
|
|
7742
|
+
var import_node_path40 = __toESM(require("path"), 1);
|
|
7743
|
+
var import_types28 = require("@neat.is/types");
|
|
7426
7744
|
var SCHEMA_VERSION = 4;
|
|
7427
7745
|
function migrateV1ToV2(payload) {
|
|
7428
7746
|
const nodes = payload.graph.nodes;
|
|
@@ -7444,12 +7762,12 @@ function migrateV2ToV3(payload) {
|
|
|
7444
7762
|
for (const edge of edges) {
|
|
7445
7763
|
const attrs = edge.attributes;
|
|
7446
7764
|
if (!attrs || attrs.provenance !== "FRONTIER") continue;
|
|
7447
|
-
attrs.provenance =
|
|
7765
|
+
attrs.provenance = import_types28.Provenance.OBSERVED;
|
|
7448
7766
|
const type = typeof attrs.type === "string" ? attrs.type : void 0;
|
|
7449
7767
|
const source = typeof attrs.source === "string" ? attrs.source : void 0;
|
|
7450
7768
|
const target = typeof attrs.target === "string" ? attrs.target : void 0;
|
|
7451
7769
|
if (type && source && target) {
|
|
7452
|
-
const newId = (0,
|
|
7770
|
+
const newId = (0, import_types28.observedEdgeId)(source, target, type);
|
|
7453
7771
|
attrs.id = newId;
|
|
7454
7772
|
if (edge.key) edge.key = newId;
|
|
7455
7773
|
}
|
|
@@ -7458,7 +7776,7 @@ function migrateV2ToV3(payload) {
|
|
|
7458
7776
|
return { ...payload, schemaVersion: 3 };
|
|
7459
7777
|
}
|
|
7460
7778
|
async function ensureDir(filePath) {
|
|
7461
|
-
await
|
|
7779
|
+
await import_node_fs24.promises.mkdir(import_node_path40.default.dirname(filePath), { recursive: true });
|
|
7462
7780
|
}
|
|
7463
7781
|
async function saveGraphToDisk(graph, outPath) {
|
|
7464
7782
|
await ensureDir(outPath);
|
|
@@ -7468,13 +7786,13 @@ async function saveGraphToDisk(graph, outPath) {
|
|
|
7468
7786
|
graph: graph.export()
|
|
7469
7787
|
};
|
|
7470
7788
|
const tmp = `${outPath}.tmp`;
|
|
7471
|
-
await
|
|
7472
|
-
await
|
|
7789
|
+
await import_node_fs24.promises.writeFile(tmp, JSON.stringify(payload), "utf8");
|
|
7790
|
+
await import_node_fs24.promises.rename(tmp, outPath);
|
|
7473
7791
|
}
|
|
7474
7792
|
async function loadGraphFromDisk(graph, outPath) {
|
|
7475
7793
|
let raw;
|
|
7476
7794
|
try {
|
|
7477
|
-
raw = await
|
|
7795
|
+
raw = await import_node_fs24.promises.readFile(outPath, "utf8");
|
|
7478
7796
|
} catch (err) {
|
|
7479
7797
|
if (err.code === "ENOENT") return;
|
|
7480
7798
|
throw err;
|
|
@@ -7539,23 +7857,23 @@ function startPersistLoop(graph, outPath, opts = {}) {
|
|
|
7539
7857
|
|
|
7540
7858
|
// src/projects.ts
|
|
7541
7859
|
init_cjs_shims();
|
|
7542
|
-
var
|
|
7860
|
+
var import_node_path41 = __toESM(require("path"), 1);
|
|
7543
7861
|
function pathsForProject(project, baseDir) {
|
|
7544
7862
|
if (project === DEFAULT_PROJECT) {
|
|
7545
7863
|
return {
|
|
7546
|
-
snapshotPath:
|
|
7547
|
-
errorsPath:
|
|
7548
|
-
staleEventsPath:
|
|
7549
|
-
embeddingsCachePath:
|
|
7550
|
-
policyViolationsPath:
|
|
7864
|
+
snapshotPath: import_node_path41.default.join(baseDir, "graph.json"),
|
|
7865
|
+
errorsPath: import_node_path41.default.join(baseDir, "errors.ndjson"),
|
|
7866
|
+
staleEventsPath: import_node_path41.default.join(baseDir, "stale-events.ndjson"),
|
|
7867
|
+
embeddingsCachePath: import_node_path41.default.join(baseDir, "embeddings.json"),
|
|
7868
|
+
policyViolationsPath: import_node_path41.default.join(baseDir, "policy-violations.ndjson")
|
|
7551
7869
|
};
|
|
7552
7870
|
}
|
|
7553
7871
|
return {
|
|
7554
|
-
snapshotPath:
|
|
7555
|
-
errorsPath:
|
|
7556
|
-
staleEventsPath:
|
|
7557
|
-
embeddingsCachePath:
|
|
7558
|
-
policyViolationsPath:
|
|
7872
|
+
snapshotPath: import_node_path41.default.join(baseDir, `${project}.json`),
|
|
7873
|
+
errorsPath: import_node_path41.default.join(baseDir, `errors.${project}.ndjson`),
|
|
7874
|
+
staleEventsPath: import_node_path41.default.join(baseDir, `stale-events.${project}.ndjson`),
|
|
7875
|
+
embeddingsCachePath: import_node_path41.default.join(baseDir, `embeddings.${project}.json`),
|
|
7876
|
+
policyViolationsPath: import_node_path41.default.join(baseDir, `policy-violations.${project}.ndjson`)
|
|
7559
7877
|
};
|
|
7560
7878
|
}
|
|
7561
7879
|
var Projects = class {
|
|
@@ -7595,23 +7913,23 @@ function parseExtraProjects(raw) {
|
|
|
7595
7913
|
|
|
7596
7914
|
// src/registry.ts
|
|
7597
7915
|
init_cjs_shims();
|
|
7598
|
-
var
|
|
7916
|
+
var import_node_fs25 = require("fs");
|
|
7599
7917
|
var import_node_os3 = __toESM(require("os"), 1);
|
|
7600
|
-
var
|
|
7601
|
-
var
|
|
7918
|
+
var import_node_path42 = __toESM(require("path"), 1);
|
|
7919
|
+
var import_types29 = require("@neat.is/types");
|
|
7602
7920
|
function neatHome() {
|
|
7603
7921
|
const override = process.env.NEAT_HOME;
|
|
7604
|
-
if (override && override.length > 0) return
|
|
7605
|
-
return
|
|
7922
|
+
if (override && override.length > 0) return import_node_path42.default.resolve(override);
|
|
7923
|
+
return import_node_path42.default.join(import_node_os3.default.homedir(), ".neat");
|
|
7606
7924
|
}
|
|
7607
7925
|
function registryPath() {
|
|
7608
|
-
return
|
|
7926
|
+
return import_node_path42.default.join(neatHome(), "projects.json");
|
|
7609
7927
|
}
|
|
7610
7928
|
async function readRegistry() {
|
|
7611
7929
|
const file = registryPath();
|
|
7612
7930
|
let raw;
|
|
7613
7931
|
try {
|
|
7614
|
-
raw = await
|
|
7932
|
+
raw = await import_node_fs25.promises.readFile(file, "utf8");
|
|
7615
7933
|
} catch (err) {
|
|
7616
7934
|
if (err.code === "ENOENT") {
|
|
7617
7935
|
return { version: 1, projects: [] };
|
|
@@ -7619,7 +7937,7 @@ async function readRegistry() {
|
|
|
7619
7937
|
throw err;
|
|
7620
7938
|
}
|
|
7621
7939
|
const parsed = JSON.parse(raw);
|
|
7622
|
-
return
|
|
7940
|
+
return import_types29.RegistryFileSchema.parse(parsed);
|
|
7623
7941
|
}
|
|
7624
7942
|
async function getProject(name) {
|
|
7625
7943
|
const reg = await readRegistry();
|
|
@@ -7840,11 +8158,11 @@ function registerRoutes(scope, ctx) {
|
|
|
7840
8158
|
const candidates = req.query.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
7841
8159
|
const parsed = [];
|
|
7842
8160
|
for (const c of candidates) {
|
|
7843
|
-
const r =
|
|
8161
|
+
const r = import_types30.DivergenceTypeSchema.safeParse(c);
|
|
7844
8162
|
if (!r.success) {
|
|
7845
8163
|
return reply.code(400).send({
|
|
7846
8164
|
error: `unknown divergence type "${c}"`,
|
|
7847
|
-
allowed:
|
|
8165
|
+
allowed: import_types30.DivergenceTypeSchema.options
|
|
7848
8166
|
});
|
|
7849
8167
|
}
|
|
7850
8168
|
parsed.push(r.data);
|
|
@@ -7892,6 +8210,23 @@ function registerRoutes(scope, ctx) {
|
|
|
7892
8210
|
const sliced = ordered.slice(0, Number.isFinite(limit) && limit > 0 ? limit : 50);
|
|
7893
8211
|
return { count: sliced.length, total, events: sliced };
|
|
7894
8212
|
});
|
|
8213
|
+
scope.get("/logs", async (req, reply) => {
|
|
8214
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
|
|
8215
|
+
if (!proj) return;
|
|
8216
|
+
const rawSource = req.query.source;
|
|
8217
|
+
const sources = rawSource === void 0 ? void 0 : Array.isArray(rawSource) ? rawSource : [rawSource];
|
|
8218
|
+
const filtered = queryLogEntries({
|
|
8219
|
+
projectName: proj.name,
|
|
8220
|
+
...sources && sources.length > 0 ? { source: sources } : {},
|
|
8221
|
+
...req.query.service ? { service: req.query.service } : {},
|
|
8222
|
+
...req.query.since ? { since: req.query.since } : {}
|
|
8223
|
+
});
|
|
8224
|
+
const total = filtered.length;
|
|
8225
|
+
const limit = req.query.limit ? Number(req.query.limit) : LOGS_QUERY_DEFAULT_LIMIT;
|
|
8226
|
+
const safeLimit = Number.isFinite(limit) && limit > 0 ? Math.min(limit, LOGS_QUERY_MAX_LIMIT) : LOGS_QUERY_DEFAULT_LIMIT;
|
|
8227
|
+
const sliced = filtered.slice(0, safeLimit);
|
|
8228
|
+
return { count: sliced.length, total, logs: sliced };
|
|
8229
|
+
});
|
|
7895
8230
|
const incidentHistoryHandler = async (req, reply) => {
|
|
7896
8231
|
const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
|
|
7897
8232
|
if (!proj) return;
|
|
@@ -7987,8 +8322,16 @@ function registerRoutes(scope, ctx) {
|
|
|
7987
8322
|
if (!against) {
|
|
7988
8323
|
return reply.code(400).send({ error: "query parameter `against` is required" });
|
|
7989
8324
|
}
|
|
8325
|
+
const targetProjectName = against === "self" ? proj.name : against;
|
|
8326
|
+
const targetProject = registry.get(targetProjectName);
|
|
8327
|
+
if (!targetProject) {
|
|
8328
|
+
return reply.code(400).send({
|
|
8329
|
+
error: "unknown snapshot id \u2014 `against` must be `self` or the name of a project this server has a snapshot for",
|
|
8330
|
+
against
|
|
8331
|
+
});
|
|
8332
|
+
}
|
|
7990
8333
|
try {
|
|
7991
|
-
const snapshot = await loadSnapshotForDiff(
|
|
8334
|
+
const snapshot = await loadSnapshotForDiff(targetProject.paths.snapshotPath);
|
|
7992
8335
|
return computeGraphDiff(proj.graph, snapshot);
|
|
7993
8336
|
} catch (err) {
|
|
7994
8337
|
return reply.code(400).send({ error: "failed to load snapshot", against, detail: err.message });
|
|
@@ -8018,6 +8361,13 @@ function registerRoutes(scope, ctx) {
|
|
|
8018
8361
|
edgeCount: proj.graph.size
|
|
8019
8362
|
};
|
|
8020
8363
|
} catch (err) {
|
|
8364
|
+
if (err instanceof SnapshotValidationError) {
|
|
8365
|
+
return reply.code(400).send({
|
|
8366
|
+
error: "snapshot merge failed",
|
|
8367
|
+
details: err.message,
|
|
8368
|
+
issues: err.issues
|
|
8369
|
+
});
|
|
8370
|
+
}
|
|
8021
8371
|
return reply.code(400).send({
|
|
8022
8372
|
error: "snapshot merge failed",
|
|
8023
8373
|
details: err.message
|
|
@@ -8063,7 +8413,7 @@ function registerRoutes(scope, ctx) {
|
|
|
8063
8413
|
const log = new PolicyViolationsLog(proj.paths.policyViolationsPath);
|
|
8064
8414
|
let violations = await log.readAll();
|
|
8065
8415
|
if (req.query.severity) {
|
|
8066
|
-
const sev =
|
|
8416
|
+
const sev = import_types30.PolicySeveritySchema.safeParse(req.query.severity);
|
|
8067
8417
|
if (!sev.success) {
|
|
8068
8418
|
return reply.code(400).send({
|
|
8069
8419
|
error: "invalid severity",
|
|
@@ -8102,7 +8452,7 @@ function registerRoutes(scope, ctx) {
|
|
|
8102
8452
|
scope.post("/policies/check", async (req, reply) => {
|
|
8103
8453
|
const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
|
|
8104
8454
|
if (!proj) return;
|
|
8105
|
-
const parsed =
|
|
8455
|
+
const parsed = import_types30.PoliciesCheckBodySchema.safeParse(req.body ?? {});
|
|
8106
8456
|
if (!parsed.success) {
|
|
8107
8457
|
return reply.code(400).send({
|
|
8108
8458
|
error: "invalid /policies/check body",
|
|
@@ -8364,8 +8714,8 @@ init_otel_grpc();
|
|
|
8364
8714
|
|
|
8365
8715
|
// src/search.ts
|
|
8366
8716
|
init_cjs_shims();
|
|
8367
|
-
var
|
|
8368
|
-
var
|
|
8717
|
+
var import_node_fs26 = require("fs");
|
|
8718
|
+
var import_node_path45 = __toESM(require("path"), 1);
|
|
8369
8719
|
var import_node_crypto3 = require("crypto");
|
|
8370
8720
|
var DEFAULT_LIMIT = 10;
|
|
8371
8721
|
var NOMIC_DIM = 768;
|
|
@@ -8519,7 +8869,7 @@ async function pickEmbedder() {
|
|
|
8519
8869
|
}
|
|
8520
8870
|
async function readCache(cachePath) {
|
|
8521
8871
|
try {
|
|
8522
|
-
const raw = await
|
|
8872
|
+
const raw = await import_node_fs26.promises.readFile(cachePath, "utf8");
|
|
8523
8873
|
const parsed = JSON.parse(raw);
|
|
8524
8874
|
if (parsed.version !== 1) return null;
|
|
8525
8875
|
return parsed;
|
|
@@ -8528,8 +8878,8 @@ async function readCache(cachePath) {
|
|
|
8528
8878
|
}
|
|
8529
8879
|
}
|
|
8530
8880
|
async function writeCache(cachePath, cache) {
|
|
8531
|
-
await
|
|
8532
|
-
await
|
|
8881
|
+
await import_node_fs26.promises.mkdir(import_node_path45.default.dirname(cachePath), { recursive: true });
|
|
8882
|
+
await import_node_fs26.promises.writeFile(cachePath, JSON.stringify(cache));
|
|
8533
8883
|
}
|
|
8534
8884
|
var VectorIndex = class {
|
|
8535
8885
|
constructor(embedder, cachePath) {
|
|
@@ -8709,14 +9059,14 @@ async function bootProject(registry, name, scanPath, baseDir) {
|
|
|
8709
9059
|
async function main() {
|
|
8710
9060
|
const baseDirEnv = process.env.NEAT_OUT_DIR;
|
|
8711
9061
|
const legacyOutPath = process.env.NEAT_OUT_PATH;
|
|
8712
|
-
const baseDir = baseDirEnv ?
|
|
8713
|
-
const defaultScanPath =
|
|
9062
|
+
const baseDir = baseDirEnv ? import_node_path46.default.resolve(baseDirEnv) : legacyOutPath ? import_node_path46.default.resolve(import_node_path46.default.dirname(legacyOutPath)) : import_node_path46.default.resolve("./neat-out");
|
|
9063
|
+
const defaultScanPath = import_node_path46.default.resolve(process.env.NEAT_SCAN_PATH ?? "./demo");
|
|
8714
9064
|
const registry = new Projects();
|
|
8715
9065
|
await bootProject(registry, DEFAULT_PROJECT, defaultScanPath, baseDir);
|
|
8716
9066
|
for (const name of parseExtraProjects(process.env.NEAT_PROJECTS)) {
|
|
8717
9067
|
const envKey = `NEAT_PROJECT_SCAN_PATH_${name.toUpperCase().replace(/[^A-Z0-9]/g, "_")}`;
|
|
8718
9068
|
const projectScan = process.env[envKey];
|
|
8719
|
-
await bootProject(registry, name, projectScan ?
|
|
9069
|
+
await bootProject(registry, name, projectScan ? import_node_path46.default.resolve(projectScan) : void 0, baseDir);
|
|
8720
9070
|
}
|
|
8721
9071
|
const host = process.env.HOST ?? "0.0.0.0";
|
|
8722
9072
|
const port = Number(process.env.PORT ?? 8080);
|