@neat.is/core 0.4.28-dev.20260712 → 0.4.28-dev.20260714
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-5T6J3WKC.js → chunk-6HPRD53A.js} +734 -144
- package/dist/chunk-6HPRD53A.js.map +1 -0
- package/dist/{chunk-UJ6WBLIE.js → chunk-AOLXZWRU.js} +163 -345
- package/dist/chunk-AOLXZWRU.js.map +1 -0
- package/dist/cli.cjs +1177 -814
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +9 -9
- package/dist/index.cjs +787 -381
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -2
- package/dist/neatd.cjs +852 -446
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +2 -2
- package/dist/server.cjs +510 -92
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +1 -1
- package/package.json +2 -2
- package/dist/chunk-5T6J3WKC.js.map +0 -1
- package/dist/chunk-UJ6WBLIE.js.map +0 -1
package/dist/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",
|
|
@@ -9009,9 +9471,11 @@ async function buildApi(opts) {
|
|
|
9009
9471
|
trustProxy,
|
|
9010
9472
|
publicRead
|
|
9011
9473
|
});
|
|
9474
|
+
const requiresAuth = authToken !== void 0 && authToken.length > 0 && trustProxy !== true;
|
|
9012
9475
|
app.get("/api/config", async () => ({
|
|
9013
9476
|
publicRead: publicRead === true,
|
|
9014
|
-
authProxy: trustProxy === true
|
|
9477
|
+
authProxy: trustProxy === true,
|
|
9478
|
+
requiresAuth
|
|
9015
9479
|
}));
|
|
9016
9480
|
const startedAt = opts.startedAt ?? Date.now();
|
|
9017
9481
|
const registry = buildLegacyRegistry(opts);
|
|
@@ -9041,7 +9505,8 @@ async function buildApi(opts) {
|
|
|
9041
9505
|
staleEventsPathFor,
|
|
9042
9506
|
policyFilePathFor,
|
|
9043
9507
|
bootstrap: opts.bootstrap,
|
|
9044
|
-
singleProject: opts.singleProject?.name
|
|
9508
|
+
singleProject: opts.singleProject?.name,
|
|
9509
|
+
connectorsHome: opts.connectorsHome
|
|
9045
9510
|
};
|
|
9046
9511
|
app.get("/health", async () => {
|
|
9047
9512
|
const uptimeMs = Date.now() - startedAt;
|
|
@@ -9126,8 +9591,8 @@ init_otel_grpc();
|
|
|
9126
9591
|
|
|
9127
9592
|
// src/daemon.ts
|
|
9128
9593
|
init_cjs_shims();
|
|
9129
|
-
var
|
|
9130
|
-
var
|
|
9594
|
+
var import_node_fs31 = require("fs");
|
|
9595
|
+
var import_node_path50 = __toESM(require("path"), 1);
|
|
9131
9596
|
var import_node_module = require("module");
|
|
9132
9597
|
init_otel();
|
|
9133
9598
|
|
|
@@ -9191,16 +9656,31 @@ function startConnectorPollLoop(connector, ctx, graph, resolveTarget, options =
|
|
|
9191
9656
|
let stopped = false;
|
|
9192
9657
|
let since = ctx.since;
|
|
9193
9658
|
const intervalMs = options.intervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
9659
|
+
const connectorId = options.connectorId;
|
|
9194
9660
|
const onError = options.onError ?? ((err) => console.error(`[neatd] connector poll failed (${connector.provider})`, err));
|
|
9195
9661
|
const tick = () => {
|
|
9196
9662
|
if (stopped) return;
|
|
9197
9663
|
void (async () => {
|
|
9198
9664
|
const tickStartedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
9199
9665
|
try {
|
|
9200
|
-
await runConnectorPoll(connector, { ...ctx, since }, graph, resolveTarget);
|
|
9666
|
+
const result = await runConnectorPoll(connector, { ...ctx, since }, graph, resolveTarget);
|
|
9201
9667
|
since = tickStartedAt;
|
|
9668
|
+
if (connectorId) {
|
|
9669
|
+
recordConnectorPoll(connectorId, {
|
|
9670
|
+
outcome: "ok",
|
|
9671
|
+
at: tickStartedAt,
|
|
9672
|
+
signalsLastPoll: result.signalCount
|
|
9673
|
+
});
|
|
9674
|
+
}
|
|
9202
9675
|
} catch (err) {
|
|
9203
9676
|
onError(err);
|
|
9677
|
+
if (connectorId) {
|
|
9678
|
+
recordConnectorPoll(connectorId, {
|
|
9679
|
+
outcome: "error",
|
|
9680
|
+
at: tickStartedAt,
|
|
9681
|
+
error: sanitizePollError(err)
|
|
9682
|
+
});
|
|
9683
|
+
}
|
|
9204
9684
|
}
|
|
9205
9685
|
})();
|
|
9206
9686
|
};
|
|
@@ -9499,6 +9979,30 @@ function buildEdgeLogsQuery(limit) {
|
|
|
9499
9979
|
`limit ${safeLimit}`
|
|
9500
9980
|
].join("\n");
|
|
9501
9981
|
}
|
|
9982
|
+
function logsAllHttpFailureMessage(status2) {
|
|
9983
|
+
if (status2 === 401 || status2 === 403) {
|
|
9984
|
+
return `supabase connector: logs.all request rejected (HTTP ${status2}). Check the Management API token, its analytics read scope, and --api-project-ref.`;
|
|
9985
|
+
}
|
|
9986
|
+
if (status2 === 404) {
|
|
9987
|
+
return "supabase connector: logs.all project not found (HTTP 404). Check --api-project-ref.";
|
|
9988
|
+
}
|
|
9989
|
+
if (status2 === 429) {
|
|
9990
|
+
return "supabase connector: logs.all rate-limited (HTTP 429). The connector will retry on the next poll; reduce poll cadence if this persists.";
|
|
9991
|
+
}
|
|
9992
|
+
if (status2 === 400) {
|
|
9993
|
+
return "supabase connector: logs.all query rejected (HTTP 400). Provider details redacted; confirm the live log-query dialect before shipping this connector.";
|
|
9994
|
+
}
|
|
9995
|
+
return `supabase connector: logs.all request failed (HTTP ${status2}); provider details redacted.`;
|
|
9996
|
+
}
|
|
9997
|
+
function providerErrorDetails(error) {
|
|
9998
|
+
if (!error || typeof error === "string") return "";
|
|
9999
|
+
const parts = [];
|
|
10000
|
+
if (typeof error.code === "number") parts.push(`code ${error.code}`);
|
|
10001
|
+
if (typeof error.status === "string" && /^[A-Z0-9_.-]+$/.test(error.status)) {
|
|
10002
|
+
parts.push(`status ${error.status}`);
|
|
10003
|
+
}
|
|
10004
|
+
return parts.length > 0 ? ` (${parts.join(", ")})` : "";
|
|
10005
|
+
}
|
|
9502
10006
|
async function fetchSupabaseEdgeLogs(config, token, startIso, endIso, fetchImpl = fetch) {
|
|
9503
10007
|
const baseUrl = config.managementApiUrl ?? DEFAULT_SUPABASE_MANAGEMENT_API_URL;
|
|
9504
10008
|
const url = new URL(`${baseUrl}/v1/projects/${config.apiProjectRef}/analytics/endpoints/logs.all`);
|
|
@@ -9513,12 +10017,18 @@ async function fetchSupabaseEdgeLogs(config, token, startIso, endIso, fetchImpl
|
|
|
9513
10017
|
{ provider: "supabase", accountKey: config.apiProjectRef, fetchImpl }
|
|
9514
10018
|
);
|
|
9515
10019
|
if (!res.ok) {
|
|
9516
|
-
throw new Error(
|
|
10020
|
+
throw new Error(logsAllHttpFailureMessage(res.status));
|
|
10021
|
+
}
|
|
10022
|
+
let body;
|
|
10023
|
+
try {
|
|
10024
|
+
body = await res.json();
|
|
10025
|
+
} catch {
|
|
10026
|
+
throw new Error("supabase connector: logs.all returned invalid JSON; provider details redacted.");
|
|
9517
10027
|
}
|
|
9518
|
-
const body = await res.json();
|
|
9519
10028
|
if (body.error) {
|
|
9520
|
-
|
|
9521
|
-
|
|
10029
|
+
throw new Error(
|
|
10030
|
+
`supabase connector: logs.all returned a provider error${providerErrorDetails(body.error)}; provider message redacted.`
|
|
10031
|
+
);
|
|
9522
10032
|
}
|
|
9523
10033
|
return body.result ?? [];
|
|
9524
10034
|
}
|
|
@@ -9550,10 +10060,10 @@ var SUPABASE_RPC_TARGET_KIND = "supabase-rpc";
|
|
|
9550
10060
|
// src/connectors/supabase/map.ts
|
|
9551
10061
|
var REST_RPC_PATH_RE = /^\/rest\/v1\/rpc\/([^/?]+)/;
|
|
9552
10062
|
var REST_TABLE_PATH_RE = /^\/rest\/v1\/([^/?]+)/;
|
|
9553
|
-
function targetFromRestPath(
|
|
9554
|
-
const rpcMatch = REST_RPC_PATH_RE.exec(
|
|
10063
|
+
function targetFromRestPath(path51) {
|
|
10064
|
+
const rpcMatch = REST_RPC_PATH_RE.exec(path51);
|
|
9555
10065
|
if (rpcMatch) return { targetKind: SUPABASE_RPC_TARGET_KIND, name: rpcMatch[1] };
|
|
9556
|
-
const tableMatch = REST_TABLE_PATH_RE.exec(
|
|
10066
|
+
const tableMatch = REST_TABLE_PATH_RE.exec(path51);
|
|
9557
10067
|
if (tableMatch) return { targetKind: SUPABASE_TABLE_TARGET_KIND, name: tableMatch[1] };
|
|
9558
10068
|
return null;
|
|
9559
10069
|
}
|
|
@@ -9656,19 +10166,19 @@ async function fetchPgStatStatements(connectionString, limit = DEFAULT_STATEMENT
|
|
|
9656
10166
|
|
|
9657
10167
|
// src/connectors/supabase/resolve.ts
|
|
9658
10168
|
init_cjs_shims();
|
|
9659
|
-
var
|
|
10169
|
+
var import_types35 = require("@neat.is/types");
|
|
9660
10170
|
function createSupabaseResolveTarget(graph, config) {
|
|
9661
10171
|
return (signal, _ctx) => {
|
|
9662
10172
|
if (signal.targetKind !== SUPABASE_TABLE_TARGET_KIND && signal.targetKind !== SUPABASE_RPC_TARGET_KIND) {
|
|
9663
10173
|
return null;
|
|
9664
10174
|
}
|
|
9665
|
-
const subResourceId = (0,
|
|
10175
|
+
const subResourceId = (0, import_types35.infraId)(signal.targetKind, `${config.nodeRef}/${signal.targetName}`);
|
|
9666
10176
|
if (graph.hasNode(subResourceId)) {
|
|
9667
|
-
return { targetNodeId: subResourceId, serviceName: config.serviceName, edgeType:
|
|
10177
|
+
return { targetNodeId: subResourceId, serviceName: config.serviceName, edgeType: import_types35.EdgeType.CALLS };
|
|
9668
10178
|
}
|
|
9669
|
-
const projectLevelId = (0,
|
|
10179
|
+
const projectLevelId = (0, import_types35.infraId)("supabase", config.nodeRef);
|
|
9670
10180
|
if (graph.hasNode(projectLevelId)) {
|
|
9671
|
-
return { targetNodeId: projectLevelId, serviceName: config.serviceName, edgeType:
|
|
10181
|
+
return { targetNodeId: projectLevelId, serviceName: config.serviceName, edgeType: import_types35.EdgeType.CALLS };
|
|
9672
10182
|
}
|
|
9673
10183
|
return null;
|
|
9674
10184
|
};
|
|
@@ -9676,6 +10186,35 @@ function createSupabaseResolveTarget(graph, config) {
|
|
|
9676
10186
|
|
|
9677
10187
|
// src/connectors/supabase/index.ts
|
|
9678
10188
|
var DEFAULT_MAX_LOOKBACK_MS = 24 * 60 * 60 * 1e3;
|
|
10189
|
+
function errorCode(err) {
|
|
10190
|
+
const code = err?.code;
|
|
10191
|
+
return typeof code === "string" && code.length > 0 ? code : void 0;
|
|
10192
|
+
}
|
|
10193
|
+
function describeSupabasePostgresSurfaceFailure(projectRef, err) {
|
|
10194
|
+
const code = errorCode(err);
|
|
10195
|
+
let reason;
|
|
10196
|
+
switch (code) {
|
|
10197
|
+
case "42501":
|
|
10198
|
+
reason = "permission denied; grant pg_read_all_stats to the configured Postgres role";
|
|
10199
|
+
break;
|
|
10200
|
+
case "42P01":
|
|
10201
|
+
case "42704":
|
|
10202
|
+
reason = "pg_stat_statements is not enabled or visible to the configured Postgres role";
|
|
10203
|
+
break;
|
|
10204
|
+
case "28P01":
|
|
10205
|
+
case "28000":
|
|
10206
|
+
reason = "Postgres credential rejected";
|
|
10207
|
+
break;
|
|
10208
|
+
case "3D000":
|
|
10209
|
+
reason = "database not found";
|
|
10210
|
+
break;
|
|
10211
|
+
default: {
|
|
10212
|
+
const name = err instanceof Error && err.name ? err.name : "Error";
|
|
10213
|
+
reason = code ? `${name} ${code}` : name;
|
|
10214
|
+
}
|
|
10215
|
+
}
|
|
10216
|
+
return `supabase connector: pg_stat_statements surface unavailable for project ${projectRef} (${reason}); continuing with Management API log surface.`;
|
|
10217
|
+
}
|
|
9679
10218
|
var SupabaseConnector = class {
|
|
9680
10219
|
// `deps.fetchPgStatStatements` defaults to the real Postgres-backed
|
|
9681
10220
|
// implementation; tests override it to exercise the "both surfaces
|
|
@@ -9707,12 +10246,18 @@ var SupabaseConnector = class {
|
|
|
9707
10246
|
const signals = mapEdgeLogRowsToSignals(logRows);
|
|
9708
10247
|
if (creds.postgresConnectionString) {
|
|
9709
10248
|
const fetchStatements = this.deps.fetchPgStatStatements ?? fetchPgStatStatements;
|
|
9710
|
-
|
|
9711
|
-
|
|
9712
|
-
|
|
9713
|
-
|
|
9714
|
-
|
|
9715
|
-
|
|
10249
|
+
try {
|
|
10250
|
+
const statementRows = await fetchStatements(
|
|
10251
|
+
creds.postgresConnectionString,
|
|
10252
|
+
this.config.statementLimit ?? DEFAULT_STATEMENT_LIMIT,
|
|
10253
|
+
this.config.apiProjectRef
|
|
10254
|
+
);
|
|
10255
|
+
signals.push(...diffPgStatStatementsToSignals(statementRows, this.statementBaselines, now.toISOString()));
|
|
10256
|
+
} catch (err) {
|
|
10257
|
+
const summary = describeSupabasePostgresSurfaceFailure(this.config.apiProjectRef, err);
|
|
10258
|
+
if (this.deps.onPostgresSurfaceError) this.deps.onPostgresSurfaceError(err, summary);
|
|
10259
|
+
else console.warn(summary);
|
|
10260
|
+
}
|
|
9716
10261
|
}
|
|
9717
10262
|
return signals;
|
|
9718
10263
|
}
|
|
@@ -9726,7 +10271,7 @@ function createSupabaseConnector(graph, config, deps = {}) {
|
|
|
9726
10271
|
|
|
9727
10272
|
// src/connectors/railway/index.ts
|
|
9728
10273
|
init_cjs_shims();
|
|
9729
|
-
var
|
|
10274
|
+
var import_types39 = require("@neat.is/types");
|
|
9730
10275
|
|
|
9731
10276
|
// src/connectors/railway/client.ts
|
|
9732
10277
|
init_cjs_shims();
|
|
@@ -9871,7 +10416,7 @@ function buildRailwayRouteIndex(graph, serviceName) {
|
|
|
9871
10416
|
const out = [];
|
|
9872
10417
|
graph.forEachNode((_id, attrs) => {
|
|
9873
10418
|
const node = attrs;
|
|
9874
|
-
if (node.type !==
|
|
10419
|
+
if (node.type !== import_types39.NodeType.RouteNode) return;
|
|
9875
10420
|
const route = attrs;
|
|
9876
10421
|
if (route.service !== serviceName) return;
|
|
9877
10422
|
out.push({
|
|
@@ -9967,12 +10512,12 @@ function createRailwayResolveTarget(config) {
|
|
|
9967
10512
|
const serviceName = config.serviceNameById[config.serviceId];
|
|
9968
10513
|
if (!serviceName) return null;
|
|
9969
10514
|
if (signal.targetKind === ROUTE_TARGET_KIND) {
|
|
9970
|
-
return { targetNodeId: signal.targetName, serviceName, edgeType:
|
|
10515
|
+
return { targetNodeId: signal.targetName, serviceName, edgeType: import_types39.EdgeType.CALLS };
|
|
9971
10516
|
}
|
|
9972
10517
|
if (signal.targetKind === PEER_SERVICE_TARGET_KIND) {
|
|
9973
10518
|
const peerName = config.serviceNameById[signal.targetName];
|
|
9974
10519
|
if (!peerName) return null;
|
|
9975
|
-
return { targetNodeId: (0,
|
|
10520
|
+
return { targetNodeId: (0, import_types39.serviceId)(peerName), serviceName, edgeType: import_types39.EdgeType.CONNECTS_TO };
|
|
9976
10521
|
}
|
|
9977
10522
|
return null;
|
|
9978
10523
|
};
|
|
@@ -10087,9 +10632,9 @@ function parseFirebaseTargetName(targetName) {
|
|
|
10087
10632
|
const secondSep = rest.indexOf(FIELD_SEP);
|
|
10088
10633
|
if (secondSep === -1) return null;
|
|
10089
10634
|
const method = rest.slice(0, secondSep);
|
|
10090
|
-
const
|
|
10091
|
-
if (!resourceName || !method || !
|
|
10092
|
-
return { resourceName, method, path:
|
|
10635
|
+
const path51 = rest.slice(secondSep + 1);
|
|
10636
|
+
if (!resourceName || !method || !path51) return null;
|
|
10637
|
+
return { resourceName, method, path: path51 };
|
|
10093
10638
|
}
|
|
10094
10639
|
function resourceNameFor(type, labels) {
|
|
10095
10640
|
if (!labels) return null;
|
|
@@ -10126,14 +10671,14 @@ function mapLogEntryToSignal(entry) {
|
|
|
10126
10671
|
if (!req) return null;
|
|
10127
10672
|
if (!req.requestMethod) return null;
|
|
10128
10673
|
const method = req.requestMethod.toUpperCase();
|
|
10129
|
-
const
|
|
10130
|
-
if (
|
|
10674
|
+
const path51 = pathFromRequestUrl(req.requestUrl);
|
|
10675
|
+
if (path51 === null) return null;
|
|
10131
10676
|
const timestamp = entry.timestamp;
|
|
10132
10677
|
if (!timestamp) return null;
|
|
10133
10678
|
const isError = typeof req.status === "number" && req.status >= ERROR_STATUS_THRESHOLD2;
|
|
10134
10679
|
return {
|
|
10135
10680
|
targetKind: resourceType,
|
|
10136
|
-
targetName: packFirebaseTargetName({ resourceName, method, path:
|
|
10681
|
+
targetName: packFirebaseTargetName({ resourceName, method, path: path51 }),
|
|
10137
10682
|
callCount: 1,
|
|
10138
10683
|
errorCount: isError ? 1 : 0,
|
|
10139
10684
|
lastObservedIso: timestamp
|
|
@@ -10150,7 +10695,7 @@ function mapLogEntriesToSignals(entries) {
|
|
|
10150
10695
|
|
|
10151
10696
|
// src/connectors/firebase/resolve.ts
|
|
10152
10697
|
init_cjs_shims();
|
|
10153
|
-
var
|
|
10698
|
+
var import_types40 = require("@neat.is/types");
|
|
10154
10699
|
function neatServiceNameFor(resourceType, resourceName, serviceMap) {
|
|
10155
10700
|
switch (resourceType) {
|
|
10156
10701
|
case "cloud_function":
|
|
@@ -10165,7 +10710,7 @@ function routeEntriesFor(graph, serviceName) {
|
|
|
10165
10710
|
const entries = [];
|
|
10166
10711
|
graph.forEachNode((_id, attrs) => {
|
|
10167
10712
|
const node = attrs;
|
|
10168
|
-
if (node.type !==
|
|
10713
|
+
if (node.type !== import_types40.NodeType.RouteNode) return;
|
|
10169
10714
|
const route = attrs;
|
|
10170
10715
|
if (route.service !== serviceName) return;
|
|
10171
10716
|
entries.push({
|
|
@@ -10197,7 +10742,7 @@ function createFirebaseResolveTarget(graph, serviceMap) {
|
|
|
10197
10742
|
return {
|
|
10198
10743
|
targetNodeId: match.routeNodeId,
|
|
10199
10744
|
serviceName,
|
|
10200
|
-
edgeType:
|
|
10745
|
+
edgeType: import_types40.EdgeType.CALLS
|
|
10201
10746
|
};
|
|
10202
10747
|
};
|
|
10203
10748
|
}
|
|
@@ -10224,7 +10769,7 @@ init_cjs_shims();
|
|
|
10224
10769
|
|
|
10225
10770
|
// src/connectors/cloudflare/connector.ts
|
|
10226
10771
|
init_cjs_shims();
|
|
10227
|
-
var
|
|
10772
|
+
var import_types42 = require("@neat.is/types");
|
|
10228
10773
|
|
|
10229
10774
|
// src/connectors/cloudflare/client.ts
|
|
10230
10775
|
init_cjs_shims();
|
|
@@ -10331,7 +10876,7 @@ function mapEventToSignal(event) {
|
|
|
10331
10876
|
if (typeof timestampMs !== "number" || !Number.isFinite(timestampMs)) return null;
|
|
10332
10877
|
const statusCode = metadata?.statusCode;
|
|
10333
10878
|
const isError = typeof statusCode === "number" && statusCode >= ERROR_STATUS_THRESHOLD3;
|
|
10334
|
-
const
|
|
10879
|
+
const path51 = metadata?.trigger ? parsePathFromTrigger(metadata.trigger) : void 0;
|
|
10335
10880
|
return {
|
|
10336
10881
|
targetKind: CLOUDFLARE_TARGET_KIND,
|
|
10337
10882
|
targetName: scriptName,
|
|
@@ -10339,7 +10884,7 @@ function mapEventToSignal(event) {
|
|
|
10339
10884
|
errorCount: isError ? 1 : 0,
|
|
10340
10885
|
lastObservedIso: new Date(timestampMs).toISOString(),
|
|
10341
10886
|
method,
|
|
10342
|
-
...
|
|
10887
|
+
...path51 ? { path: path51 } : {},
|
|
10343
10888
|
...typeof statusCode === "number" ? { statusCode } : {},
|
|
10344
10889
|
...typeof metadata?.duration === "number" ? { duration: metadata.duration } : {}
|
|
10345
10890
|
};
|
|
@@ -10379,19 +10924,19 @@ function findTaggedWorkerFileNode(graph, workerName) {
|
|
|
10379
10924
|
graph.forEachNode((id, attrs) => {
|
|
10380
10925
|
if (found) return;
|
|
10381
10926
|
const a = attrs;
|
|
10382
|
-
if (a.type ===
|
|
10927
|
+
if (a.type === import_types42.NodeType.FileNode && a.platform === "cloudflare" && a.platformName === workerName) {
|
|
10383
10928
|
found = id;
|
|
10384
10929
|
}
|
|
10385
10930
|
});
|
|
10386
10931
|
return found;
|
|
10387
10932
|
}
|
|
10388
|
-
function findMatchingRouteNode(graph, serviceName, method,
|
|
10389
|
-
const normalizedPath = normalizePathTemplate(
|
|
10933
|
+
function findMatchingRouteNode(graph, serviceName, method, path51) {
|
|
10934
|
+
const normalizedPath = normalizePathTemplate(path51);
|
|
10390
10935
|
let found = null;
|
|
10391
10936
|
graph.forEachNode((id, attrs) => {
|
|
10392
10937
|
if (found) return;
|
|
10393
10938
|
const a = attrs;
|
|
10394
|
-
if (a.type !==
|
|
10939
|
+
if (a.type !== import_types42.NodeType.RouteNode || a.service !== serviceName) return;
|
|
10395
10940
|
if (!a.pathTemplate || normalizePathTemplate(a.pathTemplate) !== normalizedPath) return;
|
|
10396
10941
|
const routeMethod = (a.method ?? "").toUpperCase();
|
|
10397
10942
|
if (routeMethod !== "ALL" && routeMethod !== method) return;
|
|
@@ -10403,18 +10948,18 @@ function createCloudflareResolveTarget(config, graph) {
|
|
|
10403
10948
|
return (signal) => {
|
|
10404
10949
|
if (signal.targetKind !== CLOUDFLARE_TARGET_KIND) return null;
|
|
10405
10950
|
const scriptName = signal.targetName;
|
|
10406
|
-
const { method, path:
|
|
10951
|
+
const { method, path: path51 } = signal;
|
|
10407
10952
|
const resolveRouteGrain = (serviceName, wholeFileId) => {
|
|
10408
|
-
if (!method || !
|
|
10409
|
-
return findMatchingRouteNode(graph, serviceName, method,
|
|
10953
|
+
if (!method || !path51) return wholeFileId;
|
|
10954
|
+
return findMatchingRouteNode(graph, serviceName, method, path51) ?? wholeFileId;
|
|
10410
10955
|
};
|
|
10411
10956
|
const mapping = config.workers?.[scriptName];
|
|
10412
10957
|
if (mapping) {
|
|
10413
|
-
const wholeFileId = (0,
|
|
10958
|
+
const wholeFileId = (0, import_types42.fileId)(mapping.service, mapping.entryFile);
|
|
10414
10959
|
return {
|
|
10415
10960
|
targetNodeId: resolveRouteGrain(mapping.service, wholeFileId),
|
|
10416
10961
|
serviceName: mapping.service,
|
|
10417
|
-
edgeType:
|
|
10962
|
+
edgeType: import_types42.EdgeType.CALLS
|
|
10418
10963
|
};
|
|
10419
10964
|
}
|
|
10420
10965
|
const taggedFileId = findTaggedWorkerFileNode(graph, scriptName);
|
|
@@ -10423,169 +10968,18 @@ function createCloudflareResolveTarget(config, graph) {
|
|
|
10423
10968
|
return {
|
|
10424
10969
|
targetNodeId: resolveRouteGrain(fileNode.service, taggedFileId),
|
|
10425
10970
|
serviceName: fileNode.service,
|
|
10426
|
-
edgeType:
|
|
10971
|
+
edgeType: import_types42.EdgeType.CALLS
|
|
10427
10972
|
};
|
|
10428
10973
|
}
|
|
10429
10974
|
return {
|
|
10430
|
-
targetNodeId: (0,
|
|
10975
|
+
targetNodeId: (0, import_types42.infraId)("cloudflare-worker", scriptName),
|
|
10431
10976
|
serviceName: scriptName,
|
|
10432
|
-
edgeType:
|
|
10977
|
+
edgeType: import_types42.EdgeType.CALLS,
|
|
10433
10978
|
ensureInfraNode: { kind: "cloudflare-worker", name: scriptName, provider: "cloudflare" }
|
|
10434
10979
|
};
|
|
10435
10980
|
};
|
|
10436
10981
|
}
|
|
10437
10982
|
|
|
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
10983
|
// src/connectors/registry.ts
|
|
10590
10984
|
var CLOUDFLARE_API_BASE_URL = "https://api.cloudflare.com/client/v4";
|
|
10591
10985
|
async function authProbe(input) {
|
|
@@ -10771,6 +11165,9 @@ function buildRegistration(entry, graph, env = process.env) {
|
|
|
10771
11165
|
return {
|
|
10772
11166
|
ok: true,
|
|
10773
11167
|
registration: {
|
|
11168
|
+
// Carry the entry id so the daemon can key this connector's poll-status
|
|
11169
|
+
// records to it (ADR-136).
|
|
11170
|
+
id: entry.id,
|
|
10774
11171
|
connector: built.connector,
|
|
10775
11172
|
credentials,
|
|
10776
11173
|
resolveTarget: built.resolveTarget,
|
|
@@ -10805,8 +11202,8 @@ init_auth();
|
|
|
10805
11202
|
|
|
10806
11203
|
// src/unrouted.ts
|
|
10807
11204
|
init_cjs_shims();
|
|
10808
|
-
var
|
|
10809
|
-
var
|
|
11205
|
+
var import_node_fs30 = require("fs");
|
|
11206
|
+
var import_node_path49 = __toESM(require("path"), 1);
|
|
10810
11207
|
function buildUnroutedSpanRecord(serviceName, traceId, now = /* @__PURE__ */ new Date()) {
|
|
10811
11208
|
return {
|
|
10812
11209
|
timestamp: now.toISOString(),
|
|
@@ -10816,34 +11213,34 @@ function buildUnroutedSpanRecord(serviceName, traceId, now = /* @__PURE__ */ new
|
|
|
10816
11213
|
};
|
|
10817
11214
|
}
|
|
10818
11215
|
async function appendUnroutedSpan(neatHome3, record) {
|
|
10819
|
-
const target =
|
|
10820
|
-
await
|
|
10821
|
-
await
|
|
11216
|
+
const target = import_node_path49.default.join(neatHome3, "errors.ndjson");
|
|
11217
|
+
await import_node_fs30.promises.mkdir(neatHome3, { recursive: true });
|
|
11218
|
+
await import_node_fs30.promises.appendFile(target, JSON.stringify(record) + "\n", "utf8");
|
|
10822
11219
|
}
|
|
10823
11220
|
function unroutedErrorsPath(neatHome3) {
|
|
10824
|
-
return
|
|
11221
|
+
return import_node_path49.default.join(neatHome3, "errors.ndjson");
|
|
10825
11222
|
}
|
|
10826
11223
|
|
|
10827
11224
|
// src/daemon.ts
|
|
10828
|
-
var
|
|
11225
|
+
var import_types45 = require("@neat.is/types");
|
|
10829
11226
|
function daemonJsonPath(scanPath) {
|
|
10830
|
-
return
|
|
11227
|
+
return import_node_path50.default.join(scanPath, "neat-out", "daemon.json");
|
|
10831
11228
|
}
|
|
10832
11229
|
function daemonsDiscoveryDir(home) {
|
|
10833
11230
|
const base = home && home.length > 0 ? home : neatHomeFromEnv();
|
|
10834
|
-
return
|
|
11231
|
+
return import_node_path50.default.join(base, "daemons");
|
|
10835
11232
|
}
|
|
10836
11233
|
function daemonDiscoveryPath(project, home) {
|
|
10837
|
-
return
|
|
11234
|
+
return import_node_path50.default.join(daemonsDiscoveryDir(home), `${sanitizeDiscoveryName(project)}.json`);
|
|
10838
11235
|
}
|
|
10839
11236
|
function sanitizeDiscoveryName(project) {
|
|
10840
11237
|
return project.replace(/[^A-Za-z0-9._-]/g, "_");
|
|
10841
11238
|
}
|
|
10842
11239
|
function neatHomeFromEnv() {
|
|
10843
11240
|
const env = process.env.NEAT_HOME;
|
|
10844
|
-
if (env && env.length > 0) return
|
|
11241
|
+
if (env && env.length > 0) return import_node_path50.default.resolve(env);
|
|
10845
11242
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
10846
|
-
return
|
|
11243
|
+
return import_node_path50.default.join(home, ".neat");
|
|
10847
11244
|
}
|
|
10848
11245
|
function resolveNeatVersion() {
|
|
10849
11246
|
if (process.env.NEAT_LOCAL_VERSION && process.env.NEAT_LOCAL_VERSION.length > 0) {
|
|
@@ -10875,7 +11272,7 @@ async function clearDaemonRecord(record, home) {
|
|
|
10875
11272
|
} catch {
|
|
10876
11273
|
}
|
|
10877
11274
|
try {
|
|
10878
|
-
await
|
|
11275
|
+
await import_node_fs31.promises.unlink(daemonDiscoveryPath(record.project, home));
|
|
10879
11276
|
} catch {
|
|
10880
11277
|
}
|
|
10881
11278
|
}
|
|
@@ -10898,11 +11295,11 @@ function teardownSlot(slot) {
|
|
|
10898
11295
|
}
|
|
10899
11296
|
}
|
|
10900
11297
|
function neatHomeFor(opts) {
|
|
10901
|
-
if (opts.neatHome && opts.neatHome.length > 0) return
|
|
11298
|
+
if (opts.neatHome && opts.neatHome.length > 0) return import_node_path50.default.resolve(opts.neatHome);
|
|
10902
11299
|
const env = process.env.NEAT_HOME;
|
|
10903
|
-
if (env && env.length > 0) return
|
|
11300
|
+
if (env && env.length > 0) return import_node_path50.default.resolve(env);
|
|
10904
11301
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
10905
|
-
return
|
|
11302
|
+
return import_node_path50.default.join(home, ".neat");
|
|
10906
11303
|
}
|
|
10907
11304
|
function routeSpanToProject(serviceName, projects) {
|
|
10908
11305
|
if (!serviceName) return DEFAULT_PROJECT;
|
|
@@ -10946,13 +11343,13 @@ function spanBelongsToSingleProject(graph, project, serviceName) {
|
|
|
10946
11343
|
if (!serviceName) return true;
|
|
10947
11344
|
if (serviceNameMatchesProject(serviceName, project)) return true;
|
|
10948
11345
|
return graph.someNode(
|
|
10949
|
-
(_id, attrs) => attrs.type ===
|
|
11346
|
+
(_id, attrs) => attrs.type === import_types45.NodeType.ServiceNode && attrs.name === serviceName
|
|
10950
11347
|
);
|
|
10951
11348
|
}
|
|
10952
11349
|
async function bootstrapProject(entry, connectors = [], neatHome3) {
|
|
10953
|
-
const paths = pathsForProject(entry.name,
|
|
11350
|
+
const paths = pathsForProject(entry.name, import_node_path50.default.join(entry.path, "neat-out"));
|
|
10954
11351
|
try {
|
|
10955
|
-
const stat = await
|
|
11352
|
+
const stat = await import_node_fs31.promises.stat(entry.path);
|
|
10956
11353
|
if (!stat.isDirectory()) {
|
|
10957
11354
|
throw new Error(`registered path ${entry.path} is not a directory`);
|
|
10958
11355
|
}
|
|
@@ -11005,7 +11402,11 @@ async function bootstrapProject(entry, connectors = [], neatHome3) {
|
|
|
11005
11402
|
{ projectDir: entry.path, credentials: registration.credentials },
|
|
11006
11403
|
graph,
|
|
11007
11404
|
registration.resolveTarget,
|
|
11008
|
-
|
|
11405
|
+
// `connectorId` is threaded through so every tick lands in the
|
|
11406
|
+
// in-process status tracker the connector-status endpoint reads
|
|
11407
|
+
// (ADR-136). Undefined for a programmatic registration, which records
|
|
11408
|
+
// nothing.
|
|
11409
|
+
{ intervalMs: registration.intervalMs, connectorId: registration.id }
|
|
11009
11410
|
)
|
|
11010
11411
|
);
|
|
11011
11412
|
const stopConnectors = () => {
|
|
@@ -11077,7 +11478,7 @@ async function startDaemon(opts = {}) {
|
|
|
11077
11478
|
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
11479
|
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
11480
|
const singleProject = projectArg;
|
|
11080
|
-
const singleProjectPath = singleProject && projectPathArg ?
|
|
11481
|
+
const singleProjectPath = singleProject && projectPathArg ? import_node_path50.default.resolve(projectPathArg) : null;
|
|
11081
11482
|
if (singleProject && !singleProjectPath) {
|
|
11082
11483
|
throw new Error(
|
|
11083
11484
|
`neatd: project "${singleProject}" given without a projectPath; pass NEAT_PROJECT_PATH alongside NEAT_PROJECT.`
|
|
@@ -11085,14 +11486,14 @@ async function startDaemon(opts = {}) {
|
|
|
11085
11486
|
}
|
|
11086
11487
|
if (!singleProject) {
|
|
11087
11488
|
try {
|
|
11088
|
-
await
|
|
11489
|
+
await import_node_fs31.promises.access(regPath);
|
|
11089
11490
|
} catch {
|
|
11090
11491
|
throw new Error(
|
|
11091
11492
|
`neatd: registry not found at ${regPath}. Run \`neat init <path>\` to register a project before starting the daemon.`
|
|
11092
11493
|
);
|
|
11093
11494
|
}
|
|
11094
11495
|
}
|
|
11095
|
-
const pidPath =
|
|
11496
|
+
const pidPath = import_node_path50.default.join(home, "neatd.pid");
|
|
11096
11497
|
await writeAtomically(pidPath, `${process.pid}
|
|
11097
11498
|
`);
|
|
11098
11499
|
const slots = /* @__PURE__ */ new Map();
|
|
@@ -11274,7 +11675,12 @@ async function startDaemon(opts = {}) {
|
|
|
11274
11675
|
// only this project (the dashboard pins to it), and the daemon-wide
|
|
11275
11676
|
// `/health` carries it at the top level for the spawn-reuse identity
|
|
11276
11677
|
// check. Absent for the legacy multi-project daemon.
|
|
11277
|
-
singleProject: singleProject && singleProjectPath ? { name: singleProject, path: singleProjectPath } : void 0
|
|
11678
|
+
singleProject: singleProject && singleProjectPath ? { name: singleProject, path: singleProjectPath } : void 0,
|
|
11679
|
+
// ADR-136 — the connector-status endpoint reads ~/.neat/connectors.json
|
|
11680
|
+
// through the same resolved home the slot bootstrap read it from, so a
|
|
11681
|
+
// daemon given an explicit NEAT_HOME serves status for the same file it
|
|
11682
|
+
// polls.
|
|
11683
|
+
connectorsHome: home
|
|
11278
11684
|
});
|
|
11279
11685
|
restAddress = await restApp.listen({ port: restPort, host });
|
|
11280
11686
|
console.log(
|
|
@@ -11286,7 +11692,7 @@ async function startDaemon(opts = {}) {
|
|
|
11286
11692
|
}
|
|
11287
11693
|
if (restApp) await restApp.close().catch(() => {
|
|
11288
11694
|
});
|
|
11289
|
-
await
|
|
11695
|
+
await import_node_fs31.promises.unlink(pidPath).catch(() => {
|
|
11290
11696
|
});
|
|
11291
11697
|
throw new Error(
|
|
11292
11698
|
`neatd: failed to bind REST on port ${restPort} \u2014 ${err.message}`
|
|
@@ -11412,7 +11818,7 @@ async function startDaemon(opts = {}) {
|
|
|
11412
11818
|
});
|
|
11413
11819
|
if (otlpApp) await otlpApp.close().catch(() => {
|
|
11414
11820
|
});
|
|
11415
|
-
await
|
|
11821
|
+
await import_node_fs31.promises.unlink(pidPath).catch(() => {
|
|
11416
11822
|
});
|
|
11417
11823
|
throw new Error(
|
|
11418
11824
|
`neatd: failed to bind OTLP on port ${otlpPort} \u2014 ${err.message}`
|
|
@@ -11447,7 +11853,7 @@ async function startDaemon(opts = {}) {
|
|
|
11447
11853
|
});
|
|
11448
11854
|
if (otlpApp) await otlpApp.close().catch(() => {
|
|
11449
11855
|
});
|
|
11450
|
-
await
|
|
11856
|
+
await import_node_fs31.promises.unlink(pidPath).catch(() => {
|
|
11451
11857
|
});
|
|
11452
11858
|
throw new Error(
|
|
11453
11859
|
`neatd: failed to write daemon.json for "${singleProject}" \u2014 ${err.message}`
|
|
@@ -11494,9 +11900,9 @@ async function startDaemon(opts = {}) {
|
|
|
11494
11900
|
let registryWatcher = null;
|
|
11495
11901
|
let reloadTimer = null;
|
|
11496
11902
|
if (!singleProject) try {
|
|
11497
|
-
const regDir =
|
|
11498
|
-
const regBase =
|
|
11499
|
-
registryWatcher = (0,
|
|
11903
|
+
const regDir = import_node_path50.default.dirname(regPath);
|
|
11904
|
+
const regBase = import_node_path50.default.basename(regPath);
|
|
11905
|
+
registryWatcher = (0, import_node_fs31.watch)(regDir, (_eventType, filename) => {
|
|
11500
11906
|
if (filename !== null && filename !== regBase) return;
|
|
11501
11907
|
if (reloadTimer) clearTimeout(reloadTimer);
|
|
11502
11908
|
reloadTimer = setTimeout(() => {
|
|
@@ -11545,7 +11951,7 @@ async function startDaemon(opts = {}) {
|
|
|
11545
11951
|
if (daemonRecord) {
|
|
11546
11952
|
await clearDaemonRecord(daemonRecord, home);
|
|
11547
11953
|
}
|
|
11548
|
-
await
|
|
11954
|
+
await import_node_fs31.promises.unlink(pidPath).catch(() => {
|
|
11549
11955
|
});
|
|
11550
11956
|
};
|
|
11551
11957
|
return {
|