@neat.is/core 0.4.28-dev.20260712 → 0.4.28-dev.20260713
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-N7HEYLOF.js} +731 -143
- package/dist/chunk-N7HEYLOF.js.map +1 -0
- package/dist/{chunk-UJ6WBLIE.js → chunk-V2HUBS5J.js} +163 -345
- package/dist/chunk-V2HUBS5J.js.map +1 -0
- package/dist/cli.cjs +1174 -813
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +9 -9
- package/dist/index.cjs +784 -380
- 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 +849 -445
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +2 -2
- package/dist/server.cjs +507 -91
- 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/index.cjs
CHANGED
|
@@ -60,8 +60,8 @@ function mountBearerAuth(app, opts) {
|
|
|
60
60
|
]);
|
|
61
61
|
const publicRead = opts.publicRead === true;
|
|
62
62
|
app.addHook("preHandler", (req, reply, done) => {
|
|
63
|
-
const
|
|
64
|
-
if (exactUnauthPaths.has(
|
|
63
|
+
const path51 = (req.url.split("?")[0] ?? "").replace(/\/+$/, "");
|
|
64
|
+
if (exactUnauthPaths.has(path51) || PROJECT_SCOPED_UNAUTH_PATTERN.test(path51)) {
|
|
65
65
|
done();
|
|
66
66
|
return;
|
|
67
67
|
}
|
|
@@ -193,8 +193,8 @@ function reshapeGrpcRequest(req) {
|
|
|
193
193
|
};
|
|
194
194
|
}
|
|
195
195
|
function resolveProtoRoot() {
|
|
196
|
-
const here =
|
|
197
|
-
return
|
|
196
|
+
const here = import_node_path47.default.dirname((0, import_node_url.fileURLToPath)(importMetaUrl));
|
|
197
|
+
return import_node_path47.default.resolve(here, "..", "proto");
|
|
198
198
|
}
|
|
199
199
|
function loadTraceService() {
|
|
200
200
|
const protoRoot = resolveProtoRoot();
|
|
@@ -262,13 +262,13 @@ async function startOtelGrpcReceiver(opts) {
|
|
|
262
262
|
})
|
|
263
263
|
};
|
|
264
264
|
}
|
|
265
|
-
var import_node_url,
|
|
265
|
+
var import_node_url, import_node_path47, import_node_crypto2, grpc, protoLoader;
|
|
266
266
|
var init_otel_grpc = __esm({
|
|
267
267
|
"src/otel-grpc.ts"() {
|
|
268
268
|
"use strict";
|
|
269
269
|
init_cjs_shims();
|
|
270
270
|
import_node_url = require("url");
|
|
271
|
-
|
|
271
|
+
import_node_path47 = __toESM(require("path"), 1);
|
|
272
272
|
import_node_crypto2 = require("crypto");
|
|
273
273
|
grpc = __toESM(require("@grpc/grpc-js"), 1);
|
|
274
274
|
protoLoader = __toESM(require("@grpc/proto-loader"), 1);
|
|
@@ -361,8 +361,8 @@ function websocketChannelPathOf(attrs) {
|
|
|
361
361
|
const v = attrs[key];
|
|
362
362
|
if (typeof v === "string" && v.length > 0) {
|
|
363
363
|
const q = v.indexOf("?");
|
|
364
|
-
const
|
|
365
|
-
if (
|
|
364
|
+
const path51 = q === -1 ? v : v.slice(0, q);
|
|
365
|
+
if (path51.length > 0) return path51;
|
|
366
366
|
}
|
|
367
367
|
}
|
|
368
368
|
return void 0;
|
|
@@ -416,10 +416,10 @@ function parseOtlpRequest(body) {
|
|
|
416
416
|
return out;
|
|
417
417
|
}
|
|
418
418
|
function loadProtoRoot() {
|
|
419
|
-
const here =
|
|
420
|
-
const protoRoot =
|
|
419
|
+
const here = import_node_path48.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
|
|
420
|
+
const protoRoot = import_node_path48.default.resolve(here, "..", "proto");
|
|
421
421
|
const root = new import_protobufjs.default.Root();
|
|
422
|
-
root.resolvePath = (_origin, target) =>
|
|
422
|
+
root.resolvePath = (_origin, target) => import_node_path48.default.resolve(protoRoot, target);
|
|
423
423
|
root.loadSync(
|
|
424
424
|
"opentelemetry/proto/collector/trace/v1/trace_service.proto",
|
|
425
425
|
{ keepCase: true }
|
|
@@ -655,12 +655,12 @@ function logSpanHandler(span) {
|
|
|
655
655
|
`otel: ${span.service} ${span.name} parent=${parent} status=${status2}${db}`
|
|
656
656
|
);
|
|
657
657
|
}
|
|
658
|
-
var
|
|
658
|
+
var import_node_path48, import_node_url2, import_fastify2, import_protobufjs, ENV_ATTR_CANONICAL, ENV_ATTR_COMPAT, ENV_FALLBACK, exportTraceServiceRequestType, exportTraceServiceResponseType, cachedProtobufResponseBody, OTLP_STEP_ATTEMPTS, OTLP_STEP_STRIDE;
|
|
659
659
|
var init_otel = __esm({
|
|
660
660
|
"src/otel.ts"() {
|
|
661
661
|
"use strict";
|
|
662
662
|
init_cjs_shims();
|
|
663
|
-
|
|
663
|
+
import_node_path48 = __toESM(require("path"), 1);
|
|
664
664
|
import_node_url2 = require("url");
|
|
665
665
|
import_fastify2 = __toESM(require("fastify"), 1);
|
|
666
666
|
import_protobufjs = __toESM(require("protobufjs"), 1);
|
|
@@ -1250,19 +1250,19 @@ function confidenceFromMix(edges, now = Date.now()) {
|
|
|
1250
1250
|
function longestIncomingWalk(graph, start, maxDepth) {
|
|
1251
1251
|
let best = { path: [start], edges: [] };
|
|
1252
1252
|
const visited = /* @__PURE__ */ new Set([start]);
|
|
1253
|
-
function step(node,
|
|
1254
|
-
if (
|
|
1255
|
-
best = { path: [...
|
|
1253
|
+
function step(node, path51, edges) {
|
|
1254
|
+
if (path51.length > best.path.length) {
|
|
1255
|
+
best = { path: [...path51], edges: [...edges] };
|
|
1256
1256
|
}
|
|
1257
|
-
if (
|
|
1257
|
+
if (path51.length - 1 >= maxDepth) return;
|
|
1258
1258
|
const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
|
|
1259
1259
|
for (const [srcId, edge] of incoming) {
|
|
1260
1260
|
if (visited.has(srcId)) continue;
|
|
1261
1261
|
visited.add(srcId);
|
|
1262
|
-
|
|
1262
|
+
path51.push(srcId);
|
|
1263
1263
|
edges.push(edge);
|
|
1264
|
-
step(srcId,
|
|
1265
|
-
|
|
1264
|
+
step(srcId, path51, edges);
|
|
1265
|
+
path51.pop();
|
|
1266
1266
|
edges.pop();
|
|
1267
1267
|
visited.delete(srcId);
|
|
1268
1268
|
}
|
|
@@ -1456,26 +1456,26 @@ function dominantFailingCall(graph, serviceId5, visited) {
|
|
|
1456
1456
|
return best;
|
|
1457
1457
|
}
|
|
1458
1458
|
function followFailingCallChain(graph, originServiceId, maxDepth) {
|
|
1459
|
-
const
|
|
1459
|
+
const path51 = [originServiceId];
|
|
1460
1460
|
const edges = [];
|
|
1461
1461
|
const visited = /* @__PURE__ */ new Set([originServiceId]);
|
|
1462
1462
|
let current = originServiceId;
|
|
1463
1463
|
for (let depth = 0; depth < maxDepth; depth++) {
|
|
1464
1464
|
const hop = dominantFailingCall(graph, current, visited);
|
|
1465
1465
|
if (!hop) break;
|
|
1466
|
-
|
|
1466
|
+
path51.push(hop.nextService);
|
|
1467
1467
|
edges.push(hop.edge);
|
|
1468
1468
|
visited.add(hop.nextService);
|
|
1469
1469
|
current = hop.nextService;
|
|
1470
1470
|
}
|
|
1471
1471
|
if (edges.length === 0) return null;
|
|
1472
|
-
return { path:
|
|
1472
|
+
return { path: path51, edges, culprit: current };
|
|
1473
1473
|
}
|
|
1474
1474
|
function crossServiceRootCause(graph, originId, incidents, errorEvent) {
|
|
1475
1475
|
const chain = followFailingCallChain(graph, originId, ROOT_CAUSE_MAX_DEPTH);
|
|
1476
1476
|
if (!chain) return null;
|
|
1477
1477
|
const culprit = chain.culprit;
|
|
1478
|
-
const
|
|
1478
|
+
const path51 = [...chain.path];
|
|
1479
1479
|
const edgeProvenances = chain.edges.map((e) => e.provenance);
|
|
1480
1480
|
const baseConfidence = confidenceFromMix(chain.edges);
|
|
1481
1481
|
const confidence = Math.max(0, Math.min(1, baseConfidence * INCIDENT_ROOT_CAUSE_CONFIDENCE));
|
|
@@ -1483,14 +1483,14 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
|
|
|
1483
1483
|
if (loc) {
|
|
1484
1484
|
let rootCauseNode = culprit;
|
|
1485
1485
|
if (loc.fileNode) {
|
|
1486
|
-
|
|
1486
|
+
path51.push(loc.fileNode);
|
|
1487
1487
|
edgeProvenances.push(import_types.Provenance.OBSERVED);
|
|
1488
1488
|
rootCauseNode = loc.fileNode;
|
|
1489
1489
|
}
|
|
1490
1490
|
return import_types.RootCauseResultSchema.parse({
|
|
1491
1491
|
rootCauseNode,
|
|
1492
1492
|
rootCauseReason: loc.rootCauseReason,
|
|
1493
|
-
traversalPath:
|
|
1493
|
+
traversalPath: path51,
|
|
1494
1494
|
edgeProvenances,
|
|
1495
1495
|
confidence,
|
|
1496
1496
|
...loc.fixRecommendation ? { fixRecommendation: loc.fixRecommendation } : {}
|
|
@@ -1502,7 +1502,7 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
|
|
|
1502
1502
|
return import_types.RootCauseResultSchema.parse({
|
|
1503
1503
|
rootCauseNode: culprit,
|
|
1504
1504
|
rootCauseReason: `${culpritName} is failing downstream calls (${errs} observed error${errs === 1 ? "" : "s"})`,
|
|
1505
|
-
traversalPath:
|
|
1505
|
+
traversalPath: path51,
|
|
1506
1506
|
edgeProvenances,
|
|
1507
1507
|
confidence,
|
|
1508
1508
|
fixRecommendation: `Inspect ${culpritName}'s failing handler`
|
|
@@ -6498,6 +6498,39 @@ function classifyImage(image) {
|
|
|
6498
6498
|
if (last.startsWith("memcached")) return "memcached";
|
|
6499
6499
|
return "container";
|
|
6500
6500
|
}
|
|
6501
|
+
function lineContaining(raw, needle) {
|
|
6502
|
+
if (!needle) return void 0;
|
|
6503
|
+
const idx = raw.indexOf(needle);
|
|
6504
|
+
if (idx === -1) return void 0;
|
|
6505
|
+
let line = 1;
|
|
6506
|
+
for (let i = 0; i < idx; i++) if (raw[i] === "\n") line++;
|
|
6507
|
+
return line;
|
|
6508
|
+
}
|
|
6509
|
+
function emitPlatformResourceEdge(graph, anchorId, edgeType, kind, name, provider, evidenceFile, line) {
|
|
6510
|
+
let nodesAdded = 0;
|
|
6511
|
+
let edgesAdded = 0;
|
|
6512
|
+
const node = makeInfraNode(kind, name, provider);
|
|
6513
|
+
if (!graph.hasNode(node.id)) {
|
|
6514
|
+
graph.addNode(node.id, node);
|
|
6515
|
+
nodesAdded++;
|
|
6516
|
+
}
|
|
6517
|
+
if (node.id === anchorId) return { nodesAdded, edgesAdded };
|
|
6518
|
+
const edgeId = (0, import_types4.extractedEdgeId)(anchorId, node.id, edgeType);
|
|
6519
|
+
if (!graph.hasEdge(edgeId)) {
|
|
6520
|
+
const edge = {
|
|
6521
|
+
id: edgeId,
|
|
6522
|
+
source: anchorId,
|
|
6523
|
+
target: node.id,
|
|
6524
|
+
type: edgeType,
|
|
6525
|
+
provenance: import_types21.Provenance.EXTRACTED,
|
|
6526
|
+
confidence: (0, import_types21.confidenceForExtracted)("structural"),
|
|
6527
|
+
evidence: { file: evidenceFile, ...line !== void 0 ? { line } : {} }
|
|
6528
|
+
};
|
|
6529
|
+
graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
|
|
6530
|
+
edgesAdded++;
|
|
6531
|
+
}
|
|
6532
|
+
return { nodesAdded, edgesAdded };
|
|
6533
|
+
}
|
|
6501
6534
|
|
|
6502
6535
|
// src/extract/infra/docker-compose.ts
|
|
6503
6536
|
function dependsOnList(value) {
|
|
@@ -6857,7 +6890,7 @@ async function readWranglerConfig(dir) {
|
|
|
6857
6890
|
}
|
|
6858
6891
|
return null;
|
|
6859
6892
|
}
|
|
6860
|
-
function
|
|
6893
|
+
function lineContaining2(raw, needle) {
|
|
6861
6894
|
if (!needle) return void 0;
|
|
6862
6895
|
const idx = raw.indexOf(needle);
|
|
6863
6896
|
if (idx === -1) return void 0;
|
|
@@ -6981,7 +7014,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
|
|
|
6981
7014
|
"cloudflare-route",
|
|
6982
7015
|
route,
|
|
6983
7016
|
evidenceFile,
|
|
6984
|
-
|
|
7017
|
+
lineContaining2(raw, route)
|
|
6985
7018
|
);
|
|
6986
7019
|
nodesAdded += result.nodesAdded;
|
|
6987
7020
|
edgesAdded += result.edgesAdded;
|
|
@@ -7005,7 +7038,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
|
|
|
7005
7038
|
group.kind,
|
|
7006
7039
|
name,
|
|
7007
7040
|
evidenceFile,
|
|
7008
|
-
|
|
7041
|
+
lineContaining2(raw, name)
|
|
7009
7042
|
);
|
|
7010
7043
|
nodesAdded += result.nodesAdded;
|
|
7011
7044
|
edgesAdded += result.edgesAdded;
|
|
@@ -7019,7 +7052,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
|
|
|
7019
7052
|
"cloudflare-cron",
|
|
7020
7053
|
cron,
|
|
7021
7054
|
evidenceFile,
|
|
7022
|
-
|
|
7055
|
+
lineContaining2(raw, cron)
|
|
7023
7056
|
);
|
|
7024
7057
|
nodesAdded += result.nodesAdded;
|
|
7025
7058
|
edgesAdded += result.edgesAdded;
|
|
@@ -7032,7 +7065,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
|
|
|
7032
7065
|
"cloudflare-env-var",
|
|
7033
7066
|
varName,
|
|
7034
7067
|
evidenceFile,
|
|
7035
|
-
|
|
7068
|
+
lineContaining2(raw, varName)
|
|
7036
7069
|
);
|
|
7037
7070
|
nodesAdded += result.nodesAdded;
|
|
7038
7071
|
edgesAdded += result.edgesAdded;
|
|
@@ -7050,7 +7083,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
|
|
|
7050
7083
|
type: import_types25.EdgeType.CALLS,
|
|
7051
7084
|
provenance: import_types25.Provenance.EXTRACTED,
|
|
7052
7085
|
confidence: (0, import_types25.confidenceForExtracted)("structural"),
|
|
7053
|
-
evidence: { file: evidenceFile, line:
|
|
7086
|
+
evidence: { file: evidenceFile, line: lineContaining2(raw, svc.service) }
|
|
7054
7087
|
};
|
|
7055
7088
|
graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
|
|
7056
7089
|
edgesAdded++;
|
|
@@ -7064,11 +7097,208 @@ async function addCloudflareWorkers(graph, services, scanPath) {
|
|
|
7064
7097
|
"cloudflare-service-binding",
|
|
7065
7098
|
svc.service,
|
|
7066
7099
|
evidenceFile,
|
|
7067
|
-
|
|
7100
|
+
lineContaining2(raw, svc.service)
|
|
7101
|
+
);
|
|
7102
|
+
nodesAdded += result.nodesAdded;
|
|
7103
|
+
edgesAdded += result.edgesAdded;
|
|
7104
|
+
}
|
|
7105
|
+
}
|
|
7106
|
+
return { nodesAdded, edgesAdded };
|
|
7107
|
+
}
|
|
7108
|
+
|
|
7109
|
+
// src/extract/infra/vercel.ts
|
|
7110
|
+
init_cjs_shims();
|
|
7111
|
+
var import_node_fs20 = require("fs");
|
|
7112
|
+
var import_node_path36 = __toESM(require("path"), 1);
|
|
7113
|
+
var import_types26 = require("@neat.is/types");
|
|
7114
|
+
var VERCEL_CONFIG_FILENAMES = ["vercel.json", "vercel.jsonc"];
|
|
7115
|
+
async function readVercelConfig(dir) {
|
|
7116
|
+
for (const filename of VERCEL_CONFIG_FILENAMES) {
|
|
7117
|
+
const abs = import_node_path36.default.join(dir, filename);
|
|
7118
|
+
if (!await exists(abs)) continue;
|
|
7119
|
+
const raw = await import_node_fs20.promises.readFile(abs, "utf8");
|
|
7120
|
+
const config = JSON.parse(maskCommentsInSource(raw));
|
|
7121
|
+
return { config, relFile: filename, raw };
|
|
7122
|
+
}
|
|
7123
|
+
return null;
|
|
7124
|
+
}
|
|
7125
|
+
async function readLinkedProjectName(dir) {
|
|
7126
|
+
const abs = import_node_path36.default.join(dir, ".vercel", "project.json");
|
|
7127
|
+
if (!await exists(abs)) return void 0;
|
|
7128
|
+
const parsed = JSON.parse(await import_node_fs20.promises.readFile(abs, "utf8"));
|
|
7129
|
+
return typeof parsed.projectName === "string" ? parsed.projectName : void 0;
|
|
7130
|
+
}
|
|
7131
|
+
function routeSource(route) {
|
|
7132
|
+
return route.source ?? route.src;
|
|
7133
|
+
}
|
|
7134
|
+
async function addVercelServices(graph, services, scanPath) {
|
|
7135
|
+
let nodesAdded = 0;
|
|
7136
|
+
let edgesAdded = 0;
|
|
7137
|
+
for (const service of services) {
|
|
7138
|
+
let read = null;
|
|
7139
|
+
let projectName;
|
|
7140
|
+
try {
|
|
7141
|
+
read = await readVercelConfig(service.dir);
|
|
7142
|
+
projectName = await readLinkedProjectName(service.dir);
|
|
7143
|
+
} catch (err) {
|
|
7144
|
+
recordExtractionError("infra vercel", import_node_path36.default.relative(scanPath, service.dir), err);
|
|
7145
|
+
continue;
|
|
7146
|
+
}
|
|
7147
|
+
if (!read && !projectName) continue;
|
|
7148
|
+
const serviceNode = graph.getNodeAttributes(service.node.id);
|
|
7149
|
+
if (serviceNode.platform !== "vercel" || projectName && serviceNode.platformName !== projectName) {
|
|
7150
|
+
const updated = {
|
|
7151
|
+
...serviceNode,
|
|
7152
|
+
platform: "vercel",
|
|
7153
|
+
...projectName ? { platformName: projectName } : {}
|
|
7154
|
+
};
|
|
7155
|
+
graph.replaceNodeAttributes(service.node.id, updated);
|
|
7156
|
+
}
|
|
7157
|
+
const anchorId = service.node.id;
|
|
7158
|
+
if (!read) continue;
|
|
7159
|
+
const { config, relFile, raw } = read;
|
|
7160
|
+
const evidenceFile = toPosix2(import_node_path36.default.relative(scanPath, import_node_path36.default.join(service.dir, relFile)));
|
|
7161
|
+
const add = (edgeType, kind, name) => {
|
|
7162
|
+
if (!name) return;
|
|
7163
|
+
const result = emitPlatformResourceEdge(
|
|
7164
|
+
graph,
|
|
7165
|
+
anchorId,
|
|
7166
|
+
edgeType,
|
|
7167
|
+
kind,
|
|
7168
|
+
name,
|
|
7169
|
+
"vercel",
|
|
7170
|
+
evidenceFile,
|
|
7171
|
+
lineContaining(raw, name)
|
|
7172
|
+
);
|
|
7173
|
+
nodesAdded += result.nodesAdded;
|
|
7174
|
+
edgesAdded += result.edgesAdded;
|
|
7175
|
+
};
|
|
7176
|
+
add(import_types26.EdgeType.RUNS_ON, "vercel", "vercel");
|
|
7177
|
+
for (const cron of config.crons ?? []) add(import_types26.EdgeType.DEPENDS_ON, "vercel-cron", cron.path ?? cron.schedule);
|
|
7178
|
+
for (const varName of Object.keys(config.env ?? {})) add(import_types26.EdgeType.DEPENDS_ON, "vercel-env-var", varName);
|
|
7179
|
+
for (const varName of Object.keys(config.build?.env ?? {})) add(import_types26.EdgeType.DEPENDS_ON, "vercel-env-var", varName);
|
|
7180
|
+
for (const route of [...config.rewrites ?? [], ...config.redirects ?? [], ...config.routes ?? []]) {
|
|
7181
|
+
add(import_types26.EdgeType.CONNECTS_TO, "vercel-route", routeSource(route));
|
|
7182
|
+
}
|
|
7183
|
+
}
|
|
7184
|
+
return { nodesAdded, edgesAdded };
|
|
7185
|
+
}
|
|
7186
|
+
|
|
7187
|
+
// src/extract/infra/railway.ts
|
|
7188
|
+
init_cjs_shims();
|
|
7189
|
+
var import_node_fs21 = require("fs");
|
|
7190
|
+
var import_node_path37 = __toESM(require("path"), 1);
|
|
7191
|
+
var import_smol_toml3 = require("smol-toml");
|
|
7192
|
+
var import_types27 = require("@neat.is/types");
|
|
7193
|
+
var RAILWAY_FILENAMES = ["railway.toml", "railway.json", "railway.jsonc"];
|
|
7194
|
+
async function readRailwayConfig(dir) {
|
|
7195
|
+
for (const filename of RAILWAY_FILENAMES) {
|
|
7196
|
+
const abs = import_node_path37.default.join(dir, filename);
|
|
7197
|
+
if (!await exists(abs)) continue;
|
|
7198
|
+
const raw = await import_node_fs21.promises.readFile(abs, "utf8");
|
|
7199
|
+
const config = filename === "railway.toml" ? (0, import_smol_toml3.parse)(raw) : JSON.parse(maskCommentsInSource(raw));
|
|
7200
|
+
return { config, relFile: filename, raw };
|
|
7201
|
+
}
|
|
7202
|
+
return null;
|
|
7203
|
+
}
|
|
7204
|
+
async function addRailwayServices(graph, services, scanPath) {
|
|
7205
|
+
let nodesAdded = 0;
|
|
7206
|
+
let edgesAdded = 0;
|
|
7207
|
+
for (const service of services) {
|
|
7208
|
+
let read = null;
|
|
7209
|
+
try {
|
|
7210
|
+
read = await readRailwayConfig(service.dir);
|
|
7211
|
+
} catch (err) {
|
|
7212
|
+
recordExtractionError("infra railway", import_node_path37.default.relative(scanPath, service.dir), err);
|
|
7213
|
+
continue;
|
|
7214
|
+
}
|
|
7215
|
+
if (!read) continue;
|
|
7216
|
+
const serviceNode = graph.getNodeAttributes(service.node.id);
|
|
7217
|
+
if (serviceNode.platform !== "railway") {
|
|
7218
|
+
graph.replaceNodeAttributes(service.node.id, { ...serviceNode, platform: "railway" });
|
|
7219
|
+
}
|
|
7220
|
+
const anchorId = service.node.id;
|
|
7221
|
+
const { config, relFile, raw } = read;
|
|
7222
|
+
const evidenceFile = toPosix2(import_node_path37.default.relative(scanPath, import_node_path37.default.join(service.dir, relFile)));
|
|
7223
|
+
const add = (edgeType, kind, name) => {
|
|
7224
|
+
if (!name) return;
|
|
7225
|
+
const result = emitPlatformResourceEdge(
|
|
7226
|
+
graph,
|
|
7227
|
+
anchorId,
|
|
7228
|
+
edgeType,
|
|
7229
|
+
kind,
|
|
7230
|
+
name,
|
|
7231
|
+
"railway",
|
|
7232
|
+
evidenceFile,
|
|
7233
|
+
lineContaining(raw, name)
|
|
7068
7234
|
);
|
|
7069
7235
|
nodesAdded += result.nodesAdded;
|
|
7070
7236
|
edgesAdded += result.edgesAdded;
|
|
7237
|
+
};
|
|
7238
|
+
add(import_types27.EdgeType.RUNS_ON, "railway", "railway");
|
|
7239
|
+
add(import_types27.EdgeType.CONNECTS_TO, "railway-route", config.deploy?.healthcheckPath);
|
|
7240
|
+
add(import_types27.EdgeType.DEPENDS_ON, "railway-cron", config.deploy?.cronSchedule);
|
|
7241
|
+
}
|
|
7242
|
+
return { nodesAdded, edgesAdded };
|
|
7243
|
+
}
|
|
7244
|
+
|
|
7245
|
+
// src/extract/infra/supabase.ts
|
|
7246
|
+
init_cjs_shims();
|
|
7247
|
+
var import_node_fs22 = require("fs");
|
|
7248
|
+
var import_node_path38 = __toESM(require("path"), 1);
|
|
7249
|
+
var import_smol_toml4 = require("smol-toml");
|
|
7250
|
+
var import_types28 = require("@neat.is/types");
|
|
7251
|
+
async function readSupabaseConfig(dir) {
|
|
7252
|
+
const relFile = import_node_path38.default.join("supabase", "config.toml");
|
|
7253
|
+
const abs = import_node_path38.default.join(dir, relFile);
|
|
7254
|
+
if (!await exists(abs)) return null;
|
|
7255
|
+
const raw = await import_node_fs22.promises.readFile(abs, "utf8");
|
|
7256
|
+
const config = (0, import_smol_toml4.parse)(raw);
|
|
7257
|
+
return { config, relFile, raw };
|
|
7258
|
+
}
|
|
7259
|
+
async function addSupabaseProjects(graph, services, scanPath) {
|
|
7260
|
+
let nodesAdded = 0;
|
|
7261
|
+
let edgesAdded = 0;
|
|
7262
|
+
for (const service of services) {
|
|
7263
|
+
let read = null;
|
|
7264
|
+
try {
|
|
7265
|
+
read = await readSupabaseConfig(service.dir);
|
|
7266
|
+
} catch (err) {
|
|
7267
|
+
recordExtractionError("infra supabase", import_node_path38.default.relative(scanPath, service.dir), err);
|
|
7268
|
+
continue;
|
|
7269
|
+
}
|
|
7270
|
+
if (!read) continue;
|
|
7271
|
+
const { config, relFile, raw } = read;
|
|
7272
|
+
const projectId = typeof config.project_id === "string" ? config.project_id : void 0;
|
|
7273
|
+
const serviceNode = graph.getNodeAttributes(service.node.id);
|
|
7274
|
+
if (serviceNode.platform !== "supabase" || projectId && serviceNode.platformName !== projectId) {
|
|
7275
|
+
graph.replaceNodeAttributes(service.node.id, {
|
|
7276
|
+
...serviceNode,
|
|
7277
|
+
platform: "supabase",
|
|
7278
|
+
...projectId ? { platformName: projectId } : {}
|
|
7279
|
+
});
|
|
7071
7280
|
}
|
|
7281
|
+
const anchorId = service.node.id;
|
|
7282
|
+
const evidenceFile = toPosix2(import_node_path38.default.relative(scanPath, import_node_path38.default.join(service.dir, relFile)));
|
|
7283
|
+
const add = (edgeType, kind, name) => {
|
|
7284
|
+
if (!name) return;
|
|
7285
|
+
const result = emitPlatformResourceEdge(
|
|
7286
|
+
graph,
|
|
7287
|
+
anchorId,
|
|
7288
|
+
edgeType,
|
|
7289
|
+
kind,
|
|
7290
|
+
name,
|
|
7291
|
+
"supabase",
|
|
7292
|
+
evidenceFile,
|
|
7293
|
+
lineContaining(raw, name)
|
|
7294
|
+
);
|
|
7295
|
+
nodesAdded += result.nodesAdded;
|
|
7296
|
+
edgesAdded += result.edgesAdded;
|
|
7297
|
+
};
|
|
7298
|
+
add(import_types28.EdgeType.RUNS_ON, "supabase", "supabase");
|
|
7299
|
+
for (const fn of Object.keys(config.functions ?? {})) add(import_types28.EdgeType.DEPENDS_ON, "supabase-function", fn);
|
|
7300
|
+
if (config.storage) add(import_types28.EdgeType.DEPENDS_ON, "supabase-storage", "storage");
|
|
7301
|
+
if (config.auth) add(import_types28.EdgeType.DEPENDS_ON, "supabase-auth", "auth");
|
|
7072
7302
|
}
|
|
7073
7303
|
return { nodesAdded, edgesAdded };
|
|
7074
7304
|
}
|
|
@@ -7080,24 +7310,27 @@ async function addInfra(graph, scanPath, services) {
|
|
|
7080
7310
|
const terraform = await addTerraformResources(graph, scanPath);
|
|
7081
7311
|
const k8s = await addK8sResources(graph, scanPath);
|
|
7082
7312
|
const cloudflare = await addCloudflareWorkers(graph, services, scanPath);
|
|
7313
|
+
const vercel = await addVercelServices(graph, services, scanPath);
|
|
7314
|
+
const railway = await addRailwayServices(graph, services, scanPath);
|
|
7315
|
+
const supabase = await addSupabaseProjects(graph, services, scanPath);
|
|
7083
7316
|
return {
|
|
7084
|
-
nodesAdded: compose.nodesAdded + dockerfile.nodesAdded + terraform.nodesAdded + k8s.nodesAdded + cloudflare.nodesAdded,
|
|
7085
|
-
edgesAdded: compose.edgesAdded + dockerfile.edgesAdded + terraform.edgesAdded + k8s.edgesAdded + cloudflare.edgesAdded
|
|
7317
|
+
nodesAdded: compose.nodesAdded + dockerfile.nodesAdded + terraform.nodesAdded + k8s.nodesAdded + cloudflare.nodesAdded + vercel.nodesAdded + railway.nodesAdded + supabase.nodesAdded,
|
|
7318
|
+
edgesAdded: compose.edgesAdded + dockerfile.edgesAdded + terraform.edgesAdded + k8s.edgesAdded + cloudflare.edgesAdded + vercel.edgesAdded + railway.edgesAdded + supabase.edgesAdded
|
|
7086
7319
|
};
|
|
7087
7320
|
}
|
|
7088
7321
|
|
|
7089
7322
|
// src/extract/index.ts
|
|
7090
|
-
var
|
|
7323
|
+
var import_node_path40 = __toESM(require("path"), 1);
|
|
7091
7324
|
|
|
7092
7325
|
// src/extract/retire.ts
|
|
7093
7326
|
init_cjs_shims();
|
|
7094
|
-
var
|
|
7095
|
-
var
|
|
7096
|
-
var
|
|
7327
|
+
var import_node_fs23 = require("fs");
|
|
7328
|
+
var import_node_path39 = __toESM(require("path"), 1);
|
|
7329
|
+
var import_types29 = require("@neat.is/types");
|
|
7097
7330
|
function dropOrphanedFileNodes(graph) {
|
|
7098
7331
|
const orphans = [];
|
|
7099
7332
|
graph.forEachNode((id, attrs) => {
|
|
7100
|
-
if (attrs.type !==
|
|
7333
|
+
if (attrs.type !== import_types29.NodeType.FileNode) return;
|
|
7101
7334
|
if (graph.inboundEdges(id).length === 0 && graph.outboundEdges(id).length === 0) {
|
|
7102
7335
|
orphans.push(id);
|
|
7103
7336
|
}
|
|
@@ -7110,14 +7343,14 @@ function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = []) {
|
|
|
7110
7343
|
const bases = [scanPath, ...serviceDirs];
|
|
7111
7344
|
graph.forEachEdge((id, attrs) => {
|
|
7112
7345
|
const edge = attrs;
|
|
7113
|
-
if (edge.provenance !==
|
|
7346
|
+
if (edge.provenance !== import_types29.Provenance.EXTRACTED) return;
|
|
7114
7347
|
const evidenceFile = edge.evidence?.file;
|
|
7115
7348
|
if (!evidenceFile) return;
|
|
7116
|
-
if (
|
|
7117
|
-
if (!(0,
|
|
7349
|
+
if (import_node_path39.default.isAbsolute(evidenceFile)) {
|
|
7350
|
+
if (!(0, import_node_fs23.existsSync)(evidenceFile)) toDrop.push(id);
|
|
7118
7351
|
return;
|
|
7119
7352
|
}
|
|
7120
|
-
const found = bases.some((base) => (0,
|
|
7353
|
+
const found = bases.some((base) => (0, import_node_fs23.existsSync)(import_node_path39.default.join(base, evidenceFile)));
|
|
7121
7354
|
if (!found) toDrop.push(id);
|
|
7122
7355
|
});
|
|
7123
7356
|
for (const id of toDrop) graph.dropEdge(id);
|
|
@@ -7159,7 +7392,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
7159
7392
|
}
|
|
7160
7393
|
const droppedEntries = drainDroppedExtracted();
|
|
7161
7394
|
if (isRejectedLogEnabled() && opts.errorsPath && droppedEntries.length > 0) {
|
|
7162
|
-
const rejectedPath =
|
|
7395
|
+
const rejectedPath = import_node_path40.default.join(import_node_path40.default.dirname(opts.errorsPath), "rejected.ndjson");
|
|
7163
7396
|
try {
|
|
7164
7397
|
await writeRejectedExtracted(droppedEntries, rejectedPath);
|
|
7165
7398
|
} catch (err) {
|
|
@@ -7193,9 +7426,9 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
7193
7426
|
|
|
7194
7427
|
// src/persist.ts
|
|
7195
7428
|
init_cjs_shims();
|
|
7196
|
-
var
|
|
7197
|
-
var
|
|
7198
|
-
var
|
|
7429
|
+
var import_node_fs24 = require("fs");
|
|
7430
|
+
var import_node_path41 = __toESM(require("path"), 1);
|
|
7431
|
+
var import_types30 = require("@neat.is/types");
|
|
7199
7432
|
var SCHEMA_VERSION = 4;
|
|
7200
7433
|
function migrateV1ToV2(payload) {
|
|
7201
7434
|
const nodes = payload.graph.nodes;
|
|
@@ -7217,12 +7450,12 @@ function migrateV2ToV3(payload) {
|
|
|
7217
7450
|
for (const edge of edges) {
|
|
7218
7451
|
const attrs = edge.attributes;
|
|
7219
7452
|
if (!attrs || attrs.provenance !== "FRONTIER") continue;
|
|
7220
|
-
attrs.provenance =
|
|
7453
|
+
attrs.provenance = import_types30.Provenance.OBSERVED;
|
|
7221
7454
|
const type = typeof attrs.type === "string" ? attrs.type : void 0;
|
|
7222
7455
|
const source = typeof attrs.source === "string" ? attrs.source : void 0;
|
|
7223
7456
|
const target = typeof attrs.target === "string" ? attrs.target : void 0;
|
|
7224
7457
|
if (type && source && target) {
|
|
7225
|
-
const newId = (0,
|
|
7458
|
+
const newId = (0, import_types30.observedEdgeId)(source, target, type);
|
|
7226
7459
|
attrs.id = newId;
|
|
7227
7460
|
if (edge.key) edge.key = newId;
|
|
7228
7461
|
}
|
|
@@ -7231,7 +7464,7 @@ function migrateV2ToV3(payload) {
|
|
|
7231
7464
|
return { ...payload, schemaVersion: 3 };
|
|
7232
7465
|
}
|
|
7233
7466
|
async function ensureDir(filePath) {
|
|
7234
|
-
await
|
|
7467
|
+
await import_node_fs24.promises.mkdir(import_node_path41.default.dirname(filePath), { recursive: true });
|
|
7235
7468
|
}
|
|
7236
7469
|
async function saveGraphToDisk(graph, outPath) {
|
|
7237
7470
|
await ensureDir(outPath);
|
|
@@ -7241,13 +7474,13 @@ async function saveGraphToDisk(graph, outPath) {
|
|
|
7241
7474
|
graph: graph.export()
|
|
7242
7475
|
};
|
|
7243
7476
|
const tmp = `${outPath}.tmp`;
|
|
7244
|
-
await
|
|
7245
|
-
await
|
|
7477
|
+
await import_node_fs24.promises.writeFile(tmp, JSON.stringify(payload), "utf8");
|
|
7478
|
+
await import_node_fs24.promises.rename(tmp, outPath);
|
|
7246
7479
|
}
|
|
7247
7480
|
async function loadGraphFromDisk(graph, outPath) {
|
|
7248
7481
|
let raw;
|
|
7249
7482
|
try {
|
|
7250
|
-
raw = await
|
|
7483
|
+
raw = await import_node_fs24.promises.readFile(outPath, "utf8");
|
|
7251
7484
|
} catch (err) {
|
|
7252
7485
|
if (err.code === "ENOENT") return;
|
|
7253
7486
|
throw err;
|
|
@@ -7314,19 +7547,19 @@ function startPersistLoop(graph, outPath, opts = {}) {
|
|
|
7314
7547
|
init_cjs_shims();
|
|
7315
7548
|
var import_fastify = __toESM(require("fastify"), 1);
|
|
7316
7549
|
var import_cors = __toESM(require("@fastify/cors"), 1);
|
|
7317
|
-
var
|
|
7550
|
+
var import_types33 = require("@neat.is/types");
|
|
7318
7551
|
|
|
7319
7552
|
// src/extend/index.ts
|
|
7320
7553
|
init_cjs_shims();
|
|
7321
|
-
var
|
|
7322
|
-
var
|
|
7554
|
+
var import_node_fs26 = require("fs");
|
|
7555
|
+
var import_node_path43 = __toESM(require("path"), 1);
|
|
7323
7556
|
var import_node_os2 = __toESM(require("os"), 1);
|
|
7324
7557
|
var import_instrumentation_registry = require("@neat.is/instrumentation-registry");
|
|
7325
7558
|
|
|
7326
7559
|
// src/installers/package-manager.ts
|
|
7327
7560
|
init_cjs_shims();
|
|
7328
|
-
var
|
|
7329
|
-
var
|
|
7561
|
+
var import_node_fs25 = require("fs");
|
|
7562
|
+
var import_node_path42 = __toESM(require("path"), 1);
|
|
7330
7563
|
var import_node_child_process = require("child_process");
|
|
7331
7564
|
var LOCKFILE_PRIORITY = [
|
|
7332
7565
|
{ lockfile: "bun.lockb", pm: "bun", args: ["install", "--no-summary"] },
|
|
@@ -7341,29 +7574,29 @@ var LOCKFILE_PRIORITY = [
|
|
|
7341
7574
|
var NPM_FALLBACK_ARGS = ["install", "--no-audit", "--no-fund", "--prefer-offline"];
|
|
7342
7575
|
async function exists2(p) {
|
|
7343
7576
|
try {
|
|
7344
|
-
await
|
|
7577
|
+
await import_node_fs25.promises.access(p);
|
|
7345
7578
|
return true;
|
|
7346
7579
|
} catch {
|
|
7347
7580
|
return false;
|
|
7348
7581
|
}
|
|
7349
7582
|
}
|
|
7350
7583
|
async function detectPackageManager(serviceDir) {
|
|
7351
|
-
let dir =
|
|
7584
|
+
let dir = import_node_path42.default.resolve(serviceDir);
|
|
7352
7585
|
const stops = /* @__PURE__ */ new Set();
|
|
7353
7586
|
for (let i = 0; i < 64; i++) {
|
|
7354
7587
|
if (stops.has(dir)) break;
|
|
7355
7588
|
stops.add(dir);
|
|
7356
7589
|
for (const candidate of LOCKFILE_PRIORITY) {
|
|
7357
|
-
const lockPath =
|
|
7590
|
+
const lockPath = import_node_path42.default.join(dir, candidate.lockfile);
|
|
7358
7591
|
if (await exists2(lockPath)) {
|
|
7359
7592
|
return { pm: candidate.pm, cwd: dir, args: [...candidate.args] };
|
|
7360
7593
|
}
|
|
7361
7594
|
}
|
|
7362
|
-
const parent =
|
|
7595
|
+
const parent = import_node_path42.default.dirname(dir);
|
|
7363
7596
|
if (parent === dir) break;
|
|
7364
7597
|
dir = parent;
|
|
7365
7598
|
}
|
|
7366
|
-
return { pm: "npm", cwd:
|
|
7599
|
+
return { pm: "npm", cwd: import_node_path42.default.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
|
|
7367
7600
|
}
|
|
7368
7601
|
async function runPackageManagerInstall(cmd) {
|
|
7369
7602
|
return new Promise((resolve) => {
|
|
@@ -7405,30 +7638,30 @@ ${err.message}`
|
|
|
7405
7638
|
// src/extend/index.ts
|
|
7406
7639
|
async function fileExists2(p) {
|
|
7407
7640
|
try {
|
|
7408
|
-
await
|
|
7641
|
+
await import_node_fs26.promises.access(p);
|
|
7409
7642
|
return true;
|
|
7410
7643
|
} catch {
|
|
7411
7644
|
return false;
|
|
7412
7645
|
}
|
|
7413
7646
|
}
|
|
7414
7647
|
async function readPackageJson(scanPath) {
|
|
7415
|
-
const pkgPath =
|
|
7416
|
-
const raw = await
|
|
7648
|
+
const pkgPath = import_node_path43.default.join(scanPath, "package.json");
|
|
7649
|
+
const raw = await import_node_fs26.promises.readFile(pkgPath, "utf8");
|
|
7417
7650
|
return JSON.parse(raw);
|
|
7418
7651
|
}
|
|
7419
7652
|
async function findHookFiles(scanPath) {
|
|
7420
|
-
const entries = await
|
|
7653
|
+
const entries = await import_node_fs26.promises.readdir(scanPath);
|
|
7421
7654
|
return entries.filter(
|
|
7422
7655
|
(e) => (e.startsWith("instrumentation") || e.startsWith("otel-init")) && /\.(ts|js)$/.test(e)
|
|
7423
7656
|
).sort();
|
|
7424
7657
|
}
|
|
7425
7658
|
function extendLogPath() {
|
|
7426
|
-
return process.env.NEAT_EXTEND_LOG ??
|
|
7659
|
+
return process.env.NEAT_EXTEND_LOG ?? import_node_path43.default.join(import_node_os2.default.homedir(), ".neat", "extend-log.ndjson");
|
|
7427
7660
|
}
|
|
7428
7661
|
async function appendExtendLog(entry) {
|
|
7429
7662
|
const logPath = extendLogPath();
|
|
7430
|
-
await
|
|
7431
|
-
await
|
|
7663
|
+
await import_node_fs26.promises.mkdir(import_node_path43.default.dirname(logPath), { recursive: true });
|
|
7664
|
+
await import_node_fs26.promises.appendFile(logPath, JSON.stringify(entry) + "\n", "utf8");
|
|
7432
7665
|
}
|
|
7433
7666
|
function splicedContent(fileContent, snippet2) {
|
|
7434
7667
|
if (fileContent.includes("__INSTRUMENTATION_BLOCK__")) {
|
|
@@ -7486,7 +7719,7 @@ function lookupInstrumentation(library, installedVersion) {
|
|
|
7486
7719
|
}
|
|
7487
7720
|
async function describeProjectInstrumentation(ctx) {
|
|
7488
7721
|
const hookFiles = await findHookFiles(ctx.scanPath);
|
|
7489
|
-
const envNeat = await fileExists2(
|
|
7722
|
+
const envNeat = await fileExists2(import_node_path43.default.join(ctx.scanPath, ".env.neat"));
|
|
7490
7723
|
const registryInstrPackages = new Set(
|
|
7491
7724
|
(0, import_instrumentation_registry.list)().map((e) => e.instrumentation_package).filter((p) => !!p)
|
|
7492
7725
|
);
|
|
@@ -7508,31 +7741,31 @@ async function applyExtension(ctx, args, options) {
|
|
|
7508
7741
|
);
|
|
7509
7742
|
}
|
|
7510
7743
|
for (const file of hookFiles) {
|
|
7511
|
-
const content = await
|
|
7744
|
+
const content = await import_node_fs26.promises.readFile(import_node_path43.default.join(ctx.scanPath, file), "utf8");
|
|
7512
7745
|
if (content.includes(args.registration_snippet)) {
|
|
7513
7746
|
return { library: args.library, filesTouched: [], depsAdded: [], installOutput: "", alreadyApplied: true };
|
|
7514
7747
|
}
|
|
7515
7748
|
}
|
|
7516
7749
|
const primaryFile = hookFiles[0];
|
|
7517
|
-
const primaryPath =
|
|
7750
|
+
const primaryPath = import_node_path43.default.join(ctx.scanPath, primaryFile);
|
|
7518
7751
|
const filesTouched = [];
|
|
7519
7752
|
const depsAdded = [];
|
|
7520
|
-
const pkgPath =
|
|
7753
|
+
const pkgPath = import_node_path43.default.join(ctx.scanPath, "package.json");
|
|
7521
7754
|
const pkg = await readPackageJson(ctx.scanPath);
|
|
7522
7755
|
if (!(pkg.dependencies ?? {})[args.instrumentation_package]) {
|
|
7523
7756
|
pkg.dependencies = { ...pkg.dependencies ?? {}, [args.instrumentation_package]: args.version };
|
|
7524
|
-
await
|
|
7757
|
+
await import_node_fs26.promises.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
|
|
7525
7758
|
filesTouched.push("package.json");
|
|
7526
7759
|
depsAdded.push(`${args.instrumentation_package}@${args.version}`);
|
|
7527
7760
|
}
|
|
7528
|
-
const hookContent = await
|
|
7761
|
+
const hookContent = await import_node_fs26.promises.readFile(primaryPath, "utf8");
|
|
7529
7762
|
const patched = splicedContent(hookContent, args.registration_snippet);
|
|
7530
7763
|
if (!patched) {
|
|
7531
7764
|
throw new Error(
|
|
7532
7765
|
`Could not find instrumentation insertion point in ${primaryFile}. Expected __INSTRUMENTATION_BLOCK__, instrumentations.push(, or new NodeSDK(.`
|
|
7533
7766
|
);
|
|
7534
7767
|
}
|
|
7535
|
-
await
|
|
7768
|
+
await import_node_fs26.promises.writeFile(primaryPath, patched, "utf8");
|
|
7536
7769
|
filesTouched.push(primaryFile);
|
|
7537
7770
|
const cmd = await detectPackageManager(ctx.scanPath);
|
|
7538
7771
|
const installer = options?.runInstall ?? runPackageManagerInstall;
|
|
@@ -7563,7 +7796,7 @@ async function dryRunExtension(ctx, args) {
|
|
|
7563
7796
|
};
|
|
7564
7797
|
}
|
|
7565
7798
|
for (const file of hookFiles) {
|
|
7566
|
-
const content = await
|
|
7799
|
+
const content = await import_node_fs26.promises.readFile(import_node_path43.default.join(ctx.scanPath, file), "utf8");
|
|
7567
7800
|
if (content.includes(args.registration_snippet)) {
|
|
7568
7801
|
return {
|
|
7569
7802
|
library: args.library,
|
|
@@ -7585,7 +7818,7 @@ async function dryRunExtension(ctx, args) {
|
|
|
7585
7818
|
depsToAdd.push(`${args.instrumentation_package}@${args.version}`);
|
|
7586
7819
|
filesTouched.push("package.json");
|
|
7587
7820
|
}
|
|
7588
|
-
const hookContent = await
|
|
7821
|
+
const hookContent = await import_node_fs26.promises.readFile(import_node_path43.default.join(ctx.scanPath, primaryFile), "utf8");
|
|
7589
7822
|
const patched = splicedContent(hookContent, args.registration_snippet);
|
|
7590
7823
|
if (patched) {
|
|
7591
7824
|
filesTouched.push(primaryFile);
|
|
@@ -7600,28 +7833,28 @@ async function rollbackExtension(ctx, args) {
|
|
|
7600
7833
|
if (!await fileExists2(logPath)) {
|
|
7601
7834
|
return { undone: false, message: "no apply found for library" };
|
|
7602
7835
|
}
|
|
7603
|
-
const raw = await
|
|
7836
|
+
const raw = await import_node_fs26.promises.readFile(logPath, "utf8");
|
|
7604
7837
|
const entries = raw.trim().split("\n").filter(Boolean).map((line) => JSON.parse(line));
|
|
7605
7838
|
const match = [...entries].reverse().find((e) => e.project === ctx.project && e.library === args.library);
|
|
7606
7839
|
if (!match) {
|
|
7607
7840
|
return { undone: false, message: "no apply found for library" };
|
|
7608
7841
|
}
|
|
7609
|
-
const pkgPath =
|
|
7842
|
+
const pkgPath = import_node_path43.default.join(ctx.scanPath, "package.json");
|
|
7610
7843
|
if (await fileExists2(pkgPath)) {
|
|
7611
7844
|
const pkg = await readPackageJson(ctx.scanPath);
|
|
7612
7845
|
if (pkg.dependencies?.[match.instrumentation_package]) {
|
|
7613
7846
|
const { [match.instrumentation_package]: _removed, ...rest } = pkg.dependencies;
|
|
7614
7847
|
pkg.dependencies = rest;
|
|
7615
|
-
await
|
|
7848
|
+
await import_node_fs26.promises.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
|
|
7616
7849
|
}
|
|
7617
7850
|
}
|
|
7618
7851
|
const hookFiles = await findHookFiles(ctx.scanPath);
|
|
7619
7852
|
for (const file of hookFiles) {
|
|
7620
|
-
const filePath =
|
|
7621
|
-
const content = await
|
|
7853
|
+
const filePath = import_node_path43.default.join(ctx.scanPath, file);
|
|
7854
|
+
const content = await import_node_fs26.promises.readFile(filePath, "utf8");
|
|
7622
7855
|
if (content.includes(match.registration_snippet)) {
|
|
7623
7856
|
const filtered = content.split("\n").filter((line) => !line.includes(match.registration_snippet)).join("\n");
|
|
7624
|
-
await
|
|
7857
|
+
await import_node_fs26.promises.writeFile(filePath, filtered, "utf8");
|
|
7625
7858
|
break;
|
|
7626
7859
|
}
|
|
7627
7860
|
}
|
|
@@ -7633,7 +7866,7 @@ async function rollbackExtension(ctx, args) {
|
|
|
7633
7866
|
|
|
7634
7867
|
// src/divergences.ts
|
|
7635
7868
|
init_cjs_shims();
|
|
7636
|
-
var
|
|
7869
|
+
var import_types31 = require("@neat.is/types");
|
|
7637
7870
|
function bucketKey(source, target, type) {
|
|
7638
7871
|
return `${type}|${source}|${target}`;
|
|
7639
7872
|
}
|
|
@@ -7641,22 +7874,22 @@ function bucketEdges(graph) {
|
|
|
7641
7874
|
const buckets2 = /* @__PURE__ */ new Map();
|
|
7642
7875
|
graph.forEachEdge((id, attrs) => {
|
|
7643
7876
|
const e = attrs;
|
|
7644
|
-
const parsed = (0,
|
|
7877
|
+
const parsed = (0, import_types31.parseEdgeId)(id);
|
|
7645
7878
|
const provenance = parsed?.provenance ?? e.provenance;
|
|
7646
7879
|
const key = bucketKey(e.source, e.target, e.type);
|
|
7647
7880
|
const cur = buckets2.get(key) ?? { source: e.source, target: e.target, type: e.type };
|
|
7648
7881
|
switch (provenance) {
|
|
7649
|
-
case
|
|
7882
|
+
case import_types31.Provenance.EXTRACTED:
|
|
7650
7883
|
cur.extracted = e;
|
|
7651
7884
|
break;
|
|
7652
|
-
case
|
|
7885
|
+
case import_types31.Provenance.OBSERVED:
|
|
7653
7886
|
cur.observed = e;
|
|
7654
7887
|
break;
|
|
7655
|
-
case
|
|
7888
|
+
case import_types31.Provenance.INFERRED:
|
|
7656
7889
|
cur.inferred = e;
|
|
7657
7890
|
break;
|
|
7658
7891
|
default:
|
|
7659
|
-
if (e.provenance ===
|
|
7892
|
+
if (e.provenance === import_types31.Provenance.STALE) cur.stale = e;
|
|
7660
7893
|
}
|
|
7661
7894
|
buckets2.set(key, cur);
|
|
7662
7895
|
});
|
|
@@ -7665,12 +7898,12 @@ function bucketEdges(graph) {
|
|
|
7665
7898
|
function nodeIsFrontier(graph, nodeId) {
|
|
7666
7899
|
if (!graph.hasNode(nodeId)) return false;
|
|
7667
7900
|
const attrs = graph.getNodeAttributes(nodeId);
|
|
7668
|
-
return attrs.type ===
|
|
7901
|
+
return attrs.type === import_types31.NodeType.FrontierNode;
|
|
7669
7902
|
}
|
|
7670
7903
|
function nodeIsWebsocketChannel(graph, nodeId) {
|
|
7671
7904
|
if (!graph.hasNode(nodeId)) return false;
|
|
7672
7905
|
const attrs = graph.getNodeAttributes(nodeId);
|
|
7673
|
-
return attrs.type ===
|
|
7906
|
+
return attrs.type === import_types31.NodeType.WebSocketChannelNode;
|
|
7674
7907
|
}
|
|
7675
7908
|
function clampConfidence(n) {
|
|
7676
7909
|
if (!Number.isFinite(n)) return 0;
|
|
@@ -7690,14 +7923,14 @@ function gradedConfidence(edge) {
|
|
|
7690
7923
|
return clampConfidence(confidenceForEdge(edge));
|
|
7691
7924
|
}
|
|
7692
7925
|
var OBSERVABLE_EDGE_TYPES = /* @__PURE__ */ new Set([
|
|
7693
|
-
|
|
7694
|
-
|
|
7695
|
-
|
|
7696
|
-
|
|
7926
|
+
import_types31.EdgeType.CALLS,
|
|
7927
|
+
import_types31.EdgeType.CONNECTS_TO,
|
|
7928
|
+
import_types31.EdgeType.PUBLISHES_TO,
|
|
7929
|
+
import_types31.EdgeType.CONSUMES_FROM
|
|
7697
7930
|
]);
|
|
7698
7931
|
function detectMissingDivergences(graph, bucket) {
|
|
7699
7932
|
const out = [];
|
|
7700
|
-
if (bucket.type ===
|
|
7933
|
+
if (bucket.type === import_types31.EdgeType.CONTAINS) return out;
|
|
7701
7934
|
if (bucket.extracted && !bucket.observed && OBSERVABLE_EDGE_TYPES.has(bucket.type)) {
|
|
7702
7935
|
if (!nodeIsFrontier(graph, bucket.target)) {
|
|
7703
7936
|
out.push({
|
|
@@ -7738,7 +7971,7 @@ function declaredHostFor(svc) {
|
|
|
7738
7971
|
function hasExtractedConfiguredBy(graph, svcId) {
|
|
7739
7972
|
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
7740
7973
|
const e = graph.getEdgeAttributes(edgeId);
|
|
7741
|
-
if (e.type ===
|
|
7974
|
+
if (e.type === import_types31.EdgeType.CONFIGURED_BY && e.provenance === import_types31.Provenance.EXTRACTED) {
|
|
7742
7975
|
return true;
|
|
7743
7976
|
}
|
|
7744
7977
|
}
|
|
@@ -7751,10 +7984,10 @@ function detectHostMismatch(graph, svcId, svc) {
|
|
|
7751
7984
|
const out = [];
|
|
7752
7985
|
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
7753
7986
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
7754
|
-
if (edge.type !==
|
|
7755
|
-
if (edge.provenance !==
|
|
7987
|
+
if (edge.type !== import_types31.EdgeType.CONNECTS_TO) continue;
|
|
7988
|
+
if (edge.provenance !== import_types31.Provenance.OBSERVED) continue;
|
|
7756
7989
|
const target = graph.getNodeAttributes(edge.target);
|
|
7757
|
-
if (target.type !==
|
|
7990
|
+
if (target.type !== import_types31.NodeType.DatabaseNode) continue;
|
|
7758
7991
|
const observedHost = target.host?.trim();
|
|
7759
7992
|
if (!observedHost) continue;
|
|
7760
7993
|
if (observedHost === declaredHost) continue;
|
|
@@ -7776,10 +8009,10 @@ function detectCompatDivergences(graph, svcId, svc) {
|
|
|
7776
8009
|
const deps = svc.dependencies ?? {};
|
|
7777
8010
|
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
7778
8011
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
7779
|
-
if (edge.type !==
|
|
7780
|
-
if (edge.provenance !==
|
|
8012
|
+
if (edge.type !== import_types31.EdgeType.CONNECTS_TO) continue;
|
|
8013
|
+
if (edge.provenance !== import_types31.Provenance.OBSERVED) continue;
|
|
7781
8014
|
const target = graph.getNodeAttributes(edge.target);
|
|
7782
|
-
if (target.type !==
|
|
8015
|
+
if (target.type !== import_types31.NodeType.DatabaseNode) continue;
|
|
7783
8016
|
for (const pair of compatPairs()) {
|
|
7784
8017
|
if (pair.engine !== target.engine) continue;
|
|
7785
8018
|
const declared = deps[pair.driver];
|
|
@@ -7838,7 +8071,7 @@ function suppressHostMismatchHalves(all) {
|
|
|
7838
8071
|
for (const d of all) {
|
|
7839
8072
|
if (d.type !== "host-mismatch") continue;
|
|
7840
8073
|
observedHalf.add(`${d.source}->${d.target}`);
|
|
7841
|
-
declaredHalf.add((0,
|
|
8074
|
+
declaredHalf.add((0, import_types31.databaseId)(d.extractedHost));
|
|
7842
8075
|
}
|
|
7843
8076
|
if (observedHalf.size === 0) return all;
|
|
7844
8077
|
return all.filter((d) => {
|
|
@@ -7857,7 +8090,7 @@ function computeDivergences(graph, opts = {}) {
|
|
|
7857
8090
|
}
|
|
7858
8091
|
graph.forEachNode((nodeId, attrs) => {
|
|
7859
8092
|
const n = attrs;
|
|
7860
|
-
if (n.type !==
|
|
8093
|
+
if (n.type !== import_types31.NodeType.ServiceNode) return;
|
|
7861
8094
|
const svc = n;
|
|
7862
8095
|
for (const d of detectHostMismatch(graph, nodeId, svc)) all.push(d);
|
|
7863
8096
|
for (const d of detectCompatDivergences(graph, nodeId, svc)) all.push(d);
|
|
@@ -7891,7 +8124,7 @@ function computeDivergences(graph, opts = {}) {
|
|
|
7891
8124
|
if (a.source !== b.source) return a.source.localeCompare(b.source);
|
|
7892
8125
|
return a.target.localeCompare(b.target);
|
|
7893
8126
|
});
|
|
7894
|
-
return
|
|
8127
|
+
return import_types31.DivergenceResultSchema.parse({
|
|
7895
8128
|
divergences: filtered,
|
|
7896
8129
|
totalAffected: filtered.length,
|
|
7897
8130
|
computedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -7945,7 +8178,7 @@ function queryLogEntries(opts) {
|
|
|
7945
8178
|
|
|
7946
8179
|
// src/diff.ts
|
|
7947
8180
|
init_cjs_shims();
|
|
7948
|
-
var
|
|
8181
|
+
var import_node_fs27 = require("fs");
|
|
7949
8182
|
async function loadSnapshotForDiff(target) {
|
|
7950
8183
|
if (/^https?:\/\//i.test(target)) {
|
|
7951
8184
|
const res = await fetch(target);
|
|
@@ -7954,7 +8187,7 @@ async function loadSnapshotForDiff(target) {
|
|
|
7954
8187
|
}
|
|
7955
8188
|
return await res.json();
|
|
7956
8189
|
}
|
|
7957
|
-
const raw = await
|
|
8190
|
+
const raw = await import_node_fs27.promises.readFile(target, "utf8");
|
|
7958
8191
|
return JSON.parse(raw);
|
|
7959
8192
|
}
|
|
7960
8193
|
function indexEntries(entries) {
|
|
@@ -8022,23 +8255,23 @@ function canonicalJson(value) {
|
|
|
8022
8255
|
|
|
8023
8256
|
// src/projects.ts
|
|
8024
8257
|
init_cjs_shims();
|
|
8025
|
-
var
|
|
8258
|
+
var import_node_path44 = __toESM(require("path"), 1);
|
|
8026
8259
|
function pathsForProject(project, baseDir) {
|
|
8027
8260
|
if (project === DEFAULT_PROJECT) {
|
|
8028
8261
|
return {
|
|
8029
|
-
snapshotPath:
|
|
8030
|
-
errorsPath:
|
|
8031
|
-
staleEventsPath:
|
|
8032
|
-
embeddingsCachePath:
|
|
8033
|
-
policyViolationsPath:
|
|
8262
|
+
snapshotPath: import_node_path44.default.join(baseDir, "graph.json"),
|
|
8263
|
+
errorsPath: import_node_path44.default.join(baseDir, "errors.ndjson"),
|
|
8264
|
+
staleEventsPath: import_node_path44.default.join(baseDir, "stale-events.ndjson"),
|
|
8265
|
+
embeddingsCachePath: import_node_path44.default.join(baseDir, "embeddings.json"),
|
|
8266
|
+
policyViolationsPath: import_node_path44.default.join(baseDir, "policy-violations.ndjson")
|
|
8034
8267
|
};
|
|
8035
8268
|
}
|
|
8036
8269
|
return {
|
|
8037
|
-
snapshotPath:
|
|
8038
|
-
errorsPath:
|
|
8039
|
-
staleEventsPath:
|
|
8040
|
-
embeddingsCachePath:
|
|
8041
|
-
policyViolationsPath:
|
|
8270
|
+
snapshotPath: import_node_path44.default.join(baseDir, `${project}.json`),
|
|
8271
|
+
errorsPath: import_node_path44.default.join(baseDir, `errors.${project}.ndjson`),
|
|
8272
|
+
staleEventsPath: import_node_path44.default.join(baseDir, `stale-events.${project}.ndjson`),
|
|
8273
|
+
embeddingsCachePath: import_node_path44.default.join(baseDir, `embeddings.${project}.json`),
|
|
8274
|
+
policyViolationsPath: import_node_path44.default.join(baseDir, `policy-violations.${project}.ndjson`)
|
|
8042
8275
|
};
|
|
8043
8276
|
}
|
|
8044
8277
|
var Projects = class {
|
|
@@ -8074,25 +8307,25 @@ var Projects = class {
|
|
|
8074
8307
|
|
|
8075
8308
|
// src/registry.ts
|
|
8076
8309
|
init_cjs_shims();
|
|
8077
|
-
var
|
|
8310
|
+
var import_node_fs28 = require("fs");
|
|
8078
8311
|
var import_node_os3 = __toESM(require("os"), 1);
|
|
8079
|
-
var
|
|
8080
|
-
var
|
|
8312
|
+
var import_node_path45 = __toESM(require("path"), 1);
|
|
8313
|
+
var import_types32 = require("@neat.is/types");
|
|
8081
8314
|
var LOCK_TIMEOUT_MS = 5e3;
|
|
8082
8315
|
var LOCK_RETRY_MS = 50;
|
|
8083
8316
|
function neatHome() {
|
|
8084
8317
|
const override = process.env.NEAT_HOME;
|
|
8085
|
-
if (override && override.length > 0) return
|
|
8086
|
-
return
|
|
8318
|
+
if (override && override.length > 0) return import_node_path45.default.resolve(override);
|
|
8319
|
+
return import_node_path45.default.join(import_node_os3.default.homedir(), ".neat");
|
|
8087
8320
|
}
|
|
8088
8321
|
function registryPath() {
|
|
8089
|
-
return
|
|
8322
|
+
return import_node_path45.default.join(neatHome(), "projects.json");
|
|
8090
8323
|
}
|
|
8091
8324
|
function registryLockPath() {
|
|
8092
|
-
return
|
|
8325
|
+
return import_node_path45.default.join(neatHome(), "projects.json.lock");
|
|
8093
8326
|
}
|
|
8094
8327
|
function daemonPidPath() {
|
|
8095
|
-
return
|
|
8328
|
+
return import_node_path45.default.join(neatHome(), "neatd.pid");
|
|
8096
8329
|
}
|
|
8097
8330
|
function isPidAliveDefault(pid) {
|
|
8098
8331
|
try {
|
|
@@ -8104,7 +8337,7 @@ function isPidAliveDefault(pid) {
|
|
|
8104
8337
|
}
|
|
8105
8338
|
async function readPidFile(file) {
|
|
8106
8339
|
try {
|
|
8107
|
-
const raw = await
|
|
8340
|
+
const raw = await import_node_fs28.promises.readFile(file, "utf8");
|
|
8108
8341
|
const pid = Number.parseInt(raw.trim(), 10);
|
|
8109
8342
|
return Number.isInteger(pid) && pid > 0 ? pid : void 0;
|
|
8110
8343
|
} catch {
|
|
@@ -8152,32 +8385,32 @@ function lockHolderMessage(holder, lockPath, timeoutMs) {
|
|
|
8152
8385
|
}
|
|
8153
8386
|
}
|
|
8154
8387
|
async function normalizeProjectPath(input) {
|
|
8155
|
-
const resolved =
|
|
8388
|
+
const resolved = import_node_path45.default.resolve(input);
|
|
8156
8389
|
try {
|
|
8157
|
-
return await
|
|
8390
|
+
return await import_node_fs28.promises.realpath(resolved);
|
|
8158
8391
|
} catch {
|
|
8159
8392
|
return resolved;
|
|
8160
8393
|
}
|
|
8161
8394
|
}
|
|
8162
8395
|
async function writeAtomically(target, contents) {
|
|
8163
|
-
await
|
|
8396
|
+
await import_node_fs28.promises.mkdir(import_node_path45.default.dirname(target), { recursive: true });
|
|
8164
8397
|
const tmp = `${target}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
|
|
8165
|
-
const fd = await
|
|
8398
|
+
const fd = await import_node_fs28.promises.open(tmp, "w");
|
|
8166
8399
|
try {
|
|
8167
8400
|
await fd.writeFile(contents, "utf8");
|
|
8168
8401
|
await fd.sync();
|
|
8169
8402
|
} finally {
|
|
8170
8403
|
await fd.close();
|
|
8171
8404
|
}
|
|
8172
|
-
await
|
|
8405
|
+
await import_node_fs28.promises.rename(tmp, target);
|
|
8173
8406
|
}
|
|
8174
8407
|
async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaultLockHolderProbe) {
|
|
8175
8408
|
const deadline = Date.now() + timeoutMs;
|
|
8176
|
-
await
|
|
8409
|
+
await import_node_fs28.promises.mkdir(import_node_path45.default.dirname(lockPath), { recursive: true });
|
|
8177
8410
|
let probedHolder = false;
|
|
8178
8411
|
while (true) {
|
|
8179
8412
|
try {
|
|
8180
|
-
const fd = await
|
|
8413
|
+
const fd = await import_node_fs28.promises.open(lockPath, "wx");
|
|
8181
8414
|
try {
|
|
8182
8415
|
await fd.writeFile(`${process.pid}
|
|
8183
8416
|
`, "utf8");
|
|
@@ -8202,7 +8435,7 @@ async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaul
|
|
|
8202
8435
|
}
|
|
8203
8436
|
}
|
|
8204
8437
|
async function releaseLock(lockPath) {
|
|
8205
|
-
await
|
|
8438
|
+
await import_node_fs28.promises.unlink(lockPath).catch(() => {
|
|
8206
8439
|
});
|
|
8207
8440
|
}
|
|
8208
8441
|
async function withLock(fn) {
|
|
@@ -8218,7 +8451,7 @@ async function readRegistry() {
|
|
|
8218
8451
|
const file = registryPath();
|
|
8219
8452
|
let raw;
|
|
8220
8453
|
try {
|
|
8221
|
-
raw = await
|
|
8454
|
+
raw = await import_node_fs28.promises.readFile(file, "utf8");
|
|
8222
8455
|
} catch (err) {
|
|
8223
8456
|
if (err.code === "ENOENT") {
|
|
8224
8457
|
return { version: 1, projects: [] };
|
|
@@ -8226,10 +8459,10 @@ async function readRegistry() {
|
|
|
8226
8459
|
throw err;
|
|
8227
8460
|
}
|
|
8228
8461
|
const parsed = JSON.parse(raw);
|
|
8229
|
-
return
|
|
8462
|
+
return import_types32.RegistryFileSchema.parse(parsed);
|
|
8230
8463
|
}
|
|
8231
8464
|
async function writeRegistry(reg) {
|
|
8232
|
-
const validated =
|
|
8465
|
+
const validated = import_types32.RegistryFileSchema.parse(reg);
|
|
8233
8466
|
await writeAtomically(registryPath(), JSON.stringify(validated, null, 2) + "\n");
|
|
8234
8467
|
}
|
|
8235
8468
|
var ProjectNameCollisionError = class extends Error {
|
|
@@ -8323,7 +8556,7 @@ function pruneTtlMs() {
|
|
|
8323
8556
|
}
|
|
8324
8557
|
async function statPathStatus(p) {
|
|
8325
8558
|
try {
|
|
8326
|
-
const stat = await
|
|
8559
|
+
const stat = await import_node_fs28.promises.stat(p);
|
|
8327
8560
|
return stat.isDirectory() ? "present" : "unknown";
|
|
8328
8561
|
} catch (err) {
|
|
8329
8562
|
return err.code === "ENOENT" ? "gone" : "unknown";
|
|
@@ -8420,6 +8653,215 @@ data: ${JSON.stringify(envelope.payload)}
|
|
|
8420
8653
|
|
|
8421
8654
|
// src/api.ts
|
|
8422
8655
|
init_auth();
|
|
8656
|
+
|
|
8657
|
+
// src/connectors-config.ts
|
|
8658
|
+
init_cjs_shims();
|
|
8659
|
+
var import_node_os4 = __toESM(require("os"), 1);
|
|
8660
|
+
var import_node_path46 = __toESM(require("path"), 1);
|
|
8661
|
+
var import_node_fs29 = require("fs");
|
|
8662
|
+
var CONNECTORS_CONFIG_VERSION = 1;
|
|
8663
|
+
var EnvRefUnsetError = class extends Error {
|
|
8664
|
+
ref;
|
|
8665
|
+
varName;
|
|
8666
|
+
constructor(ref, varName) {
|
|
8667
|
+
super(`${ref} is unset`);
|
|
8668
|
+
this.name = "EnvRefUnsetError";
|
|
8669
|
+
this.ref = ref;
|
|
8670
|
+
this.varName = varName;
|
|
8671
|
+
}
|
|
8672
|
+
};
|
|
8673
|
+
function neatHome2() {
|
|
8674
|
+
const override = process.env.NEAT_HOME;
|
|
8675
|
+
if (override && override.length > 0) return import_node_path46.default.resolve(override);
|
|
8676
|
+
return import_node_path46.default.join(import_node_os4.default.homedir(), ".neat");
|
|
8677
|
+
}
|
|
8678
|
+
function connectorsConfigPath(home = neatHome2()) {
|
|
8679
|
+
return import_node_path46.default.join(home, "connectors.json");
|
|
8680
|
+
}
|
|
8681
|
+
var MODE_MASK_LOOSER_THAN_0600 = 63;
|
|
8682
|
+
async function warnIfModeLooserThan0600(file) {
|
|
8683
|
+
if (process.platform === "win32") return;
|
|
8684
|
+
try {
|
|
8685
|
+
const stat = await import_node_fs29.promises.stat(file);
|
|
8686
|
+
if ((stat.mode & MODE_MASK_LOOSER_THAN_0600) !== 0) {
|
|
8687
|
+
const mode = (stat.mode & 511).toString(8).padStart(3, "0");
|
|
8688
|
+
console.warn(
|
|
8689
|
+
`[neat] ${file} is mode 0${mode}, looser than the 0600 this file's secrets call for \u2014 run \`chmod 600 ${file}\``
|
|
8690
|
+
);
|
|
8691
|
+
}
|
|
8692
|
+
} catch {
|
|
8693
|
+
}
|
|
8694
|
+
}
|
|
8695
|
+
async function readConnectorsConfig(home = neatHome2()) {
|
|
8696
|
+
const file = connectorsConfigPath(home);
|
|
8697
|
+
let raw;
|
|
8698
|
+
try {
|
|
8699
|
+
raw = await import_node_fs29.promises.readFile(file, "utf8");
|
|
8700
|
+
} catch (err) {
|
|
8701
|
+
if (err.code === "ENOENT") {
|
|
8702
|
+
return { version: CONNECTORS_CONFIG_VERSION, connectors: [] };
|
|
8703
|
+
}
|
|
8704
|
+
throw err;
|
|
8705
|
+
}
|
|
8706
|
+
await warnIfModeLooserThan0600(file);
|
|
8707
|
+
let parsed;
|
|
8708
|
+
try {
|
|
8709
|
+
parsed = JSON.parse(raw);
|
|
8710
|
+
} catch (err) {
|
|
8711
|
+
throw new Error(`${file} is not valid JSON: ${err.message}`);
|
|
8712
|
+
}
|
|
8713
|
+
return validateConfig(parsed, file);
|
|
8714
|
+
}
|
|
8715
|
+
function validateConfig(parsed, file) {
|
|
8716
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
8717
|
+
throw new Error(`${file} must be a JSON object with a "connectors" array`);
|
|
8718
|
+
}
|
|
8719
|
+
const obj = parsed;
|
|
8720
|
+
const version = obj.version === void 0 ? CONNECTORS_CONFIG_VERSION : obj.version;
|
|
8721
|
+
if (typeof version !== "number" || !Number.isInteger(version)) {
|
|
8722
|
+
throw new Error(`${file}: "version" must be an integer`);
|
|
8723
|
+
}
|
|
8724
|
+
const rawConnectors = obj.connectors;
|
|
8725
|
+
if (!Array.isArray(rawConnectors)) {
|
|
8726
|
+
throw new Error(`${file}: "connectors" must be an array`);
|
|
8727
|
+
}
|
|
8728
|
+
const connectors = rawConnectors.map((entry, i) => validateEntry(entry, i, file));
|
|
8729
|
+
return { version, connectors };
|
|
8730
|
+
}
|
|
8731
|
+
function validateEntry(entry, index, file) {
|
|
8732
|
+
const where = `${file}: connectors[${index}]`;
|
|
8733
|
+
if (typeof entry !== "object" || entry === null || Array.isArray(entry)) {
|
|
8734
|
+
throw new Error(`${where} must be an object`);
|
|
8735
|
+
}
|
|
8736
|
+
const e = entry;
|
|
8737
|
+
const id = e.id;
|
|
8738
|
+
if (typeof id !== "string" || id.length === 0) {
|
|
8739
|
+
throw new Error(`${where}.id must be a non-empty string`);
|
|
8740
|
+
}
|
|
8741
|
+
const provider = e.provider;
|
|
8742
|
+
if (typeof provider !== "string" || provider.length === 0) {
|
|
8743
|
+
throw new Error(`${where}.provider must be a non-empty string`);
|
|
8744
|
+
}
|
|
8745
|
+
if (e.project !== void 0 && typeof e.project !== "string") {
|
|
8746
|
+
throw new Error(`${where}.project must be a string when present`);
|
|
8747
|
+
}
|
|
8748
|
+
const credential = validateCredentialRef(e.credential, `${where}.credential`);
|
|
8749
|
+
let options;
|
|
8750
|
+
if (e.options !== void 0) {
|
|
8751
|
+
if (typeof e.options !== "object" || e.options === null || Array.isArray(e.options)) {
|
|
8752
|
+
throw new Error(`${where}.options must be an object when present`);
|
|
8753
|
+
}
|
|
8754
|
+
options = e.options;
|
|
8755
|
+
}
|
|
8756
|
+
return {
|
|
8757
|
+
id,
|
|
8758
|
+
provider,
|
|
8759
|
+
...typeof e.project === "string" ? { project: e.project } : {},
|
|
8760
|
+
credential,
|
|
8761
|
+
...options ? { options } : {}
|
|
8762
|
+
};
|
|
8763
|
+
}
|
|
8764
|
+
function validateCredentialRef(raw, where) {
|
|
8765
|
+
if (typeof raw === "string") {
|
|
8766
|
+
if (raw.length === 0) throw new Error(`${where} must be a non-empty string`);
|
|
8767
|
+
return raw;
|
|
8768
|
+
}
|
|
8769
|
+
if (typeof raw === "object" && raw !== null && !Array.isArray(raw)) {
|
|
8770
|
+
const fields = raw;
|
|
8771
|
+
const keys = Object.keys(fields);
|
|
8772
|
+
if (keys.length === 0) throw new Error(`${where} object must have at least one field`);
|
|
8773
|
+
for (const key of keys) {
|
|
8774
|
+
const v = fields[key];
|
|
8775
|
+
if (typeof v !== "string" || v.length === 0) {
|
|
8776
|
+
throw new Error(`${where}.${key} must be a non-empty string`);
|
|
8777
|
+
}
|
|
8778
|
+
}
|
|
8779
|
+
return fields;
|
|
8780
|
+
}
|
|
8781
|
+
throw new Error(`${where} must be a string or an object of strings`);
|
|
8782
|
+
}
|
|
8783
|
+
function resolveCredential(ref, env = process.env) {
|
|
8784
|
+
if (typeof ref === "string") {
|
|
8785
|
+
return { kind: "single", value: resolveRef(ref, env) };
|
|
8786
|
+
}
|
|
8787
|
+
const fields = {};
|
|
8788
|
+
for (const [key, value] of Object.entries(ref)) {
|
|
8789
|
+
fields[key] = resolveRef(value, env);
|
|
8790
|
+
}
|
|
8791
|
+
return { kind: "fields", fields };
|
|
8792
|
+
}
|
|
8793
|
+
function resolveRef(value, env) {
|
|
8794
|
+
if (value.length > 1 && value.startsWith("$")) {
|
|
8795
|
+
const varName = value.slice(1);
|
|
8796
|
+
const resolved = env[varName];
|
|
8797
|
+
if (resolved === void 0 || resolved.length === 0) {
|
|
8798
|
+
throw new EnvRefUnsetError(value, varName);
|
|
8799
|
+
}
|
|
8800
|
+
return resolved;
|
|
8801
|
+
}
|
|
8802
|
+
return value;
|
|
8803
|
+
}
|
|
8804
|
+
function connectorMatchesProject(entry, project) {
|
|
8805
|
+
return entry.project === void 0 || entry.project === project;
|
|
8806
|
+
}
|
|
8807
|
+
function isEnvRef(value) {
|
|
8808
|
+
return value.length > 1 && value.startsWith("$");
|
|
8809
|
+
}
|
|
8810
|
+
function redactCredentialRef(ref) {
|
|
8811
|
+
const one = (value) => isEnvRef(value) ? value : "****";
|
|
8812
|
+
if (typeof ref === "string") return one(ref);
|
|
8813
|
+
const out = {};
|
|
8814
|
+
for (const [key, value] of Object.entries(ref)) out[key] = one(value);
|
|
8815
|
+
return out;
|
|
8816
|
+
}
|
|
8817
|
+
|
|
8818
|
+
// src/connectors/status.ts
|
|
8819
|
+
init_cjs_shims();
|
|
8820
|
+
var CONNECTOR_STALE_THRESHOLD_MS = 5 * 6e4;
|
|
8821
|
+
var records = /* @__PURE__ */ new Map();
|
|
8822
|
+
var MAX_ERROR_LEN = 200;
|
|
8823
|
+
function sanitizePollError(err) {
|
|
8824
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
8825
|
+
const collapsed = msg.replace(/\s+/g, " ").trim();
|
|
8826
|
+
return collapsed.length > MAX_ERROR_LEN ? `${collapsed.slice(0, MAX_ERROR_LEN - 1)}\u2026` : collapsed;
|
|
8827
|
+
}
|
|
8828
|
+
function recordConnectorPoll(id, tick) {
|
|
8829
|
+
const prev = records.get(id);
|
|
8830
|
+
records.set(id, {
|
|
8831
|
+
lastPollAt: tick.at,
|
|
8832
|
+
lastOutcome: tick.outcome,
|
|
8833
|
+
lastError: tick.outcome === "error" ? tick.error ?? null : null,
|
|
8834
|
+
signalsLastPoll: tick.signalsLastPoll ?? 0,
|
|
8835
|
+
lastOkAt: tick.outcome === "ok" ? tick.at : prev?.lastOkAt ?? null
|
|
8836
|
+
});
|
|
8837
|
+
}
|
|
8838
|
+
function deriveState(rec, now, thresholdMs) {
|
|
8839
|
+
if (rec.lastOutcome === "error") return "error";
|
|
8840
|
+
const okAt = rec.lastOkAt ? Date.parse(rec.lastOkAt) : NaN;
|
|
8841
|
+
if (!Number.isNaN(okAt) && now - okAt > thresholdMs) return "stale";
|
|
8842
|
+
return "healthy";
|
|
8843
|
+
}
|
|
8844
|
+
function getConnectorStatus(id, now = Date.now(), thresholdMs = CONNECTOR_STALE_THRESHOLD_MS) {
|
|
8845
|
+
const rec = records.get(id);
|
|
8846
|
+
if (!rec) {
|
|
8847
|
+
return {
|
|
8848
|
+
state: "idle",
|
|
8849
|
+
lastPollAt: null,
|
|
8850
|
+
lastOutcome: null,
|
|
8851
|
+
lastError: null,
|
|
8852
|
+
signalsLastPoll: 0
|
|
8853
|
+
};
|
|
8854
|
+
}
|
|
8855
|
+
return {
|
|
8856
|
+
state: deriveState(rec, now, thresholdMs),
|
|
8857
|
+
lastPollAt: rec.lastPollAt,
|
|
8858
|
+
lastOutcome: rec.lastOutcome,
|
|
8859
|
+
lastError: rec.lastError,
|
|
8860
|
+
signalsLastPoll: rec.signalsLastPoll
|
|
8861
|
+
};
|
|
8862
|
+
}
|
|
8863
|
+
|
|
8864
|
+
// src/api.ts
|
|
8423
8865
|
function serializeGraph(graph) {
|
|
8424
8866
|
const nodes = [];
|
|
8425
8867
|
graph.forEachNode((_id, attrs) => {
|
|
@@ -8570,11 +9012,11 @@ function registerRoutes(scope, ctx) {
|
|
|
8570
9012
|
const candidates = req.query.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
8571
9013
|
const parsed = [];
|
|
8572
9014
|
for (const c of candidates) {
|
|
8573
|
-
const r =
|
|
9015
|
+
const r = import_types33.DivergenceTypeSchema.safeParse(c);
|
|
8574
9016
|
if (!r.success) {
|
|
8575
9017
|
return reply.code(400).send({
|
|
8576
9018
|
error: `unknown divergence type "${c}"`,
|
|
8577
|
-
allowed:
|
|
9019
|
+
allowed: import_types33.DivergenceTypeSchema.options
|
|
8578
9020
|
});
|
|
8579
9021
|
}
|
|
8580
9022
|
parsed.push(r.data);
|
|
@@ -8639,6 +9081,26 @@ function registerRoutes(scope, ctx) {
|
|
|
8639
9081
|
const sliced = filtered.slice(0, safeLimit);
|
|
8640
9082
|
return { count: sliced.length, total, logs: sliced };
|
|
8641
9083
|
});
|
|
9084
|
+
scope.get("/connectors", async (req, reply) => {
|
|
9085
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
|
|
9086
|
+
if (!proj) return;
|
|
9087
|
+
let entries;
|
|
9088
|
+
try {
|
|
9089
|
+
entries = (await readConnectorsConfig(ctx.connectorsHome)).connectors;
|
|
9090
|
+
} catch (err) {
|
|
9091
|
+
return reply.code(500).send({
|
|
9092
|
+
error: "connectors.json failed to read",
|
|
9093
|
+
details: err.message
|
|
9094
|
+
});
|
|
9095
|
+
}
|
|
9096
|
+
const connectors = entries.filter((entry) => connectorMatchesProject(entry, proj.name)).map((entry) => ({
|
|
9097
|
+
id: entry.id,
|
|
9098
|
+
provider: entry.provider,
|
|
9099
|
+
credentialRef: redactCredentialRef(entry.credential),
|
|
9100
|
+
status: getConnectorStatus(entry.id)
|
|
9101
|
+
}));
|
|
9102
|
+
return { connectors };
|
|
9103
|
+
});
|
|
8642
9104
|
const incidentHistoryHandler = async (req, reply) => {
|
|
8643
9105
|
const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
|
|
8644
9106
|
if (!proj) return;
|
|
@@ -8825,7 +9287,7 @@ function registerRoutes(scope, ctx) {
|
|
|
8825
9287
|
const log = new PolicyViolationsLog(proj.paths.policyViolationsPath);
|
|
8826
9288
|
let violations = await log.readAll();
|
|
8827
9289
|
if (req.query.severity) {
|
|
8828
|
-
const sev =
|
|
9290
|
+
const sev = import_types33.PolicySeveritySchema.safeParse(req.query.severity);
|
|
8829
9291
|
if (!sev.success) {
|
|
8830
9292
|
return reply.code(400).send({
|
|
8831
9293
|
error: "invalid severity",
|
|
@@ -8864,7 +9326,7 @@ function registerRoutes(scope, ctx) {
|
|
|
8864
9326
|
scope.post("/policies/check", async (req, reply) => {
|
|
8865
9327
|
const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
|
|
8866
9328
|
if (!proj) return;
|
|
8867
|
-
const parsed =
|
|
9329
|
+
const parsed = import_types33.PoliciesCheckBodySchema.safeParse(req.body ?? {});
|
|
8868
9330
|
if (!parsed.success) {
|
|
8869
9331
|
return reply.code(400).send({
|
|
8870
9332
|
error: "invalid /policies/check body",
|
|
@@ -9041,7 +9503,8 @@ async function buildApi(opts) {
|
|
|
9041
9503
|
staleEventsPathFor,
|
|
9042
9504
|
policyFilePathFor,
|
|
9043
9505
|
bootstrap: opts.bootstrap,
|
|
9044
|
-
singleProject: opts.singleProject?.name
|
|
9506
|
+
singleProject: opts.singleProject?.name,
|
|
9507
|
+
connectorsHome: opts.connectorsHome
|
|
9045
9508
|
};
|
|
9046
9509
|
app.get("/health", async () => {
|
|
9047
9510
|
const uptimeMs = Date.now() - startedAt;
|
|
@@ -9126,8 +9589,8 @@ init_otel_grpc();
|
|
|
9126
9589
|
|
|
9127
9590
|
// src/daemon.ts
|
|
9128
9591
|
init_cjs_shims();
|
|
9129
|
-
var
|
|
9130
|
-
var
|
|
9592
|
+
var import_node_fs31 = require("fs");
|
|
9593
|
+
var import_node_path50 = __toESM(require("path"), 1);
|
|
9131
9594
|
var import_node_module = require("module");
|
|
9132
9595
|
init_otel();
|
|
9133
9596
|
|
|
@@ -9191,16 +9654,31 @@ function startConnectorPollLoop(connector, ctx, graph, resolveTarget, options =
|
|
|
9191
9654
|
let stopped = false;
|
|
9192
9655
|
let since = ctx.since;
|
|
9193
9656
|
const intervalMs = options.intervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
9657
|
+
const connectorId = options.connectorId;
|
|
9194
9658
|
const onError = options.onError ?? ((err) => console.error(`[neatd] connector poll failed (${connector.provider})`, err));
|
|
9195
9659
|
const tick = () => {
|
|
9196
9660
|
if (stopped) return;
|
|
9197
9661
|
void (async () => {
|
|
9198
9662
|
const tickStartedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
9199
9663
|
try {
|
|
9200
|
-
await runConnectorPoll(connector, { ...ctx, since }, graph, resolveTarget);
|
|
9664
|
+
const result = await runConnectorPoll(connector, { ...ctx, since }, graph, resolveTarget);
|
|
9201
9665
|
since = tickStartedAt;
|
|
9666
|
+
if (connectorId) {
|
|
9667
|
+
recordConnectorPoll(connectorId, {
|
|
9668
|
+
outcome: "ok",
|
|
9669
|
+
at: tickStartedAt,
|
|
9670
|
+
signalsLastPoll: result.signalCount
|
|
9671
|
+
});
|
|
9672
|
+
}
|
|
9202
9673
|
} catch (err) {
|
|
9203
9674
|
onError(err);
|
|
9675
|
+
if (connectorId) {
|
|
9676
|
+
recordConnectorPoll(connectorId, {
|
|
9677
|
+
outcome: "error",
|
|
9678
|
+
at: tickStartedAt,
|
|
9679
|
+
error: sanitizePollError(err)
|
|
9680
|
+
});
|
|
9681
|
+
}
|
|
9204
9682
|
}
|
|
9205
9683
|
})();
|
|
9206
9684
|
};
|
|
@@ -9499,6 +9977,30 @@ function buildEdgeLogsQuery(limit) {
|
|
|
9499
9977
|
`limit ${safeLimit}`
|
|
9500
9978
|
].join("\n");
|
|
9501
9979
|
}
|
|
9980
|
+
function logsAllHttpFailureMessage(status2) {
|
|
9981
|
+
if (status2 === 401 || status2 === 403) {
|
|
9982
|
+
return `supabase connector: logs.all request rejected (HTTP ${status2}). Check the Management API token, its analytics read scope, and --api-project-ref.`;
|
|
9983
|
+
}
|
|
9984
|
+
if (status2 === 404) {
|
|
9985
|
+
return "supabase connector: logs.all project not found (HTTP 404). Check --api-project-ref.";
|
|
9986
|
+
}
|
|
9987
|
+
if (status2 === 429) {
|
|
9988
|
+
return "supabase connector: logs.all rate-limited (HTTP 429). The connector will retry on the next poll; reduce poll cadence if this persists.";
|
|
9989
|
+
}
|
|
9990
|
+
if (status2 === 400) {
|
|
9991
|
+
return "supabase connector: logs.all query rejected (HTTP 400). Provider details redacted; confirm the live log-query dialect before shipping this connector.";
|
|
9992
|
+
}
|
|
9993
|
+
return `supabase connector: logs.all request failed (HTTP ${status2}); provider details redacted.`;
|
|
9994
|
+
}
|
|
9995
|
+
function providerErrorDetails(error) {
|
|
9996
|
+
if (!error || typeof error === "string") return "";
|
|
9997
|
+
const parts = [];
|
|
9998
|
+
if (typeof error.code === "number") parts.push(`code ${error.code}`);
|
|
9999
|
+
if (typeof error.status === "string" && /^[A-Z0-9_.-]+$/.test(error.status)) {
|
|
10000
|
+
parts.push(`status ${error.status}`);
|
|
10001
|
+
}
|
|
10002
|
+
return parts.length > 0 ? ` (${parts.join(", ")})` : "";
|
|
10003
|
+
}
|
|
9502
10004
|
async function fetchSupabaseEdgeLogs(config, token, startIso, endIso, fetchImpl = fetch) {
|
|
9503
10005
|
const baseUrl = config.managementApiUrl ?? DEFAULT_SUPABASE_MANAGEMENT_API_URL;
|
|
9504
10006
|
const url = new URL(`${baseUrl}/v1/projects/${config.apiProjectRef}/analytics/endpoints/logs.all`);
|
|
@@ -9513,12 +10015,18 @@ async function fetchSupabaseEdgeLogs(config, token, startIso, endIso, fetchImpl
|
|
|
9513
10015
|
{ provider: "supabase", accountKey: config.apiProjectRef, fetchImpl }
|
|
9514
10016
|
);
|
|
9515
10017
|
if (!res.ok) {
|
|
9516
|
-
throw new Error(
|
|
10018
|
+
throw new Error(logsAllHttpFailureMessage(res.status));
|
|
10019
|
+
}
|
|
10020
|
+
let body;
|
|
10021
|
+
try {
|
|
10022
|
+
body = await res.json();
|
|
10023
|
+
} catch {
|
|
10024
|
+
throw new Error("supabase connector: logs.all returned invalid JSON; provider details redacted.");
|
|
9517
10025
|
}
|
|
9518
|
-
const body = await res.json();
|
|
9519
10026
|
if (body.error) {
|
|
9520
|
-
|
|
9521
|
-
|
|
10027
|
+
throw new Error(
|
|
10028
|
+
`supabase connector: logs.all returned a provider error${providerErrorDetails(body.error)}; provider message redacted.`
|
|
10029
|
+
);
|
|
9522
10030
|
}
|
|
9523
10031
|
return body.result ?? [];
|
|
9524
10032
|
}
|
|
@@ -9550,10 +10058,10 @@ var SUPABASE_RPC_TARGET_KIND = "supabase-rpc";
|
|
|
9550
10058
|
// src/connectors/supabase/map.ts
|
|
9551
10059
|
var REST_RPC_PATH_RE = /^\/rest\/v1\/rpc\/([^/?]+)/;
|
|
9552
10060
|
var REST_TABLE_PATH_RE = /^\/rest\/v1\/([^/?]+)/;
|
|
9553
|
-
function targetFromRestPath(
|
|
9554
|
-
const rpcMatch = REST_RPC_PATH_RE.exec(
|
|
10061
|
+
function targetFromRestPath(path51) {
|
|
10062
|
+
const rpcMatch = REST_RPC_PATH_RE.exec(path51);
|
|
9555
10063
|
if (rpcMatch) return { targetKind: SUPABASE_RPC_TARGET_KIND, name: rpcMatch[1] };
|
|
9556
|
-
const tableMatch = REST_TABLE_PATH_RE.exec(
|
|
10064
|
+
const tableMatch = REST_TABLE_PATH_RE.exec(path51);
|
|
9557
10065
|
if (tableMatch) return { targetKind: SUPABASE_TABLE_TARGET_KIND, name: tableMatch[1] };
|
|
9558
10066
|
return null;
|
|
9559
10067
|
}
|
|
@@ -9656,19 +10164,19 @@ async function fetchPgStatStatements(connectionString, limit = DEFAULT_STATEMENT
|
|
|
9656
10164
|
|
|
9657
10165
|
// src/connectors/supabase/resolve.ts
|
|
9658
10166
|
init_cjs_shims();
|
|
9659
|
-
var
|
|
10167
|
+
var import_types35 = require("@neat.is/types");
|
|
9660
10168
|
function createSupabaseResolveTarget(graph, config) {
|
|
9661
10169
|
return (signal, _ctx) => {
|
|
9662
10170
|
if (signal.targetKind !== SUPABASE_TABLE_TARGET_KIND && signal.targetKind !== SUPABASE_RPC_TARGET_KIND) {
|
|
9663
10171
|
return null;
|
|
9664
10172
|
}
|
|
9665
|
-
const subResourceId = (0,
|
|
10173
|
+
const subResourceId = (0, import_types35.infraId)(signal.targetKind, `${config.nodeRef}/${signal.targetName}`);
|
|
9666
10174
|
if (graph.hasNode(subResourceId)) {
|
|
9667
|
-
return { targetNodeId: subResourceId, serviceName: config.serviceName, edgeType:
|
|
10175
|
+
return { targetNodeId: subResourceId, serviceName: config.serviceName, edgeType: import_types35.EdgeType.CALLS };
|
|
9668
10176
|
}
|
|
9669
|
-
const projectLevelId = (0,
|
|
10177
|
+
const projectLevelId = (0, import_types35.infraId)("supabase", config.nodeRef);
|
|
9670
10178
|
if (graph.hasNode(projectLevelId)) {
|
|
9671
|
-
return { targetNodeId: projectLevelId, serviceName: config.serviceName, edgeType:
|
|
10179
|
+
return { targetNodeId: projectLevelId, serviceName: config.serviceName, edgeType: import_types35.EdgeType.CALLS };
|
|
9672
10180
|
}
|
|
9673
10181
|
return null;
|
|
9674
10182
|
};
|
|
@@ -9676,6 +10184,35 @@ function createSupabaseResolveTarget(graph, config) {
|
|
|
9676
10184
|
|
|
9677
10185
|
// src/connectors/supabase/index.ts
|
|
9678
10186
|
var DEFAULT_MAX_LOOKBACK_MS = 24 * 60 * 60 * 1e3;
|
|
10187
|
+
function errorCode(err) {
|
|
10188
|
+
const code = err?.code;
|
|
10189
|
+
return typeof code === "string" && code.length > 0 ? code : void 0;
|
|
10190
|
+
}
|
|
10191
|
+
function describeSupabasePostgresSurfaceFailure(projectRef, err) {
|
|
10192
|
+
const code = errorCode(err);
|
|
10193
|
+
let reason;
|
|
10194
|
+
switch (code) {
|
|
10195
|
+
case "42501":
|
|
10196
|
+
reason = "permission denied; grant pg_read_all_stats to the configured Postgres role";
|
|
10197
|
+
break;
|
|
10198
|
+
case "42P01":
|
|
10199
|
+
case "42704":
|
|
10200
|
+
reason = "pg_stat_statements is not enabled or visible to the configured Postgres role";
|
|
10201
|
+
break;
|
|
10202
|
+
case "28P01":
|
|
10203
|
+
case "28000":
|
|
10204
|
+
reason = "Postgres credential rejected";
|
|
10205
|
+
break;
|
|
10206
|
+
case "3D000":
|
|
10207
|
+
reason = "database not found";
|
|
10208
|
+
break;
|
|
10209
|
+
default: {
|
|
10210
|
+
const name = err instanceof Error && err.name ? err.name : "Error";
|
|
10211
|
+
reason = code ? `${name} ${code}` : name;
|
|
10212
|
+
}
|
|
10213
|
+
}
|
|
10214
|
+
return `supabase connector: pg_stat_statements surface unavailable for project ${projectRef} (${reason}); continuing with Management API log surface.`;
|
|
10215
|
+
}
|
|
9679
10216
|
var SupabaseConnector = class {
|
|
9680
10217
|
// `deps.fetchPgStatStatements` defaults to the real Postgres-backed
|
|
9681
10218
|
// implementation; tests override it to exercise the "both surfaces
|
|
@@ -9707,12 +10244,18 @@ var SupabaseConnector = class {
|
|
|
9707
10244
|
const signals = mapEdgeLogRowsToSignals(logRows);
|
|
9708
10245
|
if (creds.postgresConnectionString) {
|
|
9709
10246
|
const fetchStatements = this.deps.fetchPgStatStatements ?? fetchPgStatStatements;
|
|
9710
|
-
|
|
9711
|
-
|
|
9712
|
-
|
|
9713
|
-
|
|
9714
|
-
|
|
9715
|
-
|
|
10247
|
+
try {
|
|
10248
|
+
const statementRows = await fetchStatements(
|
|
10249
|
+
creds.postgresConnectionString,
|
|
10250
|
+
this.config.statementLimit ?? DEFAULT_STATEMENT_LIMIT,
|
|
10251
|
+
this.config.apiProjectRef
|
|
10252
|
+
);
|
|
10253
|
+
signals.push(...diffPgStatStatementsToSignals(statementRows, this.statementBaselines, now.toISOString()));
|
|
10254
|
+
} catch (err) {
|
|
10255
|
+
const summary = describeSupabasePostgresSurfaceFailure(this.config.apiProjectRef, err);
|
|
10256
|
+
if (this.deps.onPostgresSurfaceError) this.deps.onPostgresSurfaceError(err, summary);
|
|
10257
|
+
else console.warn(summary);
|
|
10258
|
+
}
|
|
9716
10259
|
}
|
|
9717
10260
|
return signals;
|
|
9718
10261
|
}
|
|
@@ -9726,7 +10269,7 @@ function createSupabaseConnector(graph, config, deps = {}) {
|
|
|
9726
10269
|
|
|
9727
10270
|
// src/connectors/railway/index.ts
|
|
9728
10271
|
init_cjs_shims();
|
|
9729
|
-
var
|
|
10272
|
+
var import_types39 = require("@neat.is/types");
|
|
9730
10273
|
|
|
9731
10274
|
// src/connectors/railway/client.ts
|
|
9732
10275
|
init_cjs_shims();
|
|
@@ -9871,7 +10414,7 @@ function buildRailwayRouteIndex(graph, serviceName) {
|
|
|
9871
10414
|
const out = [];
|
|
9872
10415
|
graph.forEachNode((_id, attrs) => {
|
|
9873
10416
|
const node = attrs;
|
|
9874
|
-
if (node.type !==
|
|
10417
|
+
if (node.type !== import_types39.NodeType.RouteNode) return;
|
|
9875
10418
|
const route = attrs;
|
|
9876
10419
|
if (route.service !== serviceName) return;
|
|
9877
10420
|
out.push({
|
|
@@ -9967,12 +10510,12 @@ function createRailwayResolveTarget(config) {
|
|
|
9967
10510
|
const serviceName = config.serviceNameById[config.serviceId];
|
|
9968
10511
|
if (!serviceName) return null;
|
|
9969
10512
|
if (signal.targetKind === ROUTE_TARGET_KIND) {
|
|
9970
|
-
return { targetNodeId: signal.targetName, serviceName, edgeType:
|
|
10513
|
+
return { targetNodeId: signal.targetName, serviceName, edgeType: import_types39.EdgeType.CALLS };
|
|
9971
10514
|
}
|
|
9972
10515
|
if (signal.targetKind === PEER_SERVICE_TARGET_KIND) {
|
|
9973
10516
|
const peerName = config.serviceNameById[signal.targetName];
|
|
9974
10517
|
if (!peerName) return null;
|
|
9975
|
-
return { targetNodeId: (0,
|
|
10518
|
+
return { targetNodeId: (0, import_types39.serviceId)(peerName), serviceName, edgeType: import_types39.EdgeType.CONNECTS_TO };
|
|
9976
10519
|
}
|
|
9977
10520
|
return null;
|
|
9978
10521
|
};
|
|
@@ -10087,9 +10630,9 @@ function parseFirebaseTargetName(targetName) {
|
|
|
10087
10630
|
const secondSep = rest.indexOf(FIELD_SEP);
|
|
10088
10631
|
if (secondSep === -1) return null;
|
|
10089
10632
|
const method = rest.slice(0, secondSep);
|
|
10090
|
-
const
|
|
10091
|
-
if (!resourceName || !method || !
|
|
10092
|
-
return { resourceName, method, path:
|
|
10633
|
+
const path51 = rest.slice(secondSep + 1);
|
|
10634
|
+
if (!resourceName || !method || !path51) return null;
|
|
10635
|
+
return { resourceName, method, path: path51 };
|
|
10093
10636
|
}
|
|
10094
10637
|
function resourceNameFor(type, labels) {
|
|
10095
10638
|
if (!labels) return null;
|
|
@@ -10126,14 +10669,14 @@ function mapLogEntryToSignal(entry) {
|
|
|
10126
10669
|
if (!req) return null;
|
|
10127
10670
|
if (!req.requestMethod) return null;
|
|
10128
10671
|
const method = req.requestMethod.toUpperCase();
|
|
10129
|
-
const
|
|
10130
|
-
if (
|
|
10672
|
+
const path51 = pathFromRequestUrl(req.requestUrl);
|
|
10673
|
+
if (path51 === null) return null;
|
|
10131
10674
|
const timestamp = entry.timestamp;
|
|
10132
10675
|
if (!timestamp) return null;
|
|
10133
10676
|
const isError = typeof req.status === "number" && req.status >= ERROR_STATUS_THRESHOLD2;
|
|
10134
10677
|
return {
|
|
10135
10678
|
targetKind: resourceType,
|
|
10136
|
-
targetName: packFirebaseTargetName({ resourceName, method, path:
|
|
10679
|
+
targetName: packFirebaseTargetName({ resourceName, method, path: path51 }),
|
|
10137
10680
|
callCount: 1,
|
|
10138
10681
|
errorCount: isError ? 1 : 0,
|
|
10139
10682
|
lastObservedIso: timestamp
|
|
@@ -10150,7 +10693,7 @@ function mapLogEntriesToSignals(entries) {
|
|
|
10150
10693
|
|
|
10151
10694
|
// src/connectors/firebase/resolve.ts
|
|
10152
10695
|
init_cjs_shims();
|
|
10153
|
-
var
|
|
10696
|
+
var import_types40 = require("@neat.is/types");
|
|
10154
10697
|
function neatServiceNameFor(resourceType, resourceName, serviceMap) {
|
|
10155
10698
|
switch (resourceType) {
|
|
10156
10699
|
case "cloud_function":
|
|
@@ -10165,7 +10708,7 @@ function routeEntriesFor(graph, serviceName) {
|
|
|
10165
10708
|
const entries = [];
|
|
10166
10709
|
graph.forEachNode((_id, attrs) => {
|
|
10167
10710
|
const node = attrs;
|
|
10168
|
-
if (node.type !==
|
|
10711
|
+
if (node.type !== import_types40.NodeType.RouteNode) return;
|
|
10169
10712
|
const route = attrs;
|
|
10170
10713
|
if (route.service !== serviceName) return;
|
|
10171
10714
|
entries.push({
|
|
@@ -10197,7 +10740,7 @@ function createFirebaseResolveTarget(graph, serviceMap) {
|
|
|
10197
10740
|
return {
|
|
10198
10741
|
targetNodeId: match.routeNodeId,
|
|
10199
10742
|
serviceName,
|
|
10200
|
-
edgeType:
|
|
10743
|
+
edgeType: import_types40.EdgeType.CALLS
|
|
10201
10744
|
};
|
|
10202
10745
|
};
|
|
10203
10746
|
}
|
|
@@ -10224,7 +10767,7 @@ init_cjs_shims();
|
|
|
10224
10767
|
|
|
10225
10768
|
// src/connectors/cloudflare/connector.ts
|
|
10226
10769
|
init_cjs_shims();
|
|
10227
|
-
var
|
|
10770
|
+
var import_types42 = require("@neat.is/types");
|
|
10228
10771
|
|
|
10229
10772
|
// src/connectors/cloudflare/client.ts
|
|
10230
10773
|
init_cjs_shims();
|
|
@@ -10331,7 +10874,7 @@ function mapEventToSignal(event) {
|
|
|
10331
10874
|
if (typeof timestampMs !== "number" || !Number.isFinite(timestampMs)) return null;
|
|
10332
10875
|
const statusCode = metadata?.statusCode;
|
|
10333
10876
|
const isError = typeof statusCode === "number" && statusCode >= ERROR_STATUS_THRESHOLD3;
|
|
10334
|
-
const
|
|
10877
|
+
const path51 = metadata?.trigger ? parsePathFromTrigger(metadata.trigger) : void 0;
|
|
10335
10878
|
return {
|
|
10336
10879
|
targetKind: CLOUDFLARE_TARGET_KIND,
|
|
10337
10880
|
targetName: scriptName,
|
|
@@ -10339,7 +10882,7 @@ function mapEventToSignal(event) {
|
|
|
10339
10882
|
errorCount: isError ? 1 : 0,
|
|
10340
10883
|
lastObservedIso: new Date(timestampMs).toISOString(),
|
|
10341
10884
|
method,
|
|
10342
|
-
...
|
|
10885
|
+
...path51 ? { path: path51 } : {},
|
|
10343
10886
|
...typeof statusCode === "number" ? { statusCode } : {},
|
|
10344
10887
|
...typeof metadata?.duration === "number" ? { duration: metadata.duration } : {}
|
|
10345
10888
|
};
|
|
@@ -10379,19 +10922,19 @@ function findTaggedWorkerFileNode(graph, workerName) {
|
|
|
10379
10922
|
graph.forEachNode((id, attrs) => {
|
|
10380
10923
|
if (found) return;
|
|
10381
10924
|
const a = attrs;
|
|
10382
|
-
if (a.type ===
|
|
10925
|
+
if (a.type === import_types42.NodeType.FileNode && a.platform === "cloudflare" && a.platformName === workerName) {
|
|
10383
10926
|
found = id;
|
|
10384
10927
|
}
|
|
10385
10928
|
});
|
|
10386
10929
|
return found;
|
|
10387
10930
|
}
|
|
10388
|
-
function findMatchingRouteNode(graph, serviceName, method,
|
|
10389
|
-
const normalizedPath = normalizePathTemplate(
|
|
10931
|
+
function findMatchingRouteNode(graph, serviceName, method, path51) {
|
|
10932
|
+
const normalizedPath = normalizePathTemplate(path51);
|
|
10390
10933
|
let found = null;
|
|
10391
10934
|
graph.forEachNode((id, attrs) => {
|
|
10392
10935
|
if (found) return;
|
|
10393
10936
|
const a = attrs;
|
|
10394
|
-
if (a.type !==
|
|
10937
|
+
if (a.type !== import_types42.NodeType.RouteNode || a.service !== serviceName) return;
|
|
10395
10938
|
if (!a.pathTemplate || normalizePathTemplate(a.pathTemplate) !== normalizedPath) return;
|
|
10396
10939
|
const routeMethod = (a.method ?? "").toUpperCase();
|
|
10397
10940
|
if (routeMethod !== "ALL" && routeMethod !== method) return;
|
|
@@ -10403,18 +10946,18 @@ function createCloudflareResolveTarget(config, graph) {
|
|
|
10403
10946
|
return (signal) => {
|
|
10404
10947
|
if (signal.targetKind !== CLOUDFLARE_TARGET_KIND) return null;
|
|
10405
10948
|
const scriptName = signal.targetName;
|
|
10406
|
-
const { method, path:
|
|
10949
|
+
const { method, path: path51 } = signal;
|
|
10407
10950
|
const resolveRouteGrain = (serviceName, wholeFileId) => {
|
|
10408
|
-
if (!method || !
|
|
10409
|
-
return findMatchingRouteNode(graph, serviceName, method,
|
|
10951
|
+
if (!method || !path51) return wholeFileId;
|
|
10952
|
+
return findMatchingRouteNode(graph, serviceName, method, path51) ?? wholeFileId;
|
|
10410
10953
|
};
|
|
10411
10954
|
const mapping = config.workers?.[scriptName];
|
|
10412
10955
|
if (mapping) {
|
|
10413
|
-
const wholeFileId = (0,
|
|
10956
|
+
const wholeFileId = (0, import_types42.fileId)(mapping.service, mapping.entryFile);
|
|
10414
10957
|
return {
|
|
10415
10958
|
targetNodeId: resolveRouteGrain(mapping.service, wholeFileId),
|
|
10416
10959
|
serviceName: mapping.service,
|
|
10417
|
-
edgeType:
|
|
10960
|
+
edgeType: import_types42.EdgeType.CALLS
|
|
10418
10961
|
};
|
|
10419
10962
|
}
|
|
10420
10963
|
const taggedFileId = findTaggedWorkerFileNode(graph, scriptName);
|
|
@@ -10423,169 +10966,18 @@ function createCloudflareResolveTarget(config, graph) {
|
|
|
10423
10966
|
return {
|
|
10424
10967
|
targetNodeId: resolveRouteGrain(fileNode.service, taggedFileId),
|
|
10425
10968
|
serviceName: fileNode.service,
|
|
10426
|
-
edgeType:
|
|
10969
|
+
edgeType: import_types42.EdgeType.CALLS
|
|
10427
10970
|
};
|
|
10428
10971
|
}
|
|
10429
10972
|
return {
|
|
10430
|
-
targetNodeId: (0,
|
|
10973
|
+
targetNodeId: (0, import_types42.infraId)("cloudflare-worker", scriptName),
|
|
10431
10974
|
serviceName: scriptName,
|
|
10432
|
-
edgeType:
|
|
10975
|
+
edgeType: import_types42.EdgeType.CALLS,
|
|
10433
10976
|
ensureInfraNode: { kind: "cloudflare-worker", name: scriptName, provider: "cloudflare" }
|
|
10434
10977
|
};
|
|
10435
10978
|
};
|
|
10436
10979
|
}
|
|
10437
10980
|
|
|
10438
|
-
// src/connectors-config.ts
|
|
10439
|
-
init_cjs_shims();
|
|
10440
|
-
var import_node_os4 = __toESM(require("os"), 1);
|
|
10441
|
-
var import_node_path45 = __toESM(require("path"), 1);
|
|
10442
|
-
var import_node_fs26 = require("fs");
|
|
10443
|
-
var CONNECTORS_CONFIG_VERSION = 1;
|
|
10444
|
-
var EnvRefUnsetError = class extends Error {
|
|
10445
|
-
ref;
|
|
10446
|
-
varName;
|
|
10447
|
-
constructor(ref, varName) {
|
|
10448
|
-
super(`${ref} is unset`);
|
|
10449
|
-
this.name = "EnvRefUnsetError";
|
|
10450
|
-
this.ref = ref;
|
|
10451
|
-
this.varName = varName;
|
|
10452
|
-
}
|
|
10453
|
-
};
|
|
10454
|
-
function neatHome2() {
|
|
10455
|
-
const override = process.env.NEAT_HOME;
|
|
10456
|
-
if (override && override.length > 0) return import_node_path45.default.resolve(override);
|
|
10457
|
-
return import_node_path45.default.join(import_node_os4.default.homedir(), ".neat");
|
|
10458
|
-
}
|
|
10459
|
-
function connectorsConfigPath(home = neatHome2()) {
|
|
10460
|
-
return import_node_path45.default.join(home, "connectors.json");
|
|
10461
|
-
}
|
|
10462
|
-
var MODE_MASK_LOOSER_THAN_0600 = 63;
|
|
10463
|
-
async function warnIfModeLooserThan0600(file) {
|
|
10464
|
-
if (process.platform === "win32") return;
|
|
10465
|
-
try {
|
|
10466
|
-
const stat = await import_node_fs26.promises.stat(file);
|
|
10467
|
-
if ((stat.mode & MODE_MASK_LOOSER_THAN_0600) !== 0) {
|
|
10468
|
-
const mode = (stat.mode & 511).toString(8).padStart(3, "0");
|
|
10469
|
-
console.warn(
|
|
10470
|
-
`[neat] ${file} is mode 0${mode}, looser than the 0600 this file's secrets call for \u2014 run \`chmod 600 ${file}\``
|
|
10471
|
-
);
|
|
10472
|
-
}
|
|
10473
|
-
} catch {
|
|
10474
|
-
}
|
|
10475
|
-
}
|
|
10476
|
-
async function readConnectorsConfig(home = neatHome2()) {
|
|
10477
|
-
const file = connectorsConfigPath(home);
|
|
10478
|
-
let raw;
|
|
10479
|
-
try {
|
|
10480
|
-
raw = await import_node_fs26.promises.readFile(file, "utf8");
|
|
10481
|
-
} catch (err) {
|
|
10482
|
-
if (err.code === "ENOENT") {
|
|
10483
|
-
return { version: CONNECTORS_CONFIG_VERSION, connectors: [] };
|
|
10484
|
-
}
|
|
10485
|
-
throw err;
|
|
10486
|
-
}
|
|
10487
|
-
await warnIfModeLooserThan0600(file);
|
|
10488
|
-
let parsed;
|
|
10489
|
-
try {
|
|
10490
|
-
parsed = JSON.parse(raw);
|
|
10491
|
-
} catch (err) {
|
|
10492
|
-
throw new Error(`${file} is not valid JSON: ${err.message}`);
|
|
10493
|
-
}
|
|
10494
|
-
return validateConfig(parsed, file);
|
|
10495
|
-
}
|
|
10496
|
-
function validateConfig(parsed, file) {
|
|
10497
|
-
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
10498
|
-
throw new Error(`${file} must be a JSON object with a "connectors" array`);
|
|
10499
|
-
}
|
|
10500
|
-
const obj = parsed;
|
|
10501
|
-
const version = obj.version === void 0 ? CONNECTORS_CONFIG_VERSION : obj.version;
|
|
10502
|
-
if (typeof version !== "number" || !Number.isInteger(version)) {
|
|
10503
|
-
throw new Error(`${file}: "version" must be an integer`);
|
|
10504
|
-
}
|
|
10505
|
-
const rawConnectors = obj.connectors;
|
|
10506
|
-
if (!Array.isArray(rawConnectors)) {
|
|
10507
|
-
throw new Error(`${file}: "connectors" must be an array`);
|
|
10508
|
-
}
|
|
10509
|
-
const connectors = rawConnectors.map((entry, i) => validateEntry(entry, i, file));
|
|
10510
|
-
return { version, connectors };
|
|
10511
|
-
}
|
|
10512
|
-
function validateEntry(entry, index, file) {
|
|
10513
|
-
const where = `${file}: connectors[${index}]`;
|
|
10514
|
-
if (typeof entry !== "object" || entry === null || Array.isArray(entry)) {
|
|
10515
|
-
throw new Error(`${where} must be an object`);
|
|
10516
|
-
}
|
|
10517
|
-
const e = entry;
|
|
10518
|
-
const id = e.id;
|
|
10519
|
-
if (typeof id !== "string" || id.length === 0) {
|
|
10520
|
-
throw new Error(`${where}.id must be a non-empty string`);
|
|
10521
|
-
}
|
|
10522
|
-
const provider = e.provider;
|
|
10523
|
-
if (typeof provider !== "string" || provider.length === 0) {
|
|
10524
|
-
throw new Error(`${where}.provider must be a non-empty string`);
|
|
10525
|
-
}
|
|
10526
|
-
if (e.project !== void 0 && typeof e.project !== "string") {
|
|
10527
|
-
throw new Error(`${where}.project must be a string when present`);
|
|
10528
|
-
}
|
|
10529
|
-
const credential = validateCredentialRef(e.credential, `${where}.credential`);
|
|
10530
|
-
let options;
|
|
10531
|
-
if (e.options !== void 0) {
|
|
10532
|
-
if (typeof e.options !== "object" || e.options === null || Array.isArray(e.options)) {
|
|
10533
|
-
throw new Error(`${where}.options must be an object when present`);
|
|
10534
|
-
}
|
|
10535
|
-
options = e.options;
|
|
10536
|
-
}
|
|
10537
|
-
return {
|
|
10538
|
-
id,
|
|
10539
|
-
provider,
|
|
10540
|
-
...typeof e.project === "string" ? { project: e.project } : {},
|
|
10541
|
-
credential,
|
|
10542
|
-
...options ? { options } : {}
|
|
10543
|
-
};
|
|
10544
|
-
}
|
|
10545
|
-
function validateCredentialRef(raw, where) {
|
|
10546
|
-
if (typeof raw === "string") {
|
|
10547
|
-
if (raw.length === 0) throw new Error(`${where} must be a non-empty string`);
|
|
10548
|
-
return raw;
|
|
10549
|
-
}
|
|
10550
|
-
if (typeof raw === "object" && raw !== null && !Array.isArray(raw)) {
|
|
10551
|
-
const fields = raw;
|
|
10552
|
-
const keys = Object.keys(fields);
|
|
10553
|
-
if (keys.length === 0) throw new Error(`${where} object must have at least one field`);
|
|
10554
|
-
for (const key of keys) {
|
|
10555
|
-
const v = fields[key];
|
|
10556
|
-
if (typeof v !== "string" || v.length === 0) {
|
|
10557
|
-
throw new Error(`${where}.${key} must be a non-empty string`);
|
|
10558
|
-
}
|
|
10559
|
-
}
|
|
10560
|
-
return fields;
|
|
10561
|
-
}
|
|
10562
|
-
throw new Error(`${where} must be a string or an object of strings`);
|
|
10563
|
-
}
|
|
10564
|
-
function resolveCredential(ref, env = process.env) {
|
|
10565
|
-
if (typeof ref === "string") {
|
|
10566
|
-
return { kind: "single", value: resolveRef(ref, env) };
|
|
10567
|
-
}
|
|
10568
|
-
const fields = {};
|
|
10569
|
-
for (const [key, value] of Object.entries(ref)) {
|
|
10570
|
-
fields[key] = resolveRef(value, env);
|
|
10571
|
-
}
|
|
10572
|
-
return { kind: "fields", fields };
|
|
10573
|
-
}
|
|
10574
|
-
function resolveRef(value, env) {
|
|
10575
|
-
if (value.length > 1 && value.startsWith("$")) {
|
|
10576
|
-
const varName = value.slice(1);
|
|
10577
|
-
const resolved = env[varName];
|
|
10578
|
-
if (resolved === void 0 || resolved.length === 0) {
|
|
10579
|
-
throw new EnvRefUnsetError(value, varName);
|
|
10580
|
-
}
|
|
10581
|
-
return resolved;
|
|
10582
|
-
}
|
|
10583
|
-
return value;
|
|
10584
|
-
}
|
|
10585
|
-
function connectorMatchesProject(entry, project) {
|
|
10586
|
-
return entry.project === void 0 || entry.project === project;
|
|
10587
|
-
}
|
|
10588
|
-
|
|
10589
10981
|
// src/connectors/registry.ts
|
|
10590
10982
|
var CLOUDFLARE_API_BASE_URL = "https://api.cloudflare.com/client/v4";
|
|
10591
10983
|
async function authProbe(input) {
|
|
@@ -10771,6 +11163,9 @@ function buildRegistration(entry, graph, env = process.env) {
|
|
|
10771
11163
|
return {
|
|
10772
11164
|
ok: true,
|
|
10773
11165
|
registration: {
|
|
11166
|
+
// Carry the entry id so the daemon can key this connector's poll-status
|
|
11167
|
+
// records to it (ADR-136).
|
|
11168
|
+
id: entry.id,
|
|
10774
11169
|
connector: built.connector,
|
|
10775
11170
|
credentials,
|
|
10776
11171
|
resolveTarget: built.resolveTarget,
|
|
@@ -10805,8 +11200,8 @@ init_auth();
|
|
|
10805
11200
|
|
|
10806
11201
|
// src/unrouted.ts
|
|
10807
11202
|
init_cjs_shims();
|
|
10808
|
-
var
|
|
10809
|
-
var
|
|
11203
|
+
var import_node_fs30 = require("fs");
|
|
11204
|
+
var import_node_path49 = __toESM(require("path"), 1);
|
|
10810
11205
|
function buildUnroutedSpanRecord(serviceName, traceId, now = /* @__PURE__ */ new Date()) {
|
|
10811
11206
|
return {
|
|
10812
11207
|
timestamp: now.toISOString(),
|
|
@@ -10816,34 +11211,34 @@ function buildUnroutedSpanRecord(serviceName, traceId, now = /* @__PURE__ */ new
|
|
|
10816
11211
|
};
|
|
10817
11212
|
}
|
|
10818
11213
|
async function appendUnroutedSpan(neatHome3, record) {
|
|
10819
|
-
const target =
|
|
10820
|
-
await
|
|
10821
|
-
await
|
|
11214
|
+
const target = import_node_path49.default.join(neatHome3, "errors.ndjson");
|
|
11215
|
+
await import_node_fs30.promises.mkdir(neatHome3, { recursive: true });
|
|
11216
|
+
await import_node_fs30.promises.appendFile(target, JSON.stringify(record) + "\n", "utf8");
|
|
10822
11217
|
}
|
|
10823
11218
|
function unroutedErrorsPath(neatHome3) {
|
|
10824
|
-
return
|
|
11219
|
+
return import_node_path49.default.join(neatHome3, "errors.ndjson");
|
|
10825
11220
|
}
|
|
10826
11221
|
|
|
10827
11222
|
// src/daemon.ts
|
|
10828
|
-
var
|
|
11223
|
+
var import_types45 = require("@neat.is/types");
|
|
10829
11224
|
function daemonJsonPath(scanPath) {
|
|
10830
|
-
return
|
|
11225
|
+
return import_node_path50.default.join(scanPath, "neat-out", "daemon.json");
|
|
10831
11226
|
}
|
|
10832
11227
|
function daemonsDiscoveryDir(home) {
|
|
10833
11228
|
const base = home && home.length > 0 ? home : neatHomeFromEnv();
|
|
10834
|
-
return
|
|
11229
|
+
return import_node_path50.default.join(base, "daemons");
|
|
10835
11230
|
}
|
|
10836
11231
|
function daemonDiscoveryPath(project, home) {
|
|
10837
|
-
return
|
|
11232
|
+
return import_node_path50.default.join(daemonsDiscoveryDir(home), `${sanitizeDiscoveryName(project)}.json`);
|
|
10838
11233
|
}
|
|
10839
11234
|
function sanitizeDiscoveryName(project) {
|
|
10840
11235
|
return project.replace(/[^A-Za-z0-9._-]/g, "_");
|
|
10841
11236
|
}
|
|
10842
11237
|
function neatHomeFromEnv() {
|
|
10843
11238
|
const env = process.env.NEAT_HOME;
|
|
10844
|
-
if (env && env.length > 0) return
|
|
11239
|
+
if (env && env.length > 0) return import_node_path50.default.resolve(env);
|
|
10845
11240
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
10846
|
-
return
|
|
11241
|
+
return import_node_path50.default.join(home, ".neat");
|
|
10847
11242
|
}
|
|
10848
11243
|
function resolveNeatVersion() {
|
|
10849
11244
|
if (process.env.NEAT_LOCAL_VERSION && process.env.NEAT_LOCAL_VERSION.length > 0) {
|
|
@@ -10875,7 +11270,7 @@ async function clearDaemonRecord(record, home) {
|
|
|
10875
11270
|
} catch {
|
|
10876
11271
|
}
|
|
10877
11272
|
try {
|
|
10878
|
-
await
|
|
11273
|
+
await import_node_fs31.promises.unlink(daemonDiscoveryPath(record.project, home));
|
|
10879
11274
|
} catch {
|
|
10880
11275
|
}
|
|
10881
11276
|
}
|
|
@@ -10898,11 +11293,11 @@ function teardownSlot(slot) {
|
|
|
10898
11293
|
}
|
|
10899
11294
|
}
|
|
10900
11295
|
function neatHomeFor(opts) {
|
|
10901
|
-
if (opts.neatHome && opts.neatHome.length > 0) return
|
|
11296
|
+
if (opts.neatHome && opts.neatHome.length > 0) return import_node_path50.default.resolve(opts.neatHome);
|
|
10902
11297
|
const env = process.env.NEAT_HOME;
|
|
10903
|
-
if (env && env.length > 0) return
|
|
11298
|
+
if (env && env.length > 0) return import_node_path50.default.resolve(env);
|
|
10904
11299
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
10905
|
-
return
|
|
11300
|
+
return import_node_path50.default.join(home, ".neat");
|
|
10906
11301
|
}
|
|
10907
11302
|
function routeSpanToProject(serviceName, projects) {
|
|
10908
11303
|
if (!serviceName) return DEFAULT_PROJECT;
|
|
@@ -10946,13 +11341,13 @@ function spanBelongsToSingleProject(graph, project, serviceName) {
|
|
|
10946
11341
|
if (!serviceName) return true;
|
|
10947
11342
|
if (serviceNameMatchesProject(serviceName, project)) return true;
|
|
10948
11343
|
return graph.someNode(
|
|
10949
|
-
(_id, attrs) => attrs.type ===
|
|
11344
|
+
(_id, attrs) => attrs.type === import_types45.NodeType.ServiceNode && attrs.name === serviceName
|
|
10950
11345
|
);
|
|
10951
11346
|
}
|
|
10952
11347
|
async function bootstrapProject(entry, connectors = [], neatHome3) {
|
|
10953
|
-
const paths = pathsForProject(entry.name,
|
|
11348
|
+
const paths = pathsForProject(entry.name, import_node_path50.default.join(entry.path, "neat-out"));
|
|
10954
11349
|
try {
|
|
10955
|
-
const stat = await
|
|
11350
|
+
const stat = await import_node_fs31.promises.stat(entry.path);
|
|
10956
11351
|
if (!stat.isDirectory()) {
|
|
10957
11352
|
throw new Error(`registered path ${entry.path} is not a directory`);
|
|
10958
11353
|
}
|
|
@@ -11005,7 +11400,11 @@ async function bootstrapProject(entry, connectors = [], neatHome3) {
|
|
|
11005
11400
|
{ projectDir: entry.path, credentials: registration.credentials },
|
|
11006
11401
|
graph,
|
|
11007
11402
|
registration.resolveTarget,
|
|
11008
|
-
|
|
11403
|
+
// `connectorId` is threaded through so every tick lands in the
|
|
11404
|
+
// in-process status tracker the connector-status endpoint reads
|
|
11405
|
+
// (ADR-136). Undefined for a programmatic registration, which records
|
|
11406
|
+
// nothing.
|
|
11407
|
+
{ intervalMs: registration.intervalMs, connectorId: registration.id }
|
|
11009
11408
|
)
|
|
11010
11409
|
);
|
|
11011
11410
|
const stopConnectors = () => {
|
|
@@ -11077,7 +11476,7 @@ async function startDaemon(opts = {}) {
|
|
|
11077
11476
|
const projectArg = typeof opts.project === "string" && opts.project.length > 0 ? opts.project : process.env.NEAT_PROJECT && process.env.NEAT_PROJECT.length > 0 ? process.env.NEAT_PROJECT : null;
|
|
11078
11477
|
const projectPathArg = opts.projectPath && opts.projectPath.length > 0 ? opts.projectPath : process.env.NEAT_PROJECT_PATH && process.env.NEAT_PROJECT_PATH.length > 0 ? process.env.NEAT_PROJECT_PATH : null;
|
|
11079
11478
|
const singleProject = projectArg;
|
|
11080
|
-
const singleProjectPath = singleProject && projectPathArg ?
|
|
11479
|
+
const singleProjectPath = singleProject && projectPathArg ? import_node_path50.default.resolve(projectPathArg) : null;
|
|
11081
11480
|
if (singleProject && !singleProjectPath) {
|
|
11082
11481
|
throw new Error(
|
|
11083
11482
|
`neatd: project "${singleProject}" given without a projectPath; pass NEAT_PROJECT_PATH alongside NEAT_PROJECT.`
|
|
@@ -11085,14 +11484,14 @@ async function startDaemon(opts = {}) {
|
|
|
11085
11484
|
}
|
|
11086
11485
|
if (!singleProject) {
|
|
11087
11486
|
try {
|
|
11088
|
-
await
|
|
11487
|
+
await import_node_fs31.promises.access(regPath);
|
|
11089
11488
|
} catch {
|
|
11090
11489
|
throw new Error(
|
|
11091
11490
|
`neatd: registry not found at ${regPath}. Run \`neat init <path>\` to register a project before starting the daemon.`
|
|
11092
11491
|
);
|
|
11093
11492
|
}
|
|
11094
11493
|
}
|
|
11095
|
-
const pidPath =
|
|
11494
|
+
const pidPath = import_node_path50.default.join(home, "neatd.pid");
|
|
11096
11495
|
await writeAtomically(pidPath, `${process.pid}
|
|
11097
11496
|
`);
|
|
11098
11497
|
const slots = /* @__PURE__ */ new Map();
|
|
@@ -11274,7 +11673,12 @@ async function startDaemon(opts = {}) {
|
|
|
11274
11673
|
// only this project (the dashboard pins to it), and the daemon-wide
|
|
11275
11674
|
// `/health` carries it at the top level for the spawn-reuse identity
|
|
11276
11675
|
// check. Absent for the legacy multi-project daemon.
|
|
11277
|
-
singleProject: singleProject && singleProjectPath ? { name: singleProject, path: singleProjectPath } : void 0
|
|
11676
|
+
singleProject: singleProject && singleProjectPath ? { name: singleProject, path: singleProjectPath } : void 0,
|
|
11677
|
+
// ADR-136 — the connector-status endpoint reads ~/.neat/connectors.json
|
|
11678
|
+
// through the same resolved home the slot bootstrap read it from, so a
|
|
11679
|
+
// daemon given an explicit NEAT_HOME serves status for the same file it
|
|
11680
|
+
// polls.
|
|
11681
|
+
connectorsHome: home
|
|
11278
11682
|
});
|
|
11279
11683
|
restAddress = await restApp.listen({ port: restPort, host });
|
|
11280
11684
|
console.log(
|
|
@@ -11286,7 +11690,7 @@ async function startDaemon(opts = {}) {
|
|
|
11286
11690
|
}
|
|
11287
11691
|
if (restApp) await restApp.close().catch(() => {
|
|
11288
11692
|
});
|
|
11289
|
-
await
|
|
11693
|
+
await import_node_fs31.promises.unlink(pidPath).catch(() => {
|
|
11290
11694
|
});
|
|
11291
11695
|
throw new Error(
|
|
11292
11696
|
`neatd: failed to bind REST on port ${restPort} \u2014 ${err.message}`
|
|
@@ -11412,7 +11816,7 @@ async function startDaemon(opts = {}) {
|
|
|
11412
11816
|
});
|
|
11413
11817
|
if (otlpApp) await otlpApp.close().catch(() => {
|
|
11414
11818
|
});
|
|
11415
|
-
await
|
|
11819
|
+
await import_node_fs31.promises.unlink(pidPath).catch(() => {
|
|
11416
11820
|
});
|
|
11417
11821
|
throw new Error(
|
|
11418
11822
|
`neatd: failed to bind OTLP on port ${otlpPort} \u2014 ${err.message}`
|
|
@@ -11447,7 +11851,7 @@ async function startDaemon(opts = {}) {
|
|
|
11447
11851
|
});
|
|
11448
11852
|
if (otlpApp) await otlpApp.close().catch(() => {
|
|
11449
11853
|
});
|
|
11450
|
-
await
|
|
11854
|
+
await import_node_fs31.promises.unlink(pidPath).catch(() => {
|
|
11451
11855
|
});
|
|
11452
11856
|
throw new Error(
|
|
11453
11857
|
`neatd: failed to write daemon.json for "${singleProject}" \u2014 ${err.message}`
|
|
@@ -11494,9 +11898,9 @@ async function startDaemon(opts = {}) {
|
|
|
11494
11898
|
let registryWatcher = null;
|
|
11495
11899
|
let reloadTimer = null;
|
|
11496
11900
|
if (!singleProject) try {
|
|
11497
|
-
const regDir =
|
|
11498
|
-
const regBase =
|
|
11499
|
-
registryWatcher = (0,
|
|
11901
|
+
const regDir = import_node_path50.default.dirname(regPath);
|
|
11902
|
+
const regBase = import_node_path50.default.basename(regPath);
|
|
11903
|
+
registryWatcher = (0, import_node_fs31.watch)(regDir, (_eventType, filename) => {
|
|
11500
11904
|
if (filename !== null && filename !== regBase) return;
|
|
11501
11905
|
if (reloadTimer) clearTimeout(reloadTimer);
|
|
11502
11906
|
reloadTimer = setTimeout(() => {
|
|
@@ -11545,7 +11949,7 @@ async function startDaemon(opts = {}) {
|
|
|
11545
11949
|
if (daemonRecord) {
|
|
11546
11950
|
await clearDaemonRecord(daemonRecord, home);
|
|
11547
11951
|
}
|
|
11548
|
-
await
|
|
11952
|
+
await import_node_fs31.promises.unlink(pidPath).catch(() => {
|
|
11549
11953
|
});
|
|
11550
11954
|
};
|
|
11551
11955
|
return {
|