@neat.is/core 0.4.28-dev.20260712 → 0.4.28-dev.20260714
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-5T6J3WKC.js → chunk-6HPRD53A.js} +734 -144
- package/dist/chunk-6HPRD53A.js.map +1 -0
- package/dist/{chunk-UJ6WBLIE.js → chunk-AOLXZWRU.js} +163 -345
- package/dist/chunk-AOLXZWRU.js.map +1 -0
- package/dist/cli.cjs +1177 -814
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +9 -9
- package/dist/index.cjs +787 -381
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -2
- package/dist/neatd.cjs +852 -446
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +2 -2
- package/dist/server.cjs +510 -92
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +1 -1
- package/package.json +2 -2
- package/dist/chunk-5T6J3WKC.js.map +0 -1
- package/dist/chunk-UJ6WBLIE.js.map +0 -1
package/dist/server.cjs
CHANGED
|
@@ -59,8 +59,8 @@ function mountBearerAuth(app, opts) {
|
|
|
59
59
|
]);
|
|
60
60
|
const publicRead = opts.publicRead === true;
|
|
61
61
|
app.addHook("preHandler", (req, reply, done) => {
|
|
62
|
-
const
|
|
63
|
-
if (exactUnauthPaths.has(
|
|
62
|
+
const path51 = (req.url.split("?")[0] ?? "").replace(/\/+$/, "");
|
|
63
|
+
if (exactUnauthPaths.has(path51) || PROJECT_SCOPED_UNAUTH_PATTERN.test(path51)) {
|
|
64
64
|
done();
|
|
65
65
|
return;
|
|
66
66
|
}
|
|
@@ -192,8 +192,8 @@ function reshapeGrpcRequest(req) {
|
|
|
192
192
|
};
|
|
193
193
|
}
|
|
194
194
|
function resolveProtoRoot() {
|
|
195
|
-
const here =
|
|
196
|
-
return
|
|
195
|
+
const here = import_node_path47.default.dirname((0, import_node_url.fileURLToPath)(importMetaUrl));
|
|
196
|
+
return import_node_path47.default.resolve(here, "..", "proto");
|
|
197
197
|
}
|
|
198
198
|
function loadTraceService() {
|
|
199
199
|
const protoRoot = resolveProtoRoot();
|
|
@@ -261,13 +261,13 @@ async function startOtelGrpcReceiver(opts) {
|
|
|
261
261
|
})
|
|
262
262
|
};
|
|
263
263
|
}
|
|
264
|
-
var import_node_url,
|
|
264
|
+
var import_node_url, import_node_path47, import_node_crypto2, grpc, protoLoader;
|
|
265
265
|
var init_otel_grpc = __esm({
|
|
266
266
|
"src/otel-grpc.ts"() {
|
|
267
267
|
"use strict";
|
|
268
268
|
init_cjs_shims();
|
|
269
269
|
import_node_url = require("url");
|
|
270
|
-
|
|
270
|
+
import_node_path47 = __toESM(require("path"), 1);
|
|
271
271
|
import_node_crypto2 = require("crypto");
|
|
272
272
|
grpc = __toESM(require("@grpc/grpc-js"), 1);
|
|
273
273
|
protoLoader = __toESM(require("@grpc/proto-loader"), 1);
|
|
@@ -360,8 +360,8 @@ function websocketChannelPathOf(attrs) {
|
|
|
360
360
|
const v = attrs[key];
|
|
361
361
|
if (typeof v === "string" && v.length > 0) {
|
|
362
362
|
const q = v.indexOf("?");
|
|
363
|
-
const
|
|
364
|
-
if (
|
|
363
|
+
const path51 = q === -1 ? v : v.slice(0, q);
|
|
364
|
+
if (path51.length > 0) return path51;
|
|
365
365
|
}
|
|
366
366
|
}
|
|
367
367
|
return void 0;
|
|
@@ -415,10 +415,10 @@ function parseOtlpRequest(body) {
|
|
|
415
415
|
return out;
|
|
416
416
|
}
|
|
417
417
|
function loadProtoRoot() {
|
|
418
|
-
const here =
|
|
419
|
-
const protoRoot =
|
|
418
|
+
const here = import_node_path48.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
|
|
419
|
+
const protoRoot = import_node_path48.default.resolve(here, "..", "proto");
|
|
420
420
|
const root = new import_protobufjs.default.Root();
|
|
421
|
-
root.resolvePath = (_origin, target) =>
|
|
421
|
+
root.resolvePath = (_origin, target) => import_node_path48.default.resolve(protoRoot, target);
|
|
422
422
|
root.loadSync(
|
|
423
423
|
"opentelemetry/proto/collector/trace/v1/trace_service.proto",
|
|
424
424
|
{ keepCase: true }
|
|
@@ -630,12 +630,12 @@ async function buildOtelReceiver(opts) {
|
|
|
630
630
|
};
|
|
631
631
|
return decorated;
|
|
632
632
|
}
|
|
633
|
-
var
|
|
633
|
+
var import_node_path48, import_node_url2, import_fastify2, import_protobufjs, ENV_ATTR_CANONICAL, ENV_ATTR_COMPAT, ENV_FALLBACK, exportTraceServiceRequestType, exportTraceServiceResponseType, cachedProtobufResponseBody;
|
|
634
634
|
var init_otel = __esm({
|
|
635
635
|
"src/otel.ts"() {
|
|
636
636
|
"use strict";
|
|
637
637
|
init_cjs_shims();
|
|
638
|
-
|
|
638
|
+
import_node_path48 = __toESM(require("path"), 1);
|
|
639
639
|
import_node_url2 = require("url");
|
|
640
640
|
import_fastify2 = __toESM(require("fastify"), 1);
|
|
641
641
|
import_protobufjs = __toESM(require("protobufjs"), 1);
|
|
@@ -651,7 +651,7 @@ var init_otel = __esm({
|
|
|
651
651
|
|
|
652
652
|
// src/server.ts
|
|
653
653
|
init_cjs_shims();
|
|
654
|
-
var
|
|
654
|
+
var import_node_path50 = __toESM(require("path"), 1);
|
|
655
655
|
|
|
656
656
|
// src/graph.ts
|
|
657
657
|
init_cjs_shims();
|
|
@@ -675,7 +675,7 @@ function getGraph(project = DEFAULT_PROJECT) {
|
|
|
675
675
|
init_cjs_shims();
|
|
676
676
|
var import_fastify = __toESM(require("fastify"), 1);
|
|
677
677
|
var import_cors = __toESM(require("@fastify/cors"), 1);
|
|
678
|
-
var
|
|
678
|
+
var import_types33 = require("@neat.is/types");
|
|
679
679
|
|
|
680
680
|
// src/extend/index.ts
|
|
681
681
|
init_cjs_shims();
|
|
@@ -1418,19 +1418,19 @@ function confidenceFromMix(edges, now = Date.now()) {
|
|
|
1418
1418
|
function longestIncomingWalk(graph, start, maxDepth) {
|
|
1419
1419
|
let best = { path: [start], edges: [] };
|
|
1420
1420
|
const visited = /* @__PURE__ */ new Set([start]);
|
|
1421
|
-
function step(node,
|
|
1422
|
-
if (
|
|
1423
|
-
best = { path: [...
|
|
1421
|
+
function step(node, path51, edges) {
|
|
1422
|
+
if (path51.length > best.path.length) {
|
|
1423
|
+
best = { path: [...path51], edges: [...edges] };
|
|
1424
1424
|
}
|
|
1425
|
-
if (
|
|
1425
|
+
if (path51.length - 1 >= maxDepth) return;
|
|
1426
1426
|
const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
|
|
1427
1427
|
for (const [srcId, edge] of incoming) {
|
|
1428
1428
|
if (visited.has(srcId)) continue;
|
|
1429
1429
|
visited.add(srcId);
|
|
1430
|
-
|
|
1430
|
+
path51.push(srcId);
|
|
1431
1431
|
edges.push(edge);
|
|
1432
|
-
step(srcId,
|
|
1433
|
-
|
|
1432
|
+
step(srcId, path51, edges);
|
|
1433
|
+
path51.pop();
|
|
1434
1434
|
edges.pop();
|
|
1435
1435
|
visited.delete(srcId);
|
|
1436
1436
|
}
|
|
@@ -1624,26 +1624,26 @@ function dominantFailingCall(graph, serviceId4, visited) {
|
|
|
1624
1624
|
return best;
|
|
1625
1625
|
}
|
|
1626
1626
|
function followFailingCallChain(graph, originServiceId, maxDepth) {
|
|
1627
|
-
const
|
|
1627
|
+
const path51 = [originServiceId];
|
|
1628
1628
|
const edges = [];
|
|
1629
1629
|
const visited = /* @__PURE__ */ new Set([originServiceId]);
|
|
1630
1630
|
let current = originServiceId;
|
|
1631
1631
|
for (let depth = 0; depth < maxDepth; depth++) {
|
|
1632
1632
|
const hop = dominantFailingCall(graph, current, visited);
|
|
1633
1633
|
if (!hop) break;
|
|
1634
|
-
|
|
1634
|
+
path51.push(hop.nextService);
|
|
1635
1635
|
edges.push(hop.edge);
|
|
1636
1636
|
visited.add(hop.nextService);
|
|
1637
1637
|
current = hop.nextService;
|
|
1638
1638
|
}
|
|
1639
1639
|
if (edges.length === 0) return null;
|
|
1640
|
-
return { path:
|
|
1640
|
+
return { path: path51, edges, culprit: current };
|
|
1641
1641
|
}
|
|
1642
1642
|
function crossServiceRootCause(graph, originId, incidents, errorEvent) {
|
|
1643
1643
|
const chain = followFailingCallChain(graph, originId, ROOT_CAUSE_MAX_DEPTH);
|
|
1644
1644
|
if (!chain) return null;
|
|
1645
1645
|
const culprit = chain.culprit;
|
|
1646
|
-
const
|
|
1646
|
+
const path51 = [...chain.path];
|
|
1647
1647
|
const edgeProvenances = chain.edges.map((e) => e.provenance);
|
|
1648
1648
|
const baseConfidence = confidenceFromMix(chain.edges);
|
|
1649
1649
|
const confidence = Math.max(0, Math.min(1, baseConfidence * INCIDENT_ROOT_CAUSE_CONFIDENCE));
|
|
@@ -1651,14 +1651,14 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
|
|
|
1651
1651
|
if (loc) {
|
|
1652
1652
|
let rootCauseNode = culprit;
|
|
1653
1653
|
if (loc.fileNode) {
|
|
1654
|
-
|
|
1654
|
+
path51.push(loc.fileNode);
|
|
1655
1655
|
edgeProvenances.push(import_types.Provenance.OBSERVED);
|
|
1656
1656
|
rootCauseNode = loc.fileNode;
|
|
1657
1657
|
}
|
|
1658
1658
|
return import_types.RootCauseResultSchema.parse({
|
|
1659
1659
|
rootCauseNode,
|
|
1660
1660
|
rootCauseReason: loc.rootCauseReason,
|
|
1661
|
-
traversalPath:
|
|
1661
|
+
traversalPath: path51,
|
|
1662
1662
|
edgeProvenances,
|
|
1663
1663
|
confidence,
|
|
1664
1664
|
...loc.fixRecommendation ? { fixRecommendation: loc.fixRecommendation } : {}
|
|
@@ -1670,7 +1670,7 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
|
|
|
1670
1670
|
return import_types.RootCauseResultSchema.parse({
|
|
1671
1671
|
rootCauseNode: culprit,
|
|
1672
1672
|
rootCauseReason: `${culpritName} is failing downstream calls (${errs} observed error${errs === 1 ? "" : "s"})`,
|
|
1673
|
-
traversalPath:
|
|
1673
|
+
traversalPath: path51,
|
|
1674
1674
|
edgeProvenances,
|
|
1675
1675
|
confidence,
|
|
1676
1676
|
fixRecommendation: `Inspect ${culpritName}'s failing handler`
|
|
@@ -6966,6 +6966,39 @@ function classifyImage(image) {
|
|
|
6966
6966
|
if (last.startsWith("memcached")) return "memcached";
|
|
6967
6967
|
return "container";
|
|
6968
6968
|
}
|
|
6969
|
+
function lineContaining(raw, needle) {
|
|
6970
|
+
if (!needle) return void 0;
|
|
6971
|
+
const idx = raw.indexOf(needle);
|
|
6972
|
+
if (idx === -1) return void 0;
|
|
6973
|
+
let line = 1;
|
|
6974
|
+
for (let i = 0; i < idx; i++) if (raw[i] === "\n") line++;
|
|
6975
|
+
return line;
|
|
6976
|
+
}
|
|
6977
|
+
function emitPlatformResourceEdge(graph, anchorId, edgeType, kind, name, provider, evidenceFile, line) {
|
|
6978
|
+
let nodesAdded = 0;
|
|
6979
|
+
let edgesAdded = 0;
|
|
6980
|
+
const node = makeInfraNode(kind, name, provider);
|
|
6981
|
+
if (!graph.hasNode(node.id)) {
|
|
6982
|
+
graph.addNode(node.id, node);
|
|
6983
|
+
nodesAdded++;
|
|
6984
|
+
}
|
|
6985
|
+
if (node.id === anchorId) return { nodesAdded, edgesAdded };
|
|
6986
|
+
const edgeId = (0, import_types5.extractedEdgeId)(anchorId, node.id, edgeType);
|
|
6987
|
+
if (!graph.hasEdge(edgeId)) {
|
|
6988
|
+
const edge = {
|
|
6989
|
+
id: edgeId,
|
|
6990
|
+
source: anchorId,
|
|
6991
|
+
target: node.id,
|
|
6992
|
+
type: edgeType,
|
|
6993
|
+
provenance: import_types22.Provenance.EXTRACTED,
|
|
6994
|
+
confidence: (0, import_types22.confidenceForExtracted)("structural"),
|
|
6995
|
+
evidence: { file: evidenceFile, ...line !== void 0 ? { line } : {} }
|
|
6996
|
+
};
|
|
6997
|
+
graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
|
|
6998
|
+
edgesAdded++;
|
|
6999
|
+
}
|
|
7000
|
+
return { nodesAdded, edgesAdded };
|
|
7001
|
+
}
|
|
6969
7002
|
|
|
6970
7003
|
// src/extract/infra/docker-compose.ts
|
|
6971
7004
|
function dependsOnList(value) {
|
|
@@ -7325,7 +7358,7 @@ async function readWranglerConfig(dir) {
|
|
|
7325
7358
|
}
|
|
7326
7359
|
return null;
|
|
7327
7360
|
}
|
|
7328
|
-
function
|
|
7361
|
+
function lineContaining2(raw, needle) {
|
|
7329
7362
|
if (!needle) return void 0;
|
|
7330
7363
|
const idx = raw.indexOf(needle);
|
|
7331
7364
|
if (idx === -1) return void 0;
|
|
@@ -7449,7 +7482,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
|
|
|
7449
7482
|
"cloudflare-route",
|
|
7450
7483
|
route,
|
|
7451
7484
|
evidenceFile,
|
|
7452
|
-
|
|
7485
|
+
lineContaining2(raw, route)
|
|
7453
7486
|
);
|
|
7454
7487
|
nodesAdded += result.nodesAdded;
|
|
7455
7488
|
edgesAdded += result.edgesAdded;
|
|
@@ -7473,7 +7506,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
|
|
|
7473
7506
|
group.kind,
|
|
7474
7507
|
name,
|
|
7475
7508
|
evidenceFile,
|
|
7476
|
-
|
|
7509
|
+
lineContaining2(raw, name)
|
|
7477
7510
|
);
|
|
7478
7511
|
nodesAdded += result.nodesAdded;
|
|
7479
7512
|
edgesAdded += result.edgesAdded;
|
|
@@ -7487,7 +7520,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
|
|
|
7487
7520
|
"cloudflare-cron",
|
|
7488
7521
|
cron,
|
|
7489
7522
|
evidenceFile,
|
|
7490
|
-
|
|
7523
|
+
lineContaining2(raw, cron)
|
|
7491
7524
|
);
|
|
7492
7525
|
nodesAdded += result.nodesAdded;
|
|
7493
7526
|
edgesAdded += result.edgesAdded;
|
|
@@ -7500,7 +7533,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
|
|
|
7500
7533
|
"cloudflare-env-var",
|
|
7501
7534
|
varName,
|
|
7502
7535
|
evidenceFile,
|
|
7503
|
-
|
|
7536
|
+
lineContaining2(raw, varName)
|
|
7504
7537
|
);
|
|
7505
7538
|
nodesAdded += result.nodesAdded;
|
|
7506
7539
|
edgesAdded += result.edgesAdded;
|
|
@@ -7518,7 +7551,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
|
|
|
7518
7551
|
type: import_types26.EdgeType.CALLS,
|
|
7519
7552
|
provenance: import_types26.Provenance.EXTRACTED,
|
|
7520
7553
|
confidence: (0, import_types26.confidenceForExtracted)("structural"),
|
|
7521
|
-
evidence: { file: evidenceFile, line:
|
|
7554
|
+
evidence: { file: evidenceFile, line: lineContaining2(raw, svc.service) }
|
|
7522
7555
|
};
|
|
7523
7556
|
graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
|
|
7524
7557
|
edgesAdded++;
|
|
@@ -7532,7 +7565,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
|
|
|
7532
7565
|
"cloudflare-service-binding",
|
|
7533
7566
|
svc.service,
|
|
7534
7567
|
evidenceFile,
|
|
7535
|
-
|
|
7568
|
+
lineContaining2(raw, svc.service)
|
|
7536
7569
|
);
|
|
7537
7570
|
nodesAdded += result.nodesAdded;
|
|
7538
7571
|
edgesAdded += result.edgesAdded;
|
|
@@ -7541,6 +7574,203 @@ async function addCloudflareWorkers(graph, services, scanPath) {
|
|
|
7541
7574
|
return { nodesAdded, edgesAdded };
|
|
7542
7575
|
}
|
|
7543
7576
|
|
|
7577
|
+
// src/extract/infra/vercel.ts
|
|
7578
|
+
init_cjs_shims();
|
|
7579
|
+
var import_node_fs22 = require("fs");
|
|
7580
|
+
var import_node_path38 = __toESM(require("path"), 1);
|
|
7581
|
+
var import_types27 = require("@neat.is/types");
|
|
7582
|
+
var VERCEL_CONFIG_FILENAMES = ["vercel.json", "vercel.jsonc"];
|
|
7583
|
+
async function readVercelConfig(dir) {
|
|
7584
|
+
for (const filename of VERCEL_CONFIG_FILENAMES) {
|
|
7585
|
+
const abs = import_node_path38.default.join(dir, filename);
|
|
7586
|
+
if (!await exists2(abs)) continue;
|
|
7587
|
+
const raw = await import_node_fs22.promises.readFile(abs, "utf8");
|
|
7588
|
+
const config = JSON.parse(maskCommentsInSource(raw));
|
|
7589
|
+
return { config, relFile: filename, raw };
|
|
7590
|
+
}
|
|
7591
|
+
return null;
|
|
7592
|
+
}
|
|
7593
|
+
async function readLinkedProjectName(dir) {
|
|
7594
|
+
const abs = import_node_path38.default.join(dir, ".vercel", "project.json");
|
|
7595
|
+
if (!await exists2(abs)) return void 0;
|
|
7596
|
+
const parsed = JSON.parse(await import_node_fs22.promises.readFile(abs, "utf8"));
|
|
7597
|
+
return typeof parsed.projectName === "string" ? parsed.projectName : void 0;
|
|
7598
|
+
}
|
|
7599
|
+
function routeSource(route) {
|
|
7600
|
+
return route.source ?? route.src;
|
|
7601
|
+
}
|
|
7602
|
+
async function addVercelServices(graph, services, scanPath) {
|
|
7603
|
+
let nodesAdded = 0;
|
|
7604
|
+
let edgesAdded = 0;
|
|
7605
|
+
for (const service of services) {
|
|
7606
|
+
let read = null;
|
|
7607
|
+
let projectName;
|
|
7608
|
+
try {
|
|
7609
|
+
read = await readVercelConfig(service.dir);
|
|
7610
|
+
projectName = await readLinkedProjectName(service.dir);
|
|
7611
|
+
} catch (err) {
|
|
7612
|
+
recordExtractionError("infra vercel", import_node_path38.default.relative(scanPath, service.dir), err);
|
|
7613
|
+
continue;
|
|
7614
|
+
}
|
|
7615
|
+
if (!read && !projectName) continue;
|
|
7616
|
+
const serviceNode = graph.getNodeAttributes(service.node.id);
|
|
7617
|
+
if (serviceNode.platform !== "vercel" || projectName && serviceNode.platformName !== projectName) {
|
|
7618
|
+
const updated = {
|
|
7619
|
+
...serviceNode,
|
|
7620
|
+
platform: "vercel",
|
|
7621
|
+
...projectName ? { platformName: projectName } : {}
|
|
7622
|
+
};
|
|
7623
|
+
graph.replaceNodeAttributes(service.node.id, updated);
|
|
7624
|
+
}
|
|
7625
|
+
const anchorId = service.node.id;
|
|
7626
|
+
if (!read) continue;
|
|
7627
|
+
const { config, relFile, raw } = read;
|
|
7628
|
+
const evidenceFile = toPosix2(import_node_path38.default.relative(scanPath, import_node_path38.default.join(service.dir, relFile)));
|
|
7629
|
+
const add = (edgeType, kind, name) => {
|
|
7630
|
+
if (!name) return;
|
|
7631
|
+
const result = emitPlatformResourceEdge(
|
|
7632
|
+
graph,
|
|
7633
|
+
anchorId,
|
|
7634
|
+
edgeType,
|
|
7635
|
+
kind,
|
|
7636
|
+
name,
|
|
7637
|
+
"vercel",
|
|
7638
|
+
evidenceFile,
|
|
7639
|
+
lineContaining(raw, name)
|
|
7640
|
+
);
|
|
7641
|
+
nodesAdded += result.nodesAdded;
|
|
7642
|
+
edgesAdded += result.edgesAdded;
|
|
7643
|
+
};
|
|
7644
|
+
add(import_types27.EdgeType.RUNS_ON, "vercel", "vercel");
|
|
7645
|
+
for (const cron of config.crons ?? []) add(import_types27.EdgeType.DEPENDS_ON, "vercel-cron", cron.path ?? cron.schedule);
|
|
7646
|
+
for (const varName of Object.keys(config.env ?? {})) add(import_types27.EdgeType.DEPENDS_ON, "vercel-env-var", varName);
|
|
7647
|
+
for (const varName of Object.keys(config.build?.env ?? {})) add(import_types27.EdgeType.DEPENDS_ON, "vercel-env-var", varName);
|
|
7648
|
+
for (const route of [...config.rewrites ?? [], ...config.redirects ?? [], ...config.routes ?? []]) {
|
|
7649
|
+
add(import_types27.EdgeType.CONNECTS_TO, "vercel-route", routeSource(route));
|
|
7650
|
+
}
|
|
7651
|
+
}
|
|
7652
|
+
return { nodesAdded, edgesAdded };
|
|
7653
|
+
}
|
|
7654
|
+
|
|
7655
|
+
// src/extract/infra/railway.ts
|
|
7656
|
+
init_cjs_shims();
|
|
7657
|
+
var import_node_fs23 = require("fs");
|
|
7658
|
+
var import_node_path39 = __toESM(require("path"), 1);
|
|
7659
|
+
var import_smol_toml3 = require("smol-toml");
|
|
7660
|
+
var import_types28 = require("@neat.is/types");
|
|
7661
|
+
var RAILWAY_FILENAMES = ["railway.toml", "railway.json", "railway.jsonc"];
|
|
7662
|
+
async function readRailwayConfig(dir) {
|
|
7663
|
+
for (const filename of RAILWAY_FILENAMES) {
|
|
7664
|
+
const abs = import_node_path39.default.join(dir, filename);
|
|
7665
|
+
if (!await exists2(abs)) continue;
|
|
7666
|
+
const raw = await import_node_fs23.promises.readFile(abs, "utf8");
|
|
7667
|
+
const config = filename === "railway.toml" ? (0, import_smol_toml3.parse)(raw) : JSON.parse(maskCommentsInSource(raw));
|
|
7668
|
+
return { config, relFile: filename, raw };
|
|
7669
|
+
}
|
|
7670
|
+
return null;
|
|
7671
|
+
}
|
|
7672
|
+
async function addRailwayServices(graph, services, scanPath) {
|
|
7673
|
+
let nodesAdded = 0;
|
|
7674
|
+
let edgesAdded = 0;
|
|
7675
|
+
for (const service of services) {
|
|
7676
|
+
let read = null;
|
|
7677
|
+
try {
|
|
7678
|
+
read = await readRailwayConfig(service.dir);
|
|
7679
|
+
} catch (err) {
|
|
7680
|
+
recordExtractionError("infra railway", import_node_path39.default.relative(scanPath, service.dir), err);
|
|
7681
|
+
continue;
|
|
7682
|
+
}
|
|
7683
|
+
if (!read) continue;
|
|
7684
|
+
const serviceNode = graph.getNodeAttributes(service.node.id);
|
|
7685
|
+
if (serviceNode.platform !== "railway") {
|
|
7686
|
+
graph.replaceNodeAttributes(service.node.id, { ...serviceNode, platform: "railway" });
|
|
7687
|
+
}
|
|
7688
|
+
const anchorId = service.node.id;
|
|
7689
|
+
const { config, relFile, raw } = read;
|
|
7690
|
+
const evidenceFile = toPosix2(import_node_path39.default.relative(scanPath, import_node_path39.default.join(service.dir, relFile)));
|
|
7691
|
+
const add = (edgeType, kind, name) => {
|
|
7692
|
+
if (!name) return;
|
|
7693
|
+
const result = emitPlatformResourceEdge(
|
|
7694
|
+
graph,
|
|
7695
|
+
anchorId,
|
|
7696
|
+
edgeType,
|
|
7697
|
+
kind,
|
|
7698
|
+
name,
|
|
7699
|
+
"railway",
|
|
7700
|
+
evidenceFile,
|
|
7701
|
+
lineContaining(raw, name)
|
|
7702
|
+
);
|
|
7703
|
+
nodesAdded += result.nodesAdded;
|
|
7704
|
+
edgesAdded += result.edgesAdded;
|
|
7705
|
+
};
|
|
7706
|
+
add(import_types28.EdgeType.RUNS_ON, "railway", "railway");
|
|
7707
|
+
add(import_types28.EdgeType.CONNECTS_TO, "railway-route", config.deploy?.healthcheckPath);
|
|
7708
|
+
add(import_types28.EdgeType.DEPENDS_ON, "railway-cron", config.deploy?.cronSchedule);
|
|
7709
|
+
}
|
|
7710
|
+
return { nodesAdded, edgesAdded };
|
|
7711
|
+
}
|
|
7712
|
+
|
|
7713
|
+
// src/extract/infra/supabase.ts
|
|
7714
|
+
init_cjs_shims();
|
|
7715
|
+
var import_node_fs24 = require("fs");
|
|
7716
|
+
var import_node_path40 = __toESM(require("path"), 1);
|
|
7717
|
+
var import_smol_toml4 = require("smol-toml");
|
|
7718
|
+
var import_types29 = require("@neat.is/types");
|
|
7719
|
+
async function readSupabaseConfig(dir) {
|
|
7720
|
+
const relFile = import_node_path40.default.join("supabase", "config.toml");
|
|
7721
|
+
const abs = import_node_path40.default.join(dir, relFile);
|
|
7722
|
+
if (!await exists2(abs)) return null;
|
|
7723
|
+
const raw = await import_node_fs24.promises.readFile(abs, "utf8");
|
|
7724
|
+
const config = (0, import_smol_toml4.parse)(raw);
|
|
7725
|
+
return { config, relFile, raw };
|
|
7726
|
+
}
|
|
7727
|
+
async function addSupabaseProjects(graph, services, scanPath) {
|
|
7728
|
+
let nodesAdded = 0;
|
|
7729
|
+
let edgesAdded = 0;
|
|
7730
|
+
for (const service of services) {
|
|
7731
|
+
let read = null;
|
|
7732
|
+
try {
|
|
7733
|
+
read = await readSupabaseConfig(service.dir);
|
|
7734
|
+
} catch (err) {
|
|
7735
|
+
recordExtractionError("infra supabase", import_node_path40.default.relative(scanPath, service.dir), err);
|
|
7736
|
+
continue;
|
|
7737
|
+
}
|
|
7738
|
+
if (!read) continue;
|
|
7739
|
+
const { config, relFile, raw } = read;
|
|
7740
|
+
const projectId = typeof config.project_id === "string" ? config.project_id : void 0;
|
|
7741
|
+
const serviceNode = graph.getNodeAttributes(service.node.id);
|
|
7742
|
+
if (serviceNode.platform !== "supabase" || projectId && serviceNode.platformName !== projectId) {
|
|
7743
|
+
graph.replaceNodeAttributes(service.node.id, {
|
|
7744
|
+
...serviceNode,
|
|
7745
|
+
platform: "supabase",
|
|
7746
|
+
...projectId ? { platformName: projectId } : {}
|
|
7747
|
+
});
|
|
7748
|
+
}
|
|
7749
|
+
const anchorId = service.node.id;
|
|
7750
|
+
const evidenceFile = toPosix2(import_node_path40.default.relative(scanPath, import_node_path40.default.join(service.dir, relFile)));
|
|
7751
|
+
const add = (edgeType, kind, name) => {
|
|
7752
|
+
if (!name) return;
|
|
7753
|
+
const result = emitPlatformResourceEdge(
|
|
7754
|
+
graph,
|
|
7755
|
+
anchorId,
|
|
7756
|
+
edgeType,
|
|
7757
|
+
kind,
|
|
7758
|
+
name,
|
|
7759
|
+
"supabase",
|
|
7760
|
+
evidenceFile,
|
|
7761
|
+
lineContaining(raw, name)
|
|
7762
|
+
);
|
|
7763
|
+
nodesAdded += result.nodesAdded;
|
|
7764
|
+
edgesAdded += result.edgesAdded;
|
|
7765
|
+
};
|
|
7766
|
+
add(import_types29.EdgeType.RUNS_ON, "supabase", "supabase");
|
|
7767
|
+
for (const fn of Object.keys(config.functions ?? {})) add(import_types29.EdgeType.DEPENDS_ON, "supabase-function", fn);
|
|
7768
|
+
if (config.storage) add(import_types29.EdgeType.DEPENDS_ON, "supabase-storage", "storage");
|
|
7769
|
+
if (config.auth) add(import_types29.EdgeType.DEPENDS_ON, "supabase-auth", "auth");
|
|
7770
|
+
}
|
|
7771
|
+
return { nodesAdded, edgesAdded };
|
|
7772
|
+
}
|
|
7773
|
+
|
|
7544
7774
|
// src/extract/infra/index.ts
|
|
7545
7775
|
async function addInfra(graph, scanPath, services) {
|
|
7546
7776
|
const compose = await addComposeInfra(graph, scanPath, services);
|
|
@@ -7548,24 +7778,27 @@ async function addInfra(graph, scanPath, services) {
|
|
|
7548
7778
|
const terraform = await addTerraformResources(graph, scanPath);
|
|
7549
7779
|
const k8s = await addK8sResources(graph, scanPath);
|
|
7550
7780
|
const cloudflare = await addCloudflareWorkers(graph, services, scanPath);
|
|
7781
|
+
const vercel = await addVercelServices(graph, services, scanPath);
|
|
7782
|
+
const railway = await addRailwayServices(graph, services, scanPath);
|
|
7783
|
+
const supabase = await addSupabaseProjects(graph, services, scanPath);
|
|
7551
7784
|
return {
|
|
7552
|
-
nodesAdded: compose.nodesAdded + dockerfile.nodesAdded + terraform.nodesAdded + k8s.nodesAdded + cloudflare.nodesAdded,
|
|
7553
|
-
edgesAdded: compose.edgesAdded + dockerfile.edgesAdded + terraform.edgesAdded + k8s.edgesAdded + cloudflare.edgesAdded
|
|
7785
|
+
nodesAdded: compose.nodesAdded + dockerfile.nodesAdded + terraform.nodesAdded + k8s.nodesAdded + cloudflare.nodesAdded + vercel.nodesAdded + railway.nodesAdded + supabase.nodesAdded,
|
|
7786
|
+
edgesAdded: compose.edgesAdded + dockerfile.edgesAdded + terraform.edgesAdded + k8s.edgesAdded + cloudflare.edgesAdded + vercel.edgesAdded + railway.edgesAdded + supabase.edgesAdded
|
|
7554
7787
|
};
|
|
7555
7788
|
}
|
|
7556
7789
|
|
|
7557
7790
|
// src/extract/index.ts
|
|
7558
|
-
var
|
|
7791
|
+
var import_node_path42 = __toESM(require("path"), 1);
|
|
7559
7792
|
|
|
7560
7793
|
// src/extract/retire.ts
|
|
7561
7794
|
init_cjs_shims();
|
|
7562
|
-
var
|
|
7563
|
-
var
|
|
7564
|
-
var
|
|
7795
|
+
var import_node_fs25 = require("fs");
|
|
7796
|
+
var import_node_path41 = __toESM(require("path"), 1);
|
|
7797
|
+
var import_types30 = require("@neat.is/types");
|
|
7565
7798
|
function dropOrphanedFileNodes(graph) {
|
|
7566
7799
|
const orphans = [];
|
|
7567
7800
|
graph.forEachNode((id, attrs) => {
|
|
7568
|
-
if (attrs.type !==
|
|
7801
|
+
if (attrs.type !== import_types30.NodeType.FileNode) return;
|
|
7569
7802
|
if (graph.inboundEdges(id).length === 0 && graph.outboundEdges(id).length === 0) {
|
|
7570
7803
|
orphans.push(id);
|
|
7571
7804
|
}
|
|
@@ -7578,14 +7811,14 @@ function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = []) {
|
|
|
7578
7811
|
const bases = [scanPath, ...serviceDirs];
|
|
7579
7812
|
graph.forEachEdge((id, attrs) => {
|
|
7580
7813
|
const edge = attrs;
|
|
7581
|
-
if (edge.provenance !==
|
|
7814
|
+
if (edge.provenance !== import_types30.Provenance.EXTRACTED) return;
|
|
7582
7815
|
const evidenceFile = edge.evidence?.file;
|
|
7583
7816
|
if (!evidenceFile) return;
|
|
7584
|
-
if (
|
|
7585
|
-
if (!(0,
|
|
7817
|
+
if (import_node_path41.default.isAbsolute(evidenceFile)) {
|
|
7818
|
+
if (!(0, import_node_fs25.existsSync)(evidenceFile)) toDrop.push(id);
|
|
7586
7819
|
return;
|
|
7587
7820
|
}
|
|
7588
|
-
const found = bases.some((base) => (0,
|
|
7821
|
+
const found = bases.some((base) => (0, import_node_fs25.existsSync)(import_node_path41.default.join(base, evidenceFile)));
|
|
7589
7822
|
if (!found) toDrop.push(id);
|
|
7590
7823
|
});
|
|
7591
7824
|
for (const id of toDrop) graph.dropEdge(id);
|
|
@@ -7627,7 +7860,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
7627
7860
|
}
|
|
7628
7861
|
const droppedEntries = drainDroppedExtracted();
|
|
7629
7862
|
if (isRejectedLogEnabled() && opts.errorsPath && droppedEntries.length > 0) {
|
|
7630
|
-
const rejectedPath =
|
|
7863
|
+
const rejectedPath = import_node_path42.default.join(import_node_path42.default.dirname(opts.errorsPath), "rejected.ndjson");
|
|
7631
7864
|
try {
|
|
7632
7865
|
await writeRejectedExtracted(droppedEntries, rejectedPath);
|
|
7633
7866
|
} catch (err) {
|
|
@@ -7661,7 +7894,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
7661
7894
|
|
|
7662
7895
|
// src/diff.ts
|
|
7663
7896
|
init_cjs_shims();
|
|
7664
|
-
var
|
|
7897
|
+
var import_node_fs26 = require("fs");
|
|
7665
7898
|
async function loadSnapshotForDiff(target) {
|
|
7666
7899
|
if (/^https?:\/\//i.test(target)) {
|
|
7667
7900
|
const res = await fetch(target);
|
|
@@ -7670,7 +7903,7 @@ async function loadSnapshotForDiff(target) {
|
|
|
7670
7903
|
}
|
|
7671
7904
|
return await res.json();
|
|
7672
7905
|
}
|
|
7673
|
-
const raw = await
|
|
7906
|
+
const raw = await import_node_fs26.promises.readFile(target, "utf8");
|
|
7674
7907
|
return JSON.parse(raw);
|
|
7675
7908
|
}
|
|
7676
7909
|
function indexEntries(entries) {
|
|
@@ -7738,9 +7971,9 @@ function canonicalJson(value) {
|
|
|
7738
7971
|
|
|
7739
7972
|
// src/persist.ts
|
|
7740
7973
|
init_cjs_shims();
|
|
7741
|
-
var
|
|
7742
|
-
var
|
|
7743
|
-
var
|
|
7974
|
+
var import_node_fs27 = require("fs");
|
|
7975
|
+
var import_node_path43 = __toESM(require("path"), 1);
|
|
7976
|
+
var import_types31 = require("@neat.is/types");
|
|
7744
7977
|
var SCHEMA_VERSION = 4;
|
|
7745
7978
|
function migrateV1ToV2(payload) {
|
|
7746
7979
|
const nodes = payload.graph.nodes;
|
|
@@ -7762,12 +7995,12 @@ function migrateV2ToV3(payload) {
|
|
|
7762
7995
|
for (const edge of edges) {
|
|
7763
7996
|
const attrs = edge.attributes;
|
|
7764
7997
|
if (!attrs || attrs.provenance !== "FRONTIER") continue;
|
|
7765
|
-
attrs.provenance =
|
|
7998
|
+
attrs.provenance = import_types31.Provenance.OBSERVED;
|
|
7766
7999
|
const type = typeof attrs.type === "string" ? attrs.type : void 0;
|
|
7767
8000
|
const source = typeof attrs.source === "string" ? attrs.source : void 0;
|
|
7768
8001
|
const target = typeof attrs.target === "string" ? attrs.target : void 0;
|
|
7769
8002
|
if (type && source && target) {
|
|
7770
|
-
const newId = (0,
|
|
8003
|
+
const newId = (0, import_types31.observedEdgeId)(source, target, type);
|
|
7771
8004
|
attrs.id = newId;
|
|
7772
8005
|
if (edge.key) edge.key = newId;
|
|
7773
8006
|
}
|
|
@@ -7776,7 +8009,7 @@ function migrateV2ToV3(payload) {
|
|
|
7776
8009
|
return { ...payload, schemaVersion: 3 };
|
|
7777
8010
|
}
|
|
7778
8011
|
async function ensureDir(filePath) {
|
|
7779
|
-
await
|
|
8012
|
+
await import_node_fs27.promises.mkdir(import_node_path43.default.dirname(filePath), { recursive: true });
|
|
7780
8013
|
}
|
|
7781
8014
|
async function saveGraphToDisk(graph, outPath) {
|
|
7782
8015
|
await ensureDir(outPath);
|
|
@@ -7786,13 +8019,13 @@ async function saveGraphToDisk(graph, outPath) {
|
|
|
7786
8019
|
graph: graph.export()
|
|
7787
8020
|
};
|
|
7788
8021
|
const tmp = `${outPath}.tmp`;
|
|
7789
|
-
await
|
|
7790
|
-
await
|
|
8022
|
+
await import_node_fs27.promises.writeFile(tmp, JSON.stringify(payload), "utf8");
|
|
8023
|
+
await import_node_fs27.promises.rename(tmp, outPath);
|
|
7791
8024
|
}
|
|
7792
8025
|
async function loadGraphFromDisk(graph, outPath) {
|
|
7793
8026
|
let raw;
|
|
7794
8027
|
try {
|
|
7795
|
-
raw = await
|
|
8028
|
+
raw = await import_node_fs27.promises.readFile(outPath, "utf8");
|
|
7796
8029
|
} catch (err) {
|
|
7797
8030
|
if (err.code === "ENOENT") return;
|
|
7798
8031
|
throw err;
|
|
@@ -7857,23 +8090,23 @@ function startPersistLoop(graph, outPath, opts = {}) {
|
|
|
7857
8090
|
|
|
7858
8091
|
// src/projects.ts
|
|
7859
8092
|
init_cjs_shims();
|
|
7860
|
-
var
|
|
8093
|
+
var import_node_path44 = __toESM(require("path"), 1);
|
|
7861
8094
|
function pathsForProject(project, baseDir) {
|
|
7862
8095
|
if (project === DEFAULT_PROJECT) {
|
|
7863
8096
|
return {
|
|
7864
|
-
snapshotPath:
|
|
7865
|
-
errorsPath:
|
|
7866
|
-
staleEventsPath:
|
|
7867
|
-
embeddingsCachePath:
|
|
7868
|
-
policyViolationsPath:
|
|
8097
|
+
snapshotPath: import_node_path44.default.join(baseDir, "graph.json"),
|
|
8098
|
+
errorsPath: import_node_path44.default.join(baseDir, "errors.ndjson"),
|
|
8099
|
+
staleEventsPath: import_node_path44.default.join(baseDir, "stale-events.ndjson"),
|
|
8100
|
+
embeddingsCachePath: import_node_path44.default.join(baseDir, "embeddings.json"),
|
|
8101
|
+
policyViolationsPath: import_node_path44.default.join(baseDir, "policy-violations.ndjson")
|
|
7869
8102
|
};
|
|
7870
8103
|
}
|
|
7871
8104
|
return {
|
|
7872
|
-
snapshotPath:
|
|
7873
|
-
errorsPath:
|
|
7874
|
-
staleEventsPath:
|
|
7875
|
-
embeddingsCachePath:
|
|
7876
|
-
policyViolationsPath:
|
|
8105
|
+
snapshotPath: import_node_path44.default.join(baseDir, `${project}.json`),
|
|
8106
|
+
errorsPath: import_node_path44.default.join(baseDir, `errors.${project}.ndjson`),
|
|
8107
|
+
staleEventsPath: import_node_path44.default.join(baseDir, `stale-events.${project}.ndjson`),
|
|
8108
|
+
embeddingsCachePath: import_node_path44.default.join(baseDir, `embeddings.${project}.json`),
|
|
8109
|
+
policyViolationsPath: import_node_path44.default.join(baseDir, `policy-violations.${project}.ndjson`)
|
|
7877
8110
|
};
|
|
7878
8111
|
}
|
|
7879
8112
|
var Projects = class {
|
|
@@ -7913,23 +8146,23 @@ function parseExtraProjects(raw) {
|
|
|
7913
8146
|
|
|
7914
8147
|
// src/registry.ts
|
|
7915
8148
|
init_cjs_shims();
|
|
7916
|
-
var
|
|
8149
|
+
var import_node_fs28 = require("fs");
|
|
7917
8150
|
var import_node_os3 = __toESM(require("os"), 1);
|
|
7918
|
-
var
|
|
7919
|
-
var
|
|
8151
|
+
var import_node_path45 = __toESM(require("path"), 1);
|
|
8152
|
+
var import_types32 = require("@neat.is/types");
|
|
7920
8153
|
function neatHome() {
|
|
7921
8154
|
const override = process.env.NEAT_HOME;
|
|
7922
|
-
if (override && override.length > 0) return
|
|
7923
|
-
return
|
|
8155
|
+
if (override && override.length > 0) return import_node_path45.default.resolve(override);
|
|
8156
|
+
return import_node_path45.default.join(import_node_os3.default.homedir(), ".neat");
|
|
7924
8157
|
}
|
|
7925
8158
|
function registryPath() {
|
|
7926
|
-
return
|
|
8159
|
+
return import_node_path45.default.join(neatHome(), "projects.json");
|
|
7927
8160
|
}
|
|
7928
8161
|
async function readRegistry() {
|
|
7929
8162
|
const file = registryPath();
|
|
7930
8163
|
let raw;
|
|
7931
8164
|
try {
|
|
7932
|
-
raw = await
|
|
8165
|
+
raw = await import_node_fs28.promises.readFile(file, "utf8");
|
|
7933
8166
|
} catch (err) {
|
|
7934
8167
|
if (err.code === "ENOENT") {
|
|
7935
8168
|
return { version: 1, projects: [] };
|
|
@@ -7937,7 +8170,7 @@ async function readRegistry() {
|
|
|
7937
8170
|
throw err;
|
|
7938
8171
|
}
|
|
7939
8172
|
const parsed = JSON.parse(raw);
|
|
7940
|
-
return
|
|
8173
|
+
return import_types32.RegistryFileSchema.parse(parsed);
|
|
7941
8174
|
}
|
|
7942
8175
|
async function getProject(name) {
|
|
7943
8176
|
const reg = await readRegistry();
|
|
@@ -8008,6 +8241,168 @@ data: ${JSON.stringify(envelope.payload)}
|
|
|
8008
8241
|
|
|
8009
8242
|
// src/api.ts
|
|
8010
8243
|
init_auth();
|
|
8244
|
+
|
|
8245
|
+
// src/connectors-config.ts
|
|
8246
|
+
init_cjs_shims();
|
|
8247
|
+
var import_node_os4 = __toESM(require("os"), 1);
|
|
8248
|
+
var import_node_path46 = __toESM(require("path"), 1);
|
|
8249
|
+
var import_node_fs29 = require("fs");
|
|
8250
|
+
var CONNECTORS_CONFIG_VERSION = 1;
|
|
8251
|
+
function neatHome2() {
|
|
8252
|
+
const override = process.env.NEAT_HOME;
|
|
8253
|
+
if (override && override.length > 0) return import_node_path46.default.resolve(override);
|
|
8254
|
+
return import_node_path46.default.join(import_node_os4.default.homedir(), ".neat");
|
|
8255
|
+
}
|
|
8256
|
+
function connectorsConfigPath(home = neatHome2()) {
|
|
8257
|
+
return import_node_path46.default.join(home, "connectors.json");
|
|
8258
|
+
}
|
|
8259
|
+
var MODE_MASK_LOOSER_THAN_0600 = 63;
|
|
8260
|
+
async function warnIfModeLooserThan0600(file) {
|
|
8261
|
+
if (process.platform === "win32") return;
|
|
8262
|
+
try {
|
|
8263
|
+
const stat = await import_node_fs29.promises.stat(file);
|
|
8264
|
+
if ((stat.mode & MODE_MASK_LOOSER_THAN_0600) !== 0) {
|
|
8265
|
+
const mode = (stat.mode & 511).toString(8).padStart(3, "0");
|
|
8266
|
+
console.warn(
|
|
8267
|
+
`[neat] ${file} is mode 0${mode}, looser than the 0600 this file's secrets call for \u2014 run \`chmod 600 ${file}\``
|
|
8268
|
+
);
|
|
8269
|
+
}
|
|
8270
|
+
} catch {
|
|
8271
|
+
}
|
|
8272
|
+
}
|
|
8273
|
+
async function readConnectorsConfig(home = neatHome2()) {
|
|
8274
|
+
const file = connectorsConfigPath(home);
|
|
8275
|
+
let raw;
|
|
8276
|
+
try {
|
|
8277
|
+
raw = await import_node_fs29.promises.readFile(file, "utf8");
|
|
8278
|
+
} catch (err) {
|
|
8279
|
+
if (err.code === "ENOENT") {
|
|
8280
|
+
return { version: CONNECTORS_CONFIG_VERSION, connectors: [] };
|
|
8281
|
+
}
|
|
8282
|
+
throw err;
|
|
8283
|
+
}
|
|
8284
|
+
await warnIfModeLooserThan0600(file);
|
|
8285
|
+
let parsed;
|
|
8286
|
+
try {
|
|
8287
|
+
parsed = JSON.parse(raw);
|
|
8288
|
+
} catch (err) {
|
|
8289
|
+
throw new Error(`${file} is not valid JSON: ${err.message}`);
|
|
8290
|
+
}
|
|
8291
|
+
return validateConfig(parsed, file);
|
|
8292
|
+
}
|
|
8293
|
+
function validateConfig(parsed, file) {
|
|
8294
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
8295
|
+
throw new Error(`${file} must be a JSON object with a "connectors" array`);
|
|
8296
|
+
}
|
|
8297
|
+
const obj = parsed;
|
|
8298
|
+
const version = obj.version === void 0 ? CONNECTORS_CONFIG_VERSION : obj.version;
|
|
8299
|
+
if (typeof version !== "number" || !Number.isInteger(version)) {
|
|
8300
|
+
throw new Error(`${file}: "version" must be an integer`);
|
|
8301
|
+
}
|
|
8302
|
+
const rawConnectors = obj.connectors;
|
|
8303
|
+
if (!Array.isArray(rawConnectors)) {
|
|
8304
|
+
throw new Error(`${file}: "connectors" must be an array`);
|
|
8305
|
+
}
|
|
8306
|
+
const connectors = rawConnectors.map((entry, i) => validateEntry(entry, i, file));
|
|
8307
|
+
return { version, connectors };
|
|
8308
|
+
}
|
|
8309
|
+
function validateEntry(entry, index, file) {
|
|
8310
|
+
const where = `${file}: connectors[${index}]`;
|
|
8311
|
+
if (typeof entry !== "object" || entry === null || Array.isArray(entry)) {
|
|
8312
|
+
throw new Error(`${where} must be an object`);
|
|
8313
|
+
}
|
|
8314
|
+
const e = entry;
|
|
8315
|
+
const id = e.id;
|
|
8316
|
+
if (typeof id !== "string" || id.length === 0) {
|
|
8317
|
+
throw new Error(`${where}.id must be a non-empty string`);
|
|
8318
|
+
}
|
|
8319
|
+
const provider = e.provider;
|
|
8320
|
+
if (typeof provider !== "string" || provider.length === 0) {
|
|
8321
|
+
throw new Error(`${where}.provider must be a non-empty string`);
|
|
8322
|
+
}
|
|
8323
|
+
if (e.project !== void 0 && typeof e.project !== "string") {
|
|
8324
|
+
throw new Error(`${where}.project must be a string when present`);
|
|
8325
|
+
}
|
|
8326
|
+
const credential = validateCredentialRef(e.credential, `${where}.credential`);
|
|
8327
|
+
let options;
|
|
8328
|
+
if (e.options !== void 0) {
|
|
8329
|
+
if (typeof e.options !== "object" || e.options === null || Array.isArray(e.options)) {
|
|
8330
|
+
throw new Error(`${where}.options must be an object when present`);
|
|
8331
|
+
}
|
|
8332
|
+
options = e.options;
|
|
8333
|
+
}
|
|
8334
|
+
return {
|
|
8335
|
+
id,
|
|
8336
|
+
provider,
|
|
8337
|
+
...typeof e.project === "string" ? { project: e.project } : {},
|
|
8338
|
+
credential,
|
|
8339
|
+
...options ? { options } : {}
|
|
8340
|
+
};
|
|
8341
|
+
}
|
|
8342
|
+
function validateCredentialRef(raw, where) {
|
|
8343
|
+
if (typeof raw === "string") {
|
|
8344
|
+
if (raw.length === 0) throw new Error(`${where} must be a non-empty string`);
|
|
8345
|
+
return raw;
|
|
8346
|
+
}
|
|
8347
|
+
if (typeof raw === "object" && raw !== null && !Array.isArray(raw)) {
|
|
8348
|
+
const fields = raw;
|
|
8349
|
+
const keys = Object.keys(fields);
|
|
8350
|
+
if (keys.length === 0) throw new Error(`${where} object must have at least one field`);
|
|
8351
|
+
for (const key of keys) {
|
|
8352
|
+
const v = fields[key];
|
|
8353
|
+
if (typeof v !== "string" || v.length === 0) {
|
|
8354
|
+
throw new Error(`${where}.${key} must be a non-empty string`);
|
|
8355
|
+
}
|
|
8356
|
+
}
|
|
8357
|
+
return fields;
|
|
8358
|
+
}
|
|
8359
|
+
throw new Error(`${where} must be a string or an object of strings`);
|
|
8360
|
+
}
|
|
8361
|
+
function connectorMatchesProject(entry, project) {
|
|
8362
|
+
return entry.project === void 0 || entry.project === project;
|
|
8363
|
+
}
|
|
8364
|
+
function isEnvRef(value) {
|
|
8365
|
+
return value.length > 1 && value.startsWith("$");
|
|
8366
|
+
}
|
|
8367
|
+
function redactCredentialRef(ref) {
|
|
8368
|
+
const one = (value) => isEnvRef(value) ? value : "****";
|
|
8369
|
+
if (typeof ref === "string") return one(ref);
|
|
8370
|
+
const out = {};
|
|
8371
|
+
for (const [key, value] of Object.entries(ref)) out[key] = one(value);
|
|
8372
|
+
return out;
|
|
8373
|
+
}
|
|
8374
|
+
|
|
8375
|
+
// src/connectors/status.ts
|
|
8376
|
+
init_cjs_shims();
|
|
8377
|
+
var CONNECTOR_STALE_THRESHOLD_MS = 5 * 6e4;
|
|
8378
|
+
var records = /* @__PURE__ */ new Map();
|
|
8379
|
+
function deriveState(rec, now, thresholdMs) {
|
|
8380
|
+
if (rec.lastOutcome === "error") return "error";
|
|
8381
|
+
const okAt = rec.lastOkAt ? Date.parse(rec.lastOkAt) : NaN;
|
|
8382
|
+
if (!Number.isNaN(okAt) && now - okAt > thresholdMs) return "stale";
|
|
8383
|
+
return "healthy";
|
|
8384
|
+
}
|
|
8385
|
+
function getConnectorStatus(id, now = Date.now(), thresholdMs = CONNECTOR_STALE_THRESHOLD_MS) {
|
|
8386
|
+
const rec = records.get(id);
|
|
8387
|
+
if (!rec) {
|
|
8388
|
+
return {
|
|
8389
|
+
state: "idle",
|
|
8390
|
+
lastPollAt: null,
|
|
8391
|
+
lastOutcome: null,
|
|
8392
|
+
lastError: null,
|
|
8393
|
+
signalsLastPoll: 0
|
|
8394
|
+
};
|
|
8395
|
+
}
|
|
8396
|
+
return {
|
|
8397
|
+
state: deriveState(rec, now, thresholdMs),
|
|
8398
|
+
lastPollAt: rec.lastPollAt,
|
|
8399
|
+
lastOutcome: rec.lastOutcome,
|
|
8400
|
+
lastError: rec.lastError,
|
|
8401
|
+
signalsLastPoll: rec.signalsLastPoll
|
|
8402
|
+
};
|
|
8403
|
+
}
|
|
8404
|
+
|
|
8405
|
+
// src/api.ts
|
|
8011
8406
|
function serializeGraph(graph) {
|
|
8012
8407
|
const nodes = [];
|
|
8013
8408
|
graph.forEachNode((_id, attrs) => {
|
|
@@ -8158,11 +8553,11 @@ function registerRoutes(scope, ctx) {
|
|
|
8158
8553
|
const candidates = req.query.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
8159
8554
|
const parsed = [];
|
|
8160
8555
|
for (const c of candidates) {
|
|
8161
|
-
const r =
|
|
8556
|
+
const r = import_types33.DivergenceTypeSchema.safeParse(c);
|
|
8162
8557
|
if (!r.success) {
|
|
8163
8558
|
return reply.code(400).send({
|
|
8164
8559
|
error: `unknown divergence type "${c}"`,
|
|
8165
|
-
allowed:
|
|
8560
|
+
allowed: import_types33.DivergenceTypeSchema.options
|
|
8166
8561
|
});
|
|
8167
8562
|
}
|
|
8168
8563
|
parsed.push(r.data);
|
|
@@ -8227,6 +8622,26 @@ function registerRoutes(scope, ctx) {
|
|
|
8227
8622
|
const sliced = filtered.slice(0, safeLimit);
|
|
8228
8623
|
return { count: sliced.length, total, logs: sliced };
|
|
8229
8624
|
});
|
|
8625
|
+
scope.get("/connectors", async (req, reply) => {
|
|
8626
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
|
|
8627
|
+
if (!proj) return;
|
|
8628
|
+
let entries;
|
|
8629
|
+
try {
|
|
8630
|
+
entries = (await readConnectorsConfig(ctx.connectorsHome)).connectors;
|
|
8631
|
+
} catch (err) {
|
|
8632
|
+
return reply.code(500).send({
|
|
8633
|
+
error: "connectors.json failed to read",
|
|
8634
|
+
details: err.message
|
|
8635
|
+
});
|
|
8636
|
+
}
|
|
8637
|
+
const connectors = entries.filter((entry) => connectorMatchesProject(entry, proj.name)).map((entry) => ({
|
|
8638
|
+
id: entry.id,
|
|
8639
|
+
provider: entry.provider,
|
|
8640
|
+
credentialRef: redactCredentialRef(entry.credential),
|
|
8641
|
+
status: getConnectorStatus(entry.id)
|
|
8642
|
+
}));
|
|
8643
|
+
return { connectors };
|
|
8644
|
+
});
|
|
8230
8645
|
const incidentHistoryHandler = async (req, reply) => {
|
|
8231
8646
|
const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
|
|
8232
8647
|
if (!proj) return;
|
|
@@ -8413,7 +8828,7 @@ function registerRoutes(scope, ctx) {
|
|
|
8413
8828
|
const log = new PolicyViolationsLog(proj.paths.policyViolationsPath);
|
|
8414
8829
|
let violations = await log.readAll();
|
|
8415
8830
|
if (req.query.severity) {
|
|
8416
|
-
const sev =
|
|
8831
|
+
const sev = import_types33.PolicySeveritySchema.safeParse(req.query.severity);
|
|
8417
8832
|
if (!sev.success) {
|
|
8418
8833
|
return reply.code(400).send({
|
|
8419
8834
|
error: "invalid severity",
|
|
@@ -8452,7 +8867,7 @@ function registerRoutes(scope, ctx) {
|
|
|
8452
8867
|
scope.post("/policies/check", async (req, reply) => {
|
|
8453
8868
|
const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
|
|
8454
8869
|
if (!proj) return;
|
|
8455
|
-
const parsed =
|
|
8870
|
+
const parsed = import_types33.PoliciesCheckBodySchema.safeParse(req.body ?? {});
|
|
8456
8871
|
if (!parsed.success) {
|
|
8457
8872
|
return reply.code(400).send({
|
|
8458
8873
|
error: "invalid /policies/check body",
|
|
@@ -8597,9 +9012,11 @@ async function buildApi(opts) {
|
|
|
8597
9012
|
trustProxy,
|
|
8598
9013
|
publicRead
|
|
8599
9014
|
});
|
|
9015
|
+
const requiresAuth = authToken !== void 0 && authToken.length > 0 && trustProxy !== true;
|
|
8600
9016
|
app.get("/api/config", async () => ({
|
|
8601
9017
|
publicRead: publicRead === true,
|
|
8602
|
-
authProxy: trustProxy === true
|
|
9018
|
+
authProxy: trustProxy === true,
|
|
9019
|
+
requiresAuth
|
|
8603
9020
|
}));
|
|
8604
9021
|
const startedAt = opts.startedAt ?? Date.now();
|
|
8605
9022
|
const registry = buildLegacyRegistry(opts);
|
|
@@ -8629,7 +9046,8 @@ async function buildApi(opts) {
|
|
|
8629
9046
|
staleEventsPathFor,
|
|
8630
9047
|
policyFilePathFor,
|
|
8631
9048
|
bootstrap: opts.bootstrap,
|
|
8632
|
-
singleProject: opts.singleProject?.name
|
|
9049
|
+
singleProject: opts.singleProject?.name,
|
|
9050
|
+
connectorsHome: opts.connectorsHome
|
|
8633
9051
|
};
|
|
8634
9052
|
app.get("/health", async () => {
|
|
8635
9053
|
const uptimeMs = Date.now() - startedAt;
|
|
@@ -8714,8 +9132,8 @@ init_otel_grpc();
|
|
|
8714
9132
|
|
|
8715
9133
|
// src/search.ts
|
|
8716
9134
|
init_cjs_shims();
|
|
8717
|
-
var
|
|
8718
|
-
var
|
|
9135
|
+
var import_node_fs30 = require("fs");
|
|
9136
|
+
var import_node_path49 = __toESM(require("path"), 1);
|
|
8719
9137
|
var import_node_crypto3 = require("crypto");
|
|
8720
9138
|
var DEFAULT_LIMIT = 10;
|
|
8721
9139
|
var NOMIC_DIM = 768;
|
|
@@ -8869,7 +9287,7 @@ async function pickEmbedder() {
|
|
|
8869
9287
|
}
|
|
8870
9288
|
async function readCache(cachePath) {
|
|
8871
9289
|
try {
|
|
8872
|
-
const raw = await
|
|
9290
|
+
const raw = await import_node_fs30.promises.readFile(cachePath, "utf8");
|
|
8873
9291
|
const parsed = JSON.parse(raw);
|
|
8874
9292
|
if (parsed.version !== 1) return null;
|
|
8875
9293
|
return parsed;
|
|
@@ -8878,8 +9296,8 @@ async function readCache(cachePath) {
|
|
|
8878
9296
|
}
|
|
8879
9297
|
}
|
|
8880
9298
|
async function writeCache(cachePath, cache) {
|
|
8881
|
-
await
|
|
8882
|
-
await
|
|
9299
|
+
await import_node_fs30.promises.mkdir(import_node_path49.default.dirname(cachePath), { recursive: true });
|
|
9300
|
+
await import_node_fs30.promises.writeFile(cachePath, JSON.stringify(cache));
|
|
8883
9301
|
}
|
|
8884
9302
|
var VectorIndex = class {
|
|
8885
9303
|
constructor(embedder, cachePath) {
|
|
@@ -9059,14 +9477,14 @@ async function bootProject(registry, name, scanPath, baseDir) {
|
|
|
9059
9477
|
async function main() {
|
|
9060
9478
|
const baseDirEnv = process.env.NEAT_OUT_DIR;
|
|
9061
9479
|
const legacyOutPath = process.env.NEAT_OUT_PATH;
|
|
9062
|
-
const baseDir = baseDirEnv ?
|
|
9063
|
-
const defaultScanPath =
|
|
9480
|
+
const baseDir = baseDirEnv ? import_node_path50.default.resolve(baseDirEnv) : legacyOutPath ? import_node_path50.default.resolve(import_node_path50.default.dirname(legacyOutPath)) : import_node_path50.default.resolve("./neat-out");
|
|
9481
|
+
const defaultScanPath = import_node_path50.default.resolve(process.env.NEAT_SCAN_PATH ?? "./demo");
|
|
9064
9482
|
const registry = new Projects();
|
|
9065
9483
|
await bootProject(registry, DEFAULT_PROJECT, defaultScanPath, baseDir);
|
|
9066
9484
|
for (const name of parseExtraProjects(process.env.NEAT_PROJECTS)) {
|
|
9067
9485
|
const envKey = `NEAT_PROJECT_SCAN_PATH_${name.toUpperCase().replace(/[^A-Z0-9]/g, "_")}`;
|
|
9068
9486
|
const projectScan = process.env[envKey];
|
|
9069
|
-
await bootProject(registry, name, projectScan ?
|
|
9487
|
+
await bootProject(registry, name, projectScan ? import_node_path50.default.resolve(projectScan) : void 0, baseDir);
|
|
9070
9488
|
}
|
|
9071
9489
|
const host = process.env.HOST ?? "0.0.0.0";
|
|
9072
9490
|
const port = Number(process.env.PORT ?? 8080);
|