@neat.is/core 0.4.28-dev.20260711 → 0.4.28-dev.20260713
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-5T6J3WKC.js → chunk-N7HEYLOF.js} +731 -143
- package/dist/chunk-N7HEYLOF.js.map +1 -0
- package/dist/{chunk-UJ6WBLIE.js → chunk-V2HUBS5J.js} +163 -345
- package/dist/chunk-V2HUBS5J.js.map +1 -0
- package/dist/cli.cjs +1174 -813
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +9 -9
- package/dist/index.cjs +784 -380
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -2
- package/dist/neatd.cjs +849 -445
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +2 -2
- package/dist/server.cjs +507 -91
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +1 -1
- package/package.json +2 -2
- package/dist/chunk-5T6J3WKC.js.map +0 -1
- package/dist/chunk-UJ6WBLIE.js.map +0 -1
package/dist/neatd.cjs
CHANGED
|
@@ -61,8 +61,8 @@ function mountBearerAuth(app, opts) {
|
|
|
61
61
|
]);
|
|
62
62
|
const publicRead = opts.publicRead === true;
|
|
63
63
|
app.addHook("preHandler", (req2, reply, done) => {
|
|
64
|
-
const
|
|
65
|
-
if (exactUnauthPaths.has(
|
|
64
|
+
const path53 = (req2.url.split("?")[0] ?? "").replace(/\/+$/, "");
|
|
65
|
+
if (exactUnauthPaths.has(path53) || PROJECT_SCOPED_UNAUTH_PATTERN.test(path53)) {
|
|
66
66
|
done();
|
|
67
67
|
return;
|
|
68
68
|
}
|
|
@@ -194,8 +194,8 @@ function reshapeGrpcRequest(req2) {
|
|
|
194
194
|
};
|
|
195
195
|
}
|
|
196
196
|
function resolveProtoRoot() {
|
|
197
|
-
const here =
|
|
198
|
-
return
|
|
197
|
+
const here = import_node_path47.default.dirname((0, import_node_url.fileURLToPath)(importMetaUrl));
|
|
198
|
+
return import_node_path47.default.resolve(here, "..", "proto");
|
|
199
199
|
}
|
|
200
200
|
function loadTraceService() {
|
|
201
201
|
const protoRoot = resolveProtoRoot();
|
|
@@ -263,13 +263,13 @@ async function startOtelGrpcReceiver(opts) {
|
|
|
263
263
|
})
|
|
264
264
|
};
|
|
265
265
|
}
|
|
266
|
-
var import_node_url,
|
|
266
|
+
var import_node_url, import_node_path47, import_node_crypto2, grpc, protoLoader;
|
|
267
267
|
var init_otel_grpc = __esm({
|
|
268
268
|
"src/otel-grpc.ts"() {
|
|
269
269
|
"use strict";
|
|
270
270
|
init_cjs_shims();
|
|
271
271
|
import_node_url = require("url");
|
|
272
|
-
|
|
272
|
+
import_node_path47 = __toESM(require("path"), 1);
|
|
273
273
|
import_node_crypto2 = require("crypto");
|
|
274
274
|
grpc = __toESM(require("@grpc/grpc-js"), 1);
|
|
275
275
|
protoLoader = __toESM(require("@grpc/proto-loader"), 1);
|
|
@@ -362,8 +362,8 @@ function websocketChannelPathOf(attrs) {
|
|
|
362
362
|
const v = attrs[key];
|
|
363
363
|
if (typeof v === "string" && v.length > 0) {
|
|
364
364
|
const q = v.indexOf("?");
|
|
365
|
-
const
|
|
366
|
-
if (
|
|
365
|
+
const path53 = q === -1 ? v : v.slice(0, q);
|
|
366
|
+
if (path53.length > 0) return path53;
|
|
367
367
|
}
|
|
368
368
|
}
|
|
369
369
|
return void 0;
|
|
@@ -417,10 +417,10 @@ function parseOtlpRequest(body) {
|
|
|
417
417
|
return out;
|
|
418
418
|
}
|
|
419
419
|
function loadProtoRoot() {
|
|
420
|
-
const here =
|
|
421
|
-
const protoRoot =
|
|
420
|
+
const here = import_node_path48.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
|
|
421
|
+
const protoRoot = import_node_path48.default.resolve(here, "..", "proto");
|
|
422
422
|
const root = new import_protobufjs.default.Root();
|
|
423
|
-
root.resolvePath = (_origin, target) =>
|
|
423
|
+
root.resolvePath = (_origin, target) => import_node_path48.default.resolve(protoRoot, target);
|
|
424
424
|
root.loadSync(
|
|
425
425
|
"opentelemetry/proto/collector/trace/v1/trace_service.proto",
|
|
426
426
|
{ keepCase: true }
|
|
@@ -648,12 +648,12 @@ async function listenSteppingOtlp(app, requestedPort, host) {
|
|
|
648
648
|
}
|
|
649
649
|
}
|
|
650
650
|
}
|
|
651
|
-
var
|
|
651
|
+
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;
|
|
652
652
|
var init_otel = __esm({
|
|
653
653
|
"src/otel.ts"() {
|
|
654
654
|
"use strict";
|
|
655
655
|
init_cjs_shims();
|
|
656
|
-
|
|
656
|
+
import_node_path48 = __toESM(require("path"), 1);
|
|
657
657
|
import_node_url2 = require("url");
|
|
658
658
|
import_fastify2 = __toESM(require("fastify"), 1);
|
|
659
659
|
import_protobufjs = __toESM(require("protobufjs"), 1);
|
|
@@ -676,14 +676,14 @@ __export(neatd_exports, {
|
|
|
676
676
|
});
|
|
677
677
|
module.exports = __toCommonJS(neatd_exports);
|
|
678
678
|
init_cjs_shims();
|
|
679
|
-
var
|
|
680
|
-
var
|
|
679
|
+
var import_node_fs33 = require("fs");
|
|
680
|
+
var import_node_path52 = __toESM(require("path"), 1);
|
|
681
681
|
var import_node_module2 = require("module");
|
|
682
682
|
|
|
683
683
|
// src/daemon.ts
|
|
684
684
|
init_cjs_shims();
|
|
685
|
-
var
|
|
686
|
-
var
|
|
685
|
+
var import_node_fs31 = require("fs");
|
|
686
|
+
var import_node_path50 = __toESM(require("path"), 1);
|
|
687
687
|
var import_node_module = require("module");
|
|
688
688
|
|
|
689
689
|
// src/graph.ts
|
|
@@ -1213,19 +1213,19 @@ function confidenceFromMix(edges, now = Date.now()) {
|
|
|
1213
1213
|
function longestIncomingWalk(graph, start, maxDepth) {
|
|
1214
1214
|
let best = { path: [start], edges: [] };
|
|
1215
1215
|
const visited = /* @__PURE__ */ new Set([start]);
|
|
1216
|
-
function step(node,
|
|
1217
|
-
if (
|
|
1218
|
-
best = { path: [...
|
|
1216
|
+
function step(node, path53, edges) {
|
|
1217
|
+
if (path53.length > best.path.length) {
|
|
1218
|
+
best = { path: [...path53], edges: [...edges] };
|
|
1219
1219
|
}
|
|
1220
|
-
if (
|
|
1220
|
+
if (path53.length - 1 >= maxDepth) return;
|
|
1221
1221
|
const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
|
|
1222
1222
|
for (const [srcId, edge] of incoming) {
|
|
1223
1223
|
if (visited.has(srcId)) continue;
|
|
1224
1224
|
visited.add(srcId);
|
|
1225
|
-
|
|
1225
|
+
path53.push(srcId);
|
|
1226
1226
|
edges.push(edge);
|
|
1227
|
-
step(srcId,
|
|
1228
|
-
|
|
1227
|
+
step(srcId, path53, edges);
|
|
1228
|
+
path53.pop();
|
|
1229
1229
|
edges.pop();
|
|
1230
1230
|
visited.delete(srcId);
|
|
1231
1231
|
}
|
|
@@ -1419,26 +1419,26 @@ function dominantFailingCall(graph, serviceId5, visited) {
|
|
|
1419
1419
|
return best;
|
|
1420
1420
|
}
|
|
1421
1421
|
function followFailingCallChain(graph, originServiceId, maxDepth) {
|
|
1422
|
-
const
|
|
1422
|
+
const path53 = [originServiceId];
|
|
1423
1423
|
const edges = [];
|
|
1424
1424
|
const visited = /* @__PURE__ */ new Set([originServiceId]);
|
|
1425
1425
|
let current = originServiceId;
|
|
1426
1426
|
for (let depth = 0; depth < maxDepth; depth++) {
|
|
1427
1427
|
const hop = dominantFailingCall(graph, current, visited);
|
|
1428
1428
|
if (!hop) break;
|
|
1429
|
-
|
|
1429
|
+
path53.push(hop.nextService);
|
|
1430
1430
|
edges.push(hop.edge);
|
|
1431
1431
|
visited.add(hop.nextService);
|
|
1432
1432
|
current = hop.nextService;
|
|
1433
1433
|
}
|
|
1434
1434
|
if (edges.length === 0) return null;
|
|
1435
|
-
return { path:
|
|
1435
|
+
return { path: path53, edges, culprit: current };
|
|
1436
1436
|
}
|
|
1437
1437
|
function crossServiceRootCause(graph, originId, incidents, errorEvent) {
|
|
1438
1438
|
const chain = followFailingCallChain(graph, originId, ROOT_CAUSE_MAX_DEPTH);
|
|
1439
1439
|
if (!chain) return null;
|
|
1440
1440
|
const culprit = chain.culprit;
|
|
1441
|
-
const
|
|
1441
|
+
const path53 = [...chain.path];
|
|
1442
1442
|
const edgeProvenances = chain.edges.map((e) => e.provenance);
|
|
1443
1443
|
const baseConfidence = confidenceFromMix(chain.edges);
|
|
1444
1444
|
const confidence = Math.max(0, Math.min(1, baseConfidence * INCIDENT_ROOT_CAUSE_CONFIDENCE));
|
|
@@ -1446,14 +1446,14 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
|
|
|
1446
1446
|
if (loc) {
|
|
1447
1447
|
let rootCauseNode = culprit;
|
|
1448
1448
|
if (loc.fileNode) {
|
|
1449
|
-
|
|
1449
|
+
path53.push(loc.fileNode);
|
|
1450
1450
|
edgeProvenances.push(import_types.Provenance.OBSERVED);
|
|
1451
1451
|
rootCauseNode = loc.fileNode;
|
|
1452
1452
|
}
|
|
1453
1453
|
return import_types.RootCauseResultSchema.parse({
|
|
1454
1454
|
rootCauseNode,
|
|
1455
1455
|
rootCauseReason: loc.rootCauseReason,
|
|
1456
|
-
traversalPath:
|
|
1456
|
+
traversalPath: path53,
|
|
1457
1457
|
edgeProvenances,
|
|
1458
1458
|
confidence,
|
|
1459
1459
|
...loc.fixRecommendation ? { fixRecommendation: loc.fixRecommendation } : {}
|
|
@@ -1465,7 +1465,7 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
|
|
|
1465
1465
|
return import_types.RootCauseResultSchema.parse({
|
|
1466
1466
|
rootCauseNode: culprit,
|
|
1467
1467
|
rootCauseReason: `${culpritName} is failing downstream calls (${errs} observed error${errs === 1 ? "" : "s"})`,
|
|
1468
|
-
traversalPath:
|
|
1468
|
+
traversalPath: path53,
|
|
1469
1469
|
edgeProvenances,
|
|
1470
1470
|
confidence,
|
|
1471
1471
|
fixRecommendation: `Inspect ${culpritName}'s failing handler`
|
|
@@ -6458,6 +6458,39 @@ function classifyImage(image) {
|
|
|
6458
6458
|
if (last.startsWith("memcached")) return "memcached";
|
|
6459
6459
|
return "container";
|
|
6460
6460
|
}
|
|
6461
|
+
function lineContaining(raw, needle) {
|
|
6462
|
+
if (!needle) return void 0;
|
|
6463
|
+
const idx = raw.indexOf(needle);
|
|
6464
|
+
if (idx === -1) return void 0;
|
|
6465
|
+
let line = 1;
|
|
6466
|
+
for (let i = 0; i < idx; i++) if (raw[i] === "\n") line++;
|
|
6467
|
+
return line;
|
|
6468
|
+
}
|
|
6469
|
+
function emitPlatformResourceEdge(graph, anchorId, edgeType, kind, name, provider, evidenceFile, line) {
|
|
6470
|
+
let nodesAdded = 0;
|
|
6471
|
+
let edgesAdded = 0;
|
|
6472
|
+
const node = makeInfraNode(kind, name, provider);
|
|
6473
|
+
if (!graph.hasNode(node.id)) {
|
|
6474
|
+
graph.addNode(node.id, node);
|
|
6475
|
+
nodesAdded++;
|
|
6476
|
+
}
|
|
6477
|
+
if (node.id === anchorId) return { nodesAdded, edgesAdded };
|
|
6478
|
+
const edgeId = (0, import_types4.extractedEdgeId)(anchorId, node.id, edgeType);
|
|
6479
|
+
if (!graph.hasEdge(edgeId)) {
|
|
6480
|
+
const edge = {
|
|
6481
|
+
id: edgeId,
|
|
6482
|
+
source: anchorId,
|
|
6483
|
+
target: node.id,
|
|
6484
|
+
type: edgeType,
|
|
6485
|
+
provenance: import_types21.Provenance.EXTRACTED,
|
|
6486
|
+
confidence: (0, import_types21.confidenceForExtracted)("structural"),
|
|
6487
|
+
evidence: { file: evidenceFile, ...line !== void 0 ? { line } : {} }
|
|
6488
|
+
};
|
|
6489
|
+
graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
|
|
6490
|
+
edgesAdded++;
|
|
6491
|
+
}
|
|
6492
|
+
return { nodesAdded, edgesAdded };
|
|
6493
|
+
}
|
|
6461
6494
|
|
|
6462
6495
|
// src/extract/infra/docker-compose.ts
|
|
6463
6496
|
function dependsOnList(value) {
|
|
@@ -6817,7 +6850,7 @@ async function readWranglerConfig(dir) {
|
|
|
6817
6850
|
}
|
|
6818
6851
|
return null;
|
|
6819
6852
|
}
|
|
6820
|
-
function
|
|
6853
|
+
function lineContaining2(raw, needle) {
|
|
6821
6854
|
if (!needle) return void 0;
|
|
6822
6855
|
const idx = raw.indexOf(needle);
|
|
6823
6856
|
if (idx === -1) return void 0;
|
|
@@ -6941,7 +6974,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
|
|
|
6941
6974
|
"cloudflare-route",
|
|
6942
6975
|
route,
|
|
6943
6976
|
evidenceFile,
|
|
6944
|
-
|
|
6977
|
+
lineContaining2(raw, route)
|
|
6945
6978
|
);
|
|
6946
6979
|
nodesAdded += result.nodesAdded;
|
|
6947
6980
|
edgesAdded += result.edgesAdded;
|
|
@@ -6965,7 +6998,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
|
|
|
6965
6998
|
group.kind,
|
|
6966
6999
|
name,
|
|
6967
7000
|
evidenceFile,
|
|
6968
|
-
|
|
7001
|
+
lineContaining2(raw, name)
|
|
6969
7002
|
);
|
|
6970
7003
|
nodesAdded += result.nodesAdded;
|
|
6971
7004
|
edgesAdded += result.edgesAdded;
|
|
@@ -6979,7 +7012,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
|
|
|
6979
7012
|
"cloudflare-cron",
|
|
6980
7013
|
cron,
|
|
6981
7014
|
evidenceFile,
|
|
6982
|
-
|
|
7015
|
+
lineContaining2(raw, cron)
|
|
6983
7016
|
);
|
|
6984
7017
|
nodesAdded += result.nodesAdded;
|
|
6985
7018
|
edgesAdded += result.edgesAdded;
|
|
@@ -6992,7 +7025,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
|
|
|
6992
7025
|
"cloudflare-env-var",
|
|
6993
7026
|
varName,
|
|
6994
7027
|
evidenceFile,
|
|
6995
|
-
|
|
7028
|
+
lineContaining2(raw, varName)
|
|
6996
7029
|
);
|
|
6997
7030
|
nodesAdded += result.nodesAdded;
|
|
6998
7031
|
edgesAdded += result.edgesAdded;
|
|
@@ -7010,7 +7043,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
|
|
|
7010
7043
|
type: import_types25.EdgeType.CALLS,
|
|
7011
7044
|
provenance: import_types25.Provenance.EXTRACTED,
|
|
7012
7045
|
confidence: (0, import_types25.confidenceForExtracted)("structural"),
|
|
7013
|
-
evidence: { file: evidenceFile, line:
|
|
7046
|
+
evidence: { file: evidenceFile, line: lineContaining2(raw, svc.service) }
|
|
7014
7047
|
};
|
|
7015
7048
|
graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
|
|
7016
7049
|
edgesAdded++;
|
|
@@ -7024,11 +7057,208 @@ async function addCloudflareWorkers(graph, services, scanPath) {
|
|
|
7024
7057
|
"cloudflare-service-binding",
|
|
7025
7058
|
svc.service,
|
|
7026
7059
|
evidenceFile,
|
|
7027
|
-
|
|
7060
|
+
lineContaining2(raw, svc.service)
|
|
7061
|
+
);
|
|
7062
|
+
nodesAdded += result.nodesAdded;
|
|
7063
|
+
edgesAdded += result.edgesAdded;
|
|
7064
|
+
}
|
|
7065
|
+
}
|
|
7066
|
+
return { nodesAdded, edgesAdded };
|
|
7067
|
+
}
|
|
7068
|
+
|
|
7069
|
+
// src/extract/infra/vercel.ts
|
|
7070
|
+
init_cjs_shims();
|
|
7071
|
+
var import_node_fs20 = require("fs");
|
|
7072
|
+
var import_node_path36 = __toESM(require("path"), 1);
|
|
7073
|
+
var import_types26 = require("@neat.is/types");
|
|
7074
|
+
var VERCEL_CONFIG_FILENAMES = ["vercel.json", "vercel.jsonc"];
|
|
7075
|
+
async function readVercelConfig(dir) {
|
|
7076
|
+
for (const filename of VERCEL_CONFIG_FILENAMES) {
|
|
7077
|
+
const abs = import_node_path36.default.join(dir, filename);
|
|
7078
|
+
if (!await exists(abs)) continue;
|
|
7079
|
+
const raw = await import_node_fs20.promises.readFile(abs, "utf8");
|
|
7080
|
+
const config = JSON.parse(maskCommentsInSource(raw));
|
|
7081
|
+
return { config, relFile: filename, raw };
|
|
7082
|
+
}
|
|
7083
|
+
return null;
|
|
7084
|
+
}
|
|
7085
|
+
async function readLinkedProjectName(dir) {
|
|
7086
|
+
const abs = import_node_path36.default.join(dir, ".vercel", "project.json");
|
|
7087
|
+
if (!await exists(abs)) return void 0;
|
|
7088
|
+
const parsed = JSON.parse(await import_node_fs20.promises.readFile(abs, "utf8"));
|
|
7089
|
+
return typeof parsed.projectName === "string" ? parsed.projectName : void 0;
|
|
7090
|
+
}
|
|
7091
|
+
function routeSource(route) {
|
|
7092
|
+
return route.source ?? route.src;
|
|
7093
|
+
}
|
|
7094
|
+
async function addVercelServices(graph, services, scanPath) {
|
|
7095
|
+
let nodesAdded = 0;
|
|
7096
|
+
let edgesAdded = 0;
|
|
7097
|
+
for (const service of services) {
|
|
7098
|
+
let read = null;
|
|
7099
|
+
let projectName;
|
|
7100
|
+
try {
|
|
7101
|
+
read = await readVercelConfig(service.dir);
|
|
7102
|
+
projectName = await readLinkedProjectName(service.dir);
|
|
7103
|
+
} catch (err) {
|
|
7104
|
+
recordExtractionError("infra vercel", import_node_path36.default.relative(scanPath, service.dir), err);
|
|
7105
|
+
continue;
|
|
7106
|
+
}
|
|
7107
|
+
if (!read && !projectName) continue;
|
|
7108
|
+
const serviceNode = graph.getNodeAttributes(service.node.id);
|
|
7109
|
+
if (serviceNode.platform !== "vercel" || projectName && serviceNode.platformName !== projectName) {
|
|
7110
|
+
const updated = {
|
|
7111
|
+
...serviceNode,
|
|
7112
|
+
platform: "vercel",
|
|
7113
|
+
...projectName ? { platformName: projectName } : {}
|
|
7114
|
+
};
|
|
7115
|
+
graph.replaceNodeAttributes(service.node.id, updated);
|
|
7116
|
+
}
|
|
7117
|
+
const anchorId = service.node.id;
|
|
7118
|
+
if (!read) continue;
|
|
7119
|
+
const { config, relFile, raw } = read;
|
|
7120
|
+
const evidenceFile = toPosix2(import_node_path36.default.relative(scanPath, import_node_path36.default.join(service.dir, relFile)));
|
|
7121
|
+
const add = (edgeType, kind, name) => {
|
|
7122
|
+
if (!name) return;
|
|
7123
|
+
const result = emitPlatformResourceEdge(
|
|
7124
|
+
graph,
|
|
7125
|
+
anchorId,
|
|
7126
|
+
edgeType,
|
|
7127
|
+
kind,
|
|
7128
|
+
name,
|
|
7129
|
+
"vercel",
|
|
7130
|
+
evidenceFile,
|
|
7131
|
+
lineContaining(raw, name)
|
|
7132
|
+
);
|
|
7133
|
+
nodesAdded += result.nodesAdded;
|
|
7134
|
+
edgesAdded += result.edgesAdded;
|
|
7135
|
+
};
|
|
7136
|
+
add(import_types26.EdgeType.RUNS_ON, "vercel", "vercel");
|
|
7137
|
+
for (const cron of config.crons ?? []) add(import_types26.EdgeType.DEPENDS_ON, "vercel-cron", cron.path ?? cron.schedule);
|
|
7138
|
+
for (const varName of Object.keys(config.env ?? {})) add(import_types26.EdgeType.DEPENDS_ON, "vercel-env-var", varName);
|
|
7139
|
+
for (const varName of Object.keys(config.build?.env ?? {})) add(import_types26.EdgeType.DEPENDS_ON, "vercel-env-var", varName);
|
|
7140
|
+
for (const route of [...config.rewrites ?? [], ...config.redirects ?? [], ...config.routes ?? []]) {
|
|
7141
|
+
add(import_types26.EdgeType.CONNECTS_TO, "vercel-route", routeSource(route));
|
|
7142
|
+
}
|
|
7143
|
+
}
|
|
7144
|
+
return { nodesAdded, edgesAdded };
|
|
7145
|
+
}
|
|
7146
|
+
|
|
7147
|
+
// src/extract/infra/railway.ts
|
|
7148
|
+
init_cjs_shims();
|
|
7149
|
+
var import_node_fs21 = require("fs");
|
|
7150
|
+
var import_node_path37 = __toESM(require("path"), 1);
|
|
7151
|
+
var import_smol_toml3 = require("smol-toml");
|
|
7152
|
+
var import_types27 = require("@neat.is/types");
|
|
7153
|
+
var RAILWAY_FILENAMES = ["railway.toml", "railway.json", "railway.jsonc"];
|
|
7154
|
+
async function readRailwayConfig(dir) {
|
|
7155
|
+
for (const filename of RAILWAY_FILENAMES) {
|
|
7156
|
+
const abs = import_node_path37.default.join(dir, filename);
|
|
7157
|
+
if (!await exists(abs)) continue;
|
|
7158
|
+
const raw = await import_node_fs21.promises.readFile(abs, "utf8");
|
|
7159
|
+
const config = filename === "railway.toml" ? (0, import_smol_toml3.parse)(raw) : JSON.parse(maskCommentsInSource(raw));
|
|
7160
|
+
return { config, relFile: filename, raw };
|
|
7161
|
+
}
|
|
7162
|
+
return null;
|
|
7163
|
+
}
|
|
7164
|
+
async function addRailwayServices(graph, services, scanPath) {
|
|
7165
|
+
let nodesAdded = 0;
|
|
7166
|
+
let edgesAdded = 0;
|
|
7167
|
+
for (const service of services) {
|
|
7168
|
+
let read = null;
|
|
7169
|
+
try {
|
|
7170
|
+
read = await readRailwayConfig(service.dir);
|
|
7171
|
+
} catch (err) {
|
|
7172
|
+
recordExtractionError("infra railway", import_node_path37.default.relative(scanPath, service.dir), err);
|
|
7173
|
+
continue;
|
|
7174
|
+
}
|
|
7175
|
+
if (!read) continue;
|
|
7176
|
+
const serviceNode = graph.getNodeAttributes(service.node.id);
|
|
7177
|
+
if (serviceNode.platform !== "railway") {
|
|
7178
|
+
graph.replaceNodeAttributes(service.node.id, { ...serviceNode, platform: "railway" });
|
|
7179
|
+
}
|
|
7180
|
+
const anchorId = service.node.id;
|
|
7181
|
+
const { config, relFile, raw } = read;
|
|
7182
|
+
const evidenceFile = toPosix2(import_node_path37.default.relative(scanPath, import_node_path37.default.join(service.dir, relFile)));
|
|
7183
|
+
const add = (edgeType, kind, name) => {
|
|
7184
|
+
if (!name) return;
|
|
7185
|
+
const result = emitPlatformResourceEdge(
|
|
7186
|
+
graph,
|
|
7187
|
+
anchorId,
|
|
7188
|
+
edgeType,
|
|
7189
|
+
kind,
|
|
7190
|
+
name,
|
|
7191
|
+
"railway",
|
|
7192
|
+
evidenceFile,
|
|
7193
|
+
lineContaining(raw, name)
|
|
7028
7194
|
);
|
|
7029
7195
|
nodesAdded += result.nodesAdded;
|
|
7030
7196
|
edgesAdded += result.edgesAdded;
|
|
7197
|
+
};
|
|
7198
|
+
add(import_types27.EdgeType.RUNS_ON, "railway", "railway");
|
|
7199
|
+
add(import_types27.EdgeType.CONNECTS_TO, "railway-route", config.deploy?.healthcheckPath);
|
|
7200
|
+
add(import_types27.EdgeType.DEPENDS_ON, "railway-cron", config.deploy?.cronSchedule);
|
|
7201
|
+
}
|
|
7202
|
+
return { nodesAdded, edgesAdded };
|
|
7203
|
+
}
|
|
7204
|
+
|
|
7205
|
+
// src/extract/infra/supabase.ts
|
|
7206
|
+
init_cjs_shims();
|
|
7207
|
+
var import_node_fs22 = require("fs");
|
|
7208
|
+
var import_node_path38 = __toESM(require("path"), 1);
|
|
7209
|
+
var import_smol_toml4 = require("smol-toml");
|
|
7210
|
+
var import_types28 = require("@neat.is/types");
|
|
7211
|
+
async function readSupabaseConfig(dir) {
|
|
7212
|
+
const relFile = import_node_path38.default.join("supabase", "config.toml");
|
|
7213
|
+
const abs = import_node_path38.default.join(dir, relFile);
|
|
7214
|
+
if (!await exists(abs)) return null;
|
|
7215
|
+
const raw = await import_node_fs22.promises.readFile(abs, "utf8");
|
|
7216
|
+
const config = (0, import_smol_toml4.parse)(raw);
|
|
7217
|
+
return { config, relFile, raw };
|
|
7218
|
+
}
|
|
7219
|
+
async function addSupabaseProjects(graph, services, scanPath) {
|
|
7220
|
+
let nodesAdded = 0;
|
|
7221
|
+
let edgesAdded = 0;
|
|
7222
|
+
for (const service of services) {
|
|
7223
|
+
let read = null;
|
|
7224
|
+
try {
|
|
7225
|
+
read = await readSupabaseConfig(service.dir);
|
|
7226
|
+
} catch (err) {
|
|
7227
|
+
recordExtractionError("infra supabase", import_node_path38.default.relative(scanPath, service.dir), err);
|
|
7228
|
+
continue;
|
|
7229
|
+
}
|
|
7230
|
+
if (!read) continue;
|
|
7231
|
+
const { config, relFile, raw } = read;
|
|
7232
|
+
const projectId = typeof config.project_id === "string" ? config.project_id : void 0;
|
|
7233
|
+
const serviceNode = graph.getNodeAttributes(service.node.id);
|
|
7234
|
+
if (serviceNode.platform !== "supabase" || projectId && serviceNode.platformName !== projectId) {
|
|
7235
|
+
graph.replaceNodeAttributes(service.node.id, {
|
|
7236
|
+
...serviceNode,
|
|
7237
|
+
platform: "supabase",
|
|
7238
|
+
...projectId ? { platformName: projectId } : {}
|
|
7239
|
+
});
|
|
7031
7240
|
}
|
|
7241
|
+
const anchorId = service.node.id;
|
|
7242
|
+
const evidenceFile = toPosix2(import_node_path38.default.relative(scanPath, import_node_path38.default.join(service.dir, relFile)));
|
|
7243
|
+
const add = (edgeType, kind, name) => {
|
|
7244
|
+
if (!name) return;
|
|
7245
|
+
const result = emitPlatformResourceEdge(
|
|
7246
|
+
graph,
|
|
7247
|
+
anchorId,
|
|
7248
|
+
edgeType,
|
|
7249
|
+
kind,
|
|
7250
|
+
name,
|
|
7251
|
+
"supabase",
|
|
7252
|
+
evidenceFile,
|
|
7253
|
+
lineContaining(raw, name)
|
|
7254
|
+
);
|
|
7255
|
+
nodesAdded += result.nodesAdded;
|
|
7256
|
+
edgesAdded += result.edgesAdded;
|
|
7257
|
+
};
|
|
7258
|
+
add(import_types28.EdgeType.RUNS_ON, "supabase", "supabase");
|
|
7259
|
+
for (const fn of Object.keys(config.functions ?? {})) add(import_types28.EdgeType.DEPENDS_ON, "supabase-function", fn);
|
|
7260
|
+
if (config.storage) add(import_types28.EdgeType.DEPENDS_ON, "supabase-storage", "storage");
|
|
7261
|
+
if (config.auth) add(import_types28.EdgeType.DEPENDS_ON, "supabase-auth", "auth");
|
|
7032
7262
|
}
|
|
7033
7263
|
return { nodesAdded, edgesAdded };
|
|
7034
7264
|
}
|
|
@@ -7040,24 +7270,27 @@ async function addInfra(graph, scanPath, services) {
|
|
|
7040
7270
|
const terraform = await addTerraformResources(graph, scanPath);
|
|
7041
7271
|
const k8s = await addK8sResources(graph, scanPath);
|
|
7042
7272
|
const cloudflare = await addCloudflareWorkers(graph, services, scanPath);
|
|
7273
|
+
const vercel = await addVercelServices(graph, services, scanPath);
|
|
7274
|
+
const railway = await addRailwayServices(graph, services, scanPath);
|
|
7275
|
+
const supabase = await addSupabaseProjects(graph, services, scanPath);
|
|
7043
7276
|
return {
|
|
7044
|
-
nodesAdded: compose.nodesAdded + dockerfile.nodesAdded + terraform.nodesAdded + k8s.nodesAdded + cloudflare.nodesAdded,
|
|
7045
|
-
edgesAdded: compose.edgesAdded + dockerfile.edgesAdded + terraform.edgesAdded + k8s.edgesAdded + cloudflare.edgesAdded
|
|
7277
|
+
nodesAdded: compose.nodesAdded + dockerfile.nodesAdded + terraform.nodesAdded + k8s.nodesAdded + cloudflare.nodesAdded + vercel.nodesAdded + railway.nodesAdded + supabase.nodesAdded,
|
|
7278
|
+
edgesAdded: compose.edgesAdded + dockerfile.edgesAdded + terraform.edgesAdded + k8s.edgesAdded + cloudflare.edgesAdded + vercel.edgesAdded + railway.edgesAdded + supabase.edgesAdded
|
|
7046
7279
|
};
|
|
7047
7280
|
}
|
|
7048
7281
|
|
|
7049
7282
|
// src/extract/index.ts
|
|
7050
|
-
var
|
|
7283
|
+
var import_node_path40 = __toESM(require("path"), 1);
|
|
7051
7284
|
|
|
7052
7285
|
// src/extract/retire.ts
|
|
7053
7286
|
init_cjs_shims();
|
|
7054
|
-
var
|
|
7055
|
-
var
|
|
7056
|
-
var
|
|
7287
|
+
var import_node_fs23 = require("fs");
|
|
7288
|
+
var import_node_path39 = __toESM(require("path"), 1);
|
|
7289
|
+
var import_types29 = require("@neat.is/types");
|
|
7057
7290
|
function dropOrphanedFileNodes(graph) {
|
|
7058
7291
|
const orphans = [];
|
|
7059
7292
|
graph.forEachNode((id, attrs) => {
|
|
7060
|
-
if (attrs.type !==
|
|
7293
|
+
if (attrs.type !== import_types29.NodeType.FileNode) return;
|
|
7061
7294
|
if (graph.inboundEdges(id).length === 0 && graph.outboundEdges(id).length === 0) {
|
|
7062
7295
|
orphans.push(id);
|
|
7063
7296
|
}
|
|
@@ -7070,14 +7303,14 @@ function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = []) {
|
|
|
7070
7303
|
const bases = [scanPath, ...serviceDirs];
|
|
7071
7304
|
graph.forEachEdge((id, attrs) => {
|
|
7072
7305
|
const edge = attrs;
|
|
7073
|
-
if (edge.provenance !==
|
|
7306
|
+
if (edge.provenance !== import_types29.Provenance.EXTRACTED) return;
|
|
7074
7307
|
const evidenceFile = edge.evidence?.file;
|
|
7075
7308
|
if (!evidenceFile) return;
|
|
7076
|
-
if (
|
|
7077
|
-
if (!(0,
|
|
7309
|
+
if (import_node_path39.default.isAbsolute(evidenceFile)) {
|
|
7310
|
+
if (!(0, import_node_fs23.existsSync)(evidenceFile)) toDrop.push(id);
|
|
7078
7311
|
return;
|
|
7079
7312
|
}
|
|
7080
|
-
const found = bases.some((base) => (0,
|
|
7313
|
+
const found = bases.some((base) => (0, import_node_fs23.existsSync)(import_node_path39.default.join(base, evidenceFile)));
|
|
7081
7314
|
if (!found) toDrop.push(id);
|
|
7082
7315
|
});
|
|
7083
7316
|
for (const id of toDrop) graph.dropEdge(id);
|
|
@@ -7119,7 +7352,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
7119
7352
|
}
|
|
7120
7353
|
const droppedEntries = drainDroppedExtracted();
|
|
7121
7354
|
if (isRejectedLogEnabled() && opts.errorsPath && droppedEntries.length > 0) {
|
|
7122
|
-
const rejectedPath =
|
|
7355
|
+
const rejectedPath = import_node_path40.default.join(import_node_path40.default.dirname(opts.errorsPath), "rejected.ndjson");
|
|
7123
7356
|
try {
|
|
7124
7357
|
await writeRejectedExtracted(droppedEntries, rejectedPath);
|
|
7125
7358
|
} catch (err) {
|
|
@@ -7153,9 +7386,9 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
7153
7386
|
|
|
7154
7387
|
// src/persist.ts
|
|
7155
7388
|
init_cjs_shims();
|
|
7156
|
-
var
|
|
7157
|
-
var
|
|
7158
|
-
var
|
|
7389
|
+
var import_node_fs24 = require("fs");
|
|
7390
|
+
var import_node_path41 = __toESM(require("path"), 1);
|
|
7391
|
+
var import_types30 = require("@neat.is/types");
|
|
7159
7392
|
var SCHEMA_VERSION = 4;
|
|
7160
7393
|
function migrateV1ToV2(payload) {
|
|
7161
7394
|
const nodes = payload.graph.nodes;
|
|
@@ -7177,12 +7410,12 @@ function migrateV2ToV3(payload) {
|
|
|
7177
7410
|
for (const edge of edges) {
|
|
7178
7411
|
const attrs = edge.attributes;
|
|
7179
7412
|
if (!attrs || attrs.provenance !== "FRONTIER") continue;
|
|
7180
|
-
attrs.provenance =
|
|
7413
|
+
attrs.provenance = import_types30.Provenance.OBSERVED;
|
|
7181
7414
|
const type = typeof attrs.type === "string" ? attrs.type : void 0;
|
|
7182
7415
|
const source = typeof attrs.source === "string" ? attrs.source : void 0;
|
|
7183
7416
|
const target = typeof attrs.target === "string" ? attrs.target : void 0;
|
|
7184
7417
|
if (type && source && target) {
|
|
7185
|
-
const newId = (0,
|
|
7418
|
+
const newId = (0, import_types30.observedEdgeId)(source, target, type);
|
|
7186
7419
|
attrs.id = newId;
|
|
7187
7420
|
if (edge.key) edge.key = newId;
|
|
7188
7421
|
}
|
|
@@ -7191,7 +7424,7 @@ function migrateV2ToV3(payload) {
|
|
|
7191
7424
|
return { ...payload, schemaVersion: 3 };
|
|
7192
7425
|
}
|
|
7193
7426
|
async function ensureDir(filePath) {
|
|
7194
|
-
await
|
|
7427
|
+
await import_node_fs24.promises.mkdir(import_node_path41.default.dirname(filePath), { recursive: true });
|
|
7195
7428
|
}
|
|
7196
7429
|
async function saveGraphToDisk(graph, outPath) {
|
|
7197
7430
|
await ensureDir(outPath);
|
|
@@ -7201,13 +7434,13 @@ async function saveGraphToDisk(graph, outPath) {
|
|
|
7201
7434
|
graph: graph.export()
|
|
7202
7435
|
};
|
|
7203
7436
|
const tmp = `${outPath}.tmp`;
|
|
7204
|
-
await
|
|
7205
|
-
await
|
|
7437
|
+
await import_node_fs24.promises.writeFile(tmp, JSON.stringify(payload), "utf8");
|
|
7438
|
+
await import_node_fs24.promises.rename(tmp, outPath);
|
|
7206
7439
|
}
|
|
7207
7440
|
async function loadGraphFromDisk(graph, outPath) {
|
|
7208
7441
|
let raw;
|
|
7209
7442
|
try {
|
|
7210
|
-
raw = await
|
|
7443
|
+
raw = await import_node_fs24.promises.readFile(outPath, "utf8");
|
|
7211
7444
|
} catch (err) {
|
|
7212
7445
|
if (err.code === "ENOENT") return;
|
|
7213
7446
|
throw err;
|
|
@@ -7272,23 +7505,23 @@ function startPersistLoop(graph, outPath, opts = {}) {
|
|
|
7272
7505
|
|
|
7273
7506
|
// src/projects.ts
|
|
7274
7507
|
init_cjs_shims();
|
|
7275
|
-
var
|
|
7508
|
+
var import_node_path42 = __toESM(require("path"), 1);
|
|
7276
7509
|
function pathsForProject(project, baseDir) {
|
|
7277
7510
|
if (project === DEFAULT_PROJECT) {
|
|
7278
7511
|
return {
|
|
7279
|
-
snapshotPath:
|
|
7280
|
-
errorsPath:
|
|
7281
|
-
staleEventsPath:
|
|
7282
|
-
embeddingsCachePath:
|
|
7283
|
-
policyViolationsPath:
|
|
7512
|
+
snapshotPath: import_node_path42.default.join(baseDir, "graph.json"),
|
|
7513
|
+
errorsPath: import_node_path42.default.join(baseDir, "errors.ndjson"),
|
|
7514
|
+
staleEventsPath: import_node_path42.default.join(baseDir, "stale-events.ndjson"),
|
|
7515
|
+
embeddingsCachePath: import_node_path42.default.join(baseDir, "embeddings.json"),
|
|
7516
|
+
policyViolationsPath: import_node_path42.default.join(baseDir, "policy-violations.ndjson")
|
|
7284
7517
|
};
|
|
7285
7518
|
}
|
|
7286
7519
|
return {
|
|
7287
|
-
snapshotPath:
|
|
7288
|
-
errorsPath:
|
|
7289
|
-
staleEventsPath:
|
|
7290
|
-
embeddingsCachePath:
|
|
7291
|
-
policyViolationsPath:
|
|
7520
|
+
snapshotPath: import_node_path42.default.join(baseDir, `${project}.json`),
|
|
7521
|
+
errorsPath: import_node_path42.default.join(baseDir, `errors.${project}.ndjson`),
|
|
7522
|
+
staleEventsPath: import_node_path42.default.join(baseDir, `stale-events.${project}.ndjson`),
|
|
7523
|
+
embeddingsCachePath: import_node_path42.default.join(baseDir, `embeddings.${project}.json`),
|
|
7524
|
+
policyViolationsPath: import_node_path42.default.join(baseDir, `policy-violations.${project}.ndjson`)
|
|
7292
7525
|
};
|
|
7293
7526
|
}
|
|
7294
7527
|
var Projects = class {
|
|
@@ -7326,19 +7559,19 @@ var Projects = class {
|
|
|
7326
7559
|
init_cjs_shims();
|
|
7327
7560
|
var import_fastify = __toESM(require("fastify"), 1);
|
|
7328
7561
|
var import_cors = __toESM(require("@fastify/cors"), 1);
|
|
7329
|
-
var
|
|
7562
|
+
var import_types33 = require("@neat.is/types");
|
|
7330
7563
|
|
|
7331
7564
|
// src/extend/index.ts
|
|
7332
7565
|
init_cjs_shims();
|
|
7333
|
-
var
|
|
7334
|
-
var
|
|
7566
|
+
var import_node_fs26 = require("fs");
|
|
7567
|
+
var import_node_path44 = __toESM(require("path"), 1);
|
|
7335
7568
|
var import_node_os2 = __toESM(require("os"), 1);
|
|
7336
7569
|
var import_instrumentation_registry = require("@neat.is/instrumentation-registry");
|
|
7337
7570
|
|
|
7338
7571
|
// src/installers/package-manager.ts
|
|
7339
7572
|
init_cjs_shims();
|
|
7340
|
-
var
|
|
7341
|
-
var
|
|
7573
|
+
var import_node_fs25 = require("fs");
|
|
7574
|
+
var import_node_path43 = __toESM(require("path"), 1);
|
|
7342
7575
|
var import_node_child_process = require("child_process");
|
|
7343
7576
|
var LOCKFILE_PRIORITY = [
|
|
7344
7577
|
{ lockfile: "bun.lockb", pm: "bun", args: ["install", "--no-summary"] },
|
|
@@ -7353,29 +7586,29 @@ var LOCKFILE_PRIORITY = [
|
|
|
7353
7586
|
var NPM_FALLBACK_ARGS = ["install", "--no-audit", "--no-fund", "--prefer-offline"];
|
|
7354
7587
|
async function exists2(p) {
|
|
7355
7588
|
try {
|
|
7356
|
-
await
|
|
7589
|
+
await import_node_fs25.promises.access(p);
|
|
7357
7590
|
return true;
|
|
7358
7591
|
} catch {
|
|
7359
7592
|
return false;
|
|
7360
7593
|
}
|
|
7361
7594
|
}
|
|
7362
7595
|
async function detectPackageManager(serviceDir) {
|
|
7363
|
-
let dir =
|
|
7596
|
+
let dir = import_node_path43.default.resolve(serviceDir);
|
|
7364
7597
|
const stops = /* @__PURE__ */ new Set();
|
|
7365
7598
|
for (let i = 0; i < 64; i++) {
|
|
7366
7599
|
if (stops.has(dir)) break;
|
|
7367
7600
|
stops.add(dir);
|
|
7368
7601
|
for (const candidate of LOCKFILE_PRIORITY) {
|
|
7369
|
-
const lockPath =
|
|
7602
|
+
const lockPath = import_node_path43.default.join(dir, candidate.lockfile);
|
|
7370
7603
|
if (await exists2(lockPath)) {
|
|
7371
7604
|
return { pm: candidate.pm, cwd: dir, args: [...candidate.args] };
|
|
7372
7605
|
}
|
|
7373
7606
|
}
|
|
7374
|
-
const parent =
|
|
7607
|
+
const parent = import_node_path43.default.dirname(dir);
|
|
7375
7608
|
if (parent === dir) break;
|
|
7376
7609
|
dir = parent;
|
|
7377
7610
|
}
|
|
7378
|
-
return { pm: "npm", cwd:
|
|
7611
|
+
return { pm: "npm", cwd: import_node_path43.default.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
|
|
7379
7612
|
}
|
|
7380
7613
|
async function runPackageManagerInstall(cmd) {
|
|
7381
7614
|
return new Promise((resolve) => {
|
|
@@ -7417,30 +7650,30 @@ ${err.message}`
|
|
|
7417
7650
|
// src/extend/index.ts
|
|
7418
7651
|
async function fileExists2(p) {
|
|
7419
7652
|
try {
|
|
7420
|
-
await
|
|
7653
|
+
await import_node_fs26.promises.access(p);
|
|
7421
7654
|
return true;
|
|
7422
7655
|
} catch {
|
|
7423
7656
|
return false;
|
|
7424
7657
|
}
|
|
7425
7658
|
}
|
|
7426
7659
|
async function readPackageJson(scanPath) {
|
|
7427
|
-
const pkgPath =
|
|
7428
|
-
const raw = await
|
|
7660
|
+
const pkgPath = import_node_path44.default.join(scanPath, "package.json");
|
|
7661
|
+
const raw = await import_node_fs26.promises.readFile(pkgPath, "utf8");
|
|
7429
7662
|
return JSON.parse(raw);
|
|
7430
7663
|
}
|
|
7431
7664
|
async function findHookFiles(scanPath) {
|
|
7432
|
-
const entries = await
|
|
7665
|
+
const entries = await import_node_fs26.promises.readdir(scanPath);
|
|
7433
7666
|
return entries.filter(
|
|
7434
7667
|
(e) => (e.startsWith("instrumentation") || e.startsWith("otel-init")) && /\.(ts|js)$/.test(e)
|
|
7435
7668
|
).sort();
|
|
7436
7669
|
}
|
|
7437
7670
|
function extendLogPath() {
|
|
7438
|
-
return process.env.NEAT_EXTEND_LOG ??
|
|
7671
|
+
return process.env.NEAT_EXTEND_LOG ?? import_node_path44.default.join(import_node_os2.default.homedir(), ".neat", "extend-log.ndjson");
|
|
7439
7672
|
}
|
|
7440
7673
|
async function appendExtendLog(entry2) {
|
|
7441
7674
|
const logPath = extendLogPath();
|
|
7442
|
-
await
|
|
7443
|
-
await
|
|
7675
|
+
await import_node_fs26.promises.mkdir(import_node_path44.default.dirname(logPath), { recursive: true });
|
|
7676
|
+
await import_node_fs26.promises.appendFile(logPath, JSON.stringify(entry2) + "\n", "utf8");
|
|
7444
7677
|
}
|
|
7445
7678
|
function splicedContent(fileContent, snippet2) {
|
|
7446
7679
|
if (fileContent.includes("__INSTRUMENTATION_BLOCK__")) {
|
|
@@ -7498,7 +7731,7 @@ function lookupInstrumentation(library, installedVersion) {
|
|
|
7498
7731
|
}
|
|
7499
7732
|
async function describeProjectInstrumentation(ctx) {
|
|
7500
7733
|
const hookFiles = await findHookFiles(ctx.scanPath);
|
|
7501
|
-
const envNeat = await fileExists2(
|
|
7734
|
+
const envNeat = await fileExists2(import_node_path44.default.join(ctx.scanPath, ".env.neat"));
|
|
7502
7735
|
const registryInstrPackages = new Set(
|
|
7503
7736
|
(0, import_instrumentation_registry.list)().map((e) => e.instrumentation_package).filter((p) => !!p)
|
|
7504
7737
|
);
|
|
@@ -7520,31 +7753,31 @@ async function applyExtension(ctx, args, options) {
|
|
|
7520
7753
|
);
|
|
7521
7754
|
}
|
|
7522
7755
|
for (const file of hookFiles) {
|
|
7523
|
-
const content = await
|
|
7756
|
+
const content = await import_node_fs26.promises.readFile(import_node_path44.default.join(ctx.scanPath, file), "utf8");
|
|
7524
7757
|
if (content.includes(args.registration_snippet)) {
|
|
7525
7758
|
return { library: args.library, filesTouched: [], depsAdded: [], installOutput: "", alreadyApplied: true };
|
|
7526
7759
|
}
|
|
7527
7760
|
}
|
|
7528
7761
|
const primaryFile = hookFiles[0];
|
|
7529
|
-
const primaryPath =
|
|
7762
|
+
const primaryPath = import_node_path44.default.join(ctx.scanPath, primaryFile);
|
|
7530
7763
|
const filesTouched = [];
|
|
7531
7764
|
const depsAdded = [];
|
|
7532
|
-
const pkgPath =
|
|
7765
|
+
const pkgPath = import_node_path44.default.join(ctx.scanPath, "package.json");
|
|
7533
7766
|
const pkg = await readPackageJson(ctx.scanPath);
|
|
7534
7767
|
if (!(pkg.dependencies ?? {})[args.instrumentation_package]) {
|
|
7535
7768
|
pkg.dependencies = { ...pkg.dependencies ?? {}, [args.instrumentation_package]: args.version };
|
|
7536
|
-
await
|
|
7769
|
+
await import_node_fs26.promises.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
|
|
7537
7770
|
filesTouched.push("package.json");
|
|
7538
7771
|
depsAdded.push(`${args.instrumentation_package}@${args.version}`);
|
|
7539
7772
|
}
|
|
7540
|
-
const hookContent = await
|
|
7773
|
+
const hookContent = await import_node_fs26.promises.readFile(primaryPath, "utf8");
|
|
7541
7774
|
const patched = splicedContent(hookContent, args.registration_snippet);
|
|
7542
7775
|
if (!patched) {
|
|
7543
7776
|
throw new Error(
|
|
7544
7777
|
`Could not find instrumentation insertion point in ${primaryFile}. Expected __INSTRUMENTATION_BLOCK__, instrumentations.push(, or new NodeSDK(.`
|
|
7545
7778
|
);
|
|
7546
7779
|
}
|
|
7547
|
-
await
|
|
7780
|
+
await import_node_fs26.promises.writeFile(primaryPath, patched, "utf8");
|
|
7548
7781
|
filesTouched.push(primaryFile);
|
|
7549
7782
|
const cmd = await detectPackageManager(ctx.scanPath);
|
|
7550
7783
|
const installer = options?.runInstall ?? runPackageManagerInstall;
|
|
@@ -7575,7 +7808,7 @@ async function dryRunExtension(ctx, args) {
|
|
|
7575
7808
|
};
|
|
7576
7809
|
}
|
|
7577
7810
|
for (const file of hookFiles) {
|
|
7578
|
-
const content = await
|
|
7811
|
+
const content = await import_node_fs26.promises.readFile(import_node_path44.default.join(ctx.scanPath, file), "utf8");
|
|
7579
7812
|
if (content.includes(args.registration_snippet)) {
|
|
7580
7813
|
return {
|
|
7581
7814
|
library: args.library,
|
|
@@ -7597,7 +7830,7 @@ async function dryRunExtension(ctx, args) {
|
|
|
7597
7830
|
depsToAdd.push(`${args.instrumentation_package}@${args.version}`);
|
|
7598
7831
|
filesTouched.push("package.json");
|
|
7599
7832
|
}
|
|
7600
|
-
const hookContent = await
|
|
7833
|
+
const hookContent = await import_node_fs26.promises.readFile(import_node_path44.default.join(ctx.scanPath, primaryFile), "utf8");
|
|
7601
7834
|
const patched = splicedContent(hookContent, args.registration_snippet);
|
|
7602
7835
|
if (patched) {
|
|
7603
7836
|
filesTouched.push(primaryFile);
|
|
@@ -7612,28 +7845,28 @@ async function rollbackExtension(ctx, args) {
|
|
|
7612
7845
|
if (!await fileExists2(logPath)) {
|
|
7613
7846
|
return { undone: false, message: "no apply found for library" };
|
|
7614
7847
|
}
|
|
7615
|
-
const raw = await
|
|
7848
|
+
const raw = await import_node_fs26.promises.readFile(logPath, "utf8");
|
|
7616
7849
|
const entries = raw.trim().split("\n").filter(Boolean).map((line) => JSON.parse(line));
|
|
7617
7850
|
const match = [...entries].reverse().find((e) => e.project === ctx.project && e.library === args.library);
|
|
7618
7851
|
if (!match) {
|
|
7619
7852
|
return { undone: false, message: "no apply found for library" };
|
|
7620
7853
|
}
|
|
7621
|
-
const pkgPath =
|
|
7854
|
+
const pkgPath = import_node_path44.default.join(ctx.scanPath, "package.json");
|
|
7622
7855
|
if (await fileExists2(pkgPath)) {
|
|
7623
7856
|
const pkg = await readPackageJson(ctx.scanPath);
|
|
7624
7857
|
if (pkg.dependencies?.[match.instrumentation_package]) {
|
|
7625
7858
|
const { [match.instrumentation_package]: _removed, ...rest } = pkg.dependencies;
|
|
7626
7859
|
pkg.dependencies = rest;
|
|
7627
|
-
await
|
|
7860
|
+
await import_node_fs26.promises.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
|
|
7628
7861
|
}
|
|
7629
7862
|
}
|
|
7630
7863
|
const hookFiles = await findHookFiles(ctx.scanPath);
|
|
7631
7864
|
for (const file of hookFiles) {
|
|
7632
|
-
const filePath =
|
|
7633
|
-
const content = await
|
|
7865
|
+
const filePath = import_node_path44.default.join(ctx.scanPath, file);
|
|
7866
|
+
const content = await import_node_fs26.promises.readFile(filePath, "utf8");
|
|
7634
7867
|
if (content.includes(match.registration_snippet)) {
|
|
7635
7868
|
const filtered = content.split("\n").filter((line) => !line.includes(match.registration_snippet)).join("\n");
|
|
7636
|
-
await
|
|
7869
|
+
await import_node_fs26.promises.writeFile(filePath, filtered, "utf8");
|
|
7637
7870
|
break;
|
|
7638
7871
|
}
|
|
7639
7872
|
}
|
|
@@ -7645,7 +7878,7 @@ async function rollbackExtension(ctx, args) {
|
|
|
7645
7878
|
|
|
7646
7879
|
// src/divergences.ts
|
|
7647
7880
|
init_cjs_shims();
|
|
7648
|
-
var
|
|
7881
|
+
var import_types31 = require("@neat.is/types");
|
|
7649
7882
|
function bucketKey(source, target, type) {
|
|
7650
7883
|
return `${type}|${source}|${target}`;
|
|
7651
7884
|
}
|
|
@@ -7653,22 +7886,22 @@ function bucketEdges(graph) {
|
|
|
7653
7886
|
const buckets2 = /* @__PURE__ */ new Map();
|
|
7654
7887
|
graph.forEachEdge((id, attrs) => {
|
|
7655
7888
|
const e = attrs;
|
|
7656
|
-
const parsed = (0,
|
|
7889
|
+
const parsed = (0, import_types31.parseEdgeId)(id);
|
|
7657
7890
|
const provenance = parsed?.provenance ?? e.provenance;
|
|
7658
7891
|
const key = bucketKey(e.source, e.target, e.type);
|
|
7659
7892
|
const cur = buckets2.get(key) ?? { source: e.source, target: e.target, type: e.type };
|
|
7660
7893
|
switch (provenance) {
|
|
7661
|
-
case
|
|
7894
|
+
case import_types31.Provenance.EXTRACTED:
|
|
7662
7895
|
cur.extracted = e;
|
|
7663
7896
|
break;
|
|
7664
|
-
case
|
|
7897
|
+
case import_types31.Provenance.OBSERVED:
|
|
7665
7898
|
cur.observed = e;
|
|
7666
7899
|
break;
|
|
7667
|
-
case
|
|
7900
|
+
case import_types31.Provenance.INFERRED:
|
|
7668
7901
|
cur.inferred = e;
|
|
7669
7902
|
break;
|
|
7670
7903
|
default:
|
|
7671
|
-
if (e.provenance ===
|
|
7904
|
+
if (e.provenance === import_types31.Provenance.STALE) cur.stale = e;
|
|
7672
7905
|
}
|
|
7673
7906
|
buckets2.set(key, cur);
|
|
7674
7907
|
});
|
|
@@ -7677,12 +7910,12 @@ function bucketEdges(graph) {
|
|
|
7677
7910
|
function nodeIsFrontier(graph, nodeId) {
|
|
7678
7911
|
if (!graph.hasNode(nodeId)) return false;
|
|
7679
7912
|
const attrs = graph.getNodeAttributes(nodeId);
|
|
7680
|
-
return attrs.type ===
|
|
7913
|
+
return attrs.type === import_types31.NodeType.FrontierNode;
|
|
7681
7914
|
}
|
|
7682
7915
|
function nodeIsWebsocketChannel(graph, nodeId) {
|
|
7683
7916
|
if (!graph.hasNode(nodeId)) return false;
|
|
7684
7917
|
const attrs = graph.getNodeAttributes(nodeId);
|
|
7685
|
-
return attrs.type ===
|
|
7918
|
+
return attrs.type === import_types31.NodeType.WebSocketChannelNode;
|
|
7686
7919
|
}
|
|
7687
7920
|
function clampConfidence(n) {
|
|
7688
7921
|
if (!Number.isFinite(n)) return 0;
|
|
@@ -7702,14 +7935,14 @@ function gradedConfidence(edge) {
|
|
|
7702
7935
|
return clampConfidence(confidenceForEdge(edge));
|
|
7703
7936
|
}
|
|
7704
7937
|
var OBSERVABLE_EDGE_TYPES = /* @__PURE__ */ new Set([
|
|
7705
|
-
|
|
7706
|
-
|
|
7707
|
-
|
|
7708
|
-
|
|
7938
|
+
import_types31.EdgeType.CALLS,
|
|
7939
|
+
import_types31.EdgeType.CONNECTS_TO,
|
|
7940
|
+
import_types31.EdgeType.PUBLISHES_TO,
|
|
7941
|
+
import_types31.EdgeType.CONSUMES_FROM
|
|
7709
7942
|
]);
|
|
7710
7943
|
function detectMissingDivergences(graph, bucket) {
|
|
7711
7944
|
const out = [];
|
|
7712
|
-
if (bucket.type ===
|
|
7945
|
+
if (bucket.type === import_types31.EdgeType.CONTAINS) return out;
|
|
7713
7946
|
if (bucket.extracted && !bucket.observed && OBSERVABLE_EDGE_TYPES.has(bucket.type)) {
|
|
7714
7947
|
if (!nodeIsFrontier(graph, bucket.target)) {
|
|
7715
7948
|
out.push({
|
|
@@ -7750,7 +7983,7 @@ function declaredHostFor(svc) {
|
|
|
7750
7983
|
function hasExtractedConfiguredBy(graph, svcId) {
|
|
7751
7984
|
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
7752
7985
|
const e = graph.getEdgeAttributes(edgeId);
|
|
7753
|
-
if (e.type ===
|
|
7986
|
+
if (e.type === import_types31.EdgeType.CONFIGURED_BY && e.provenance === import_types31.Provenance.EXTRACTED) {
|
|
7754
7987
|
return true;
|
|
7755
7988
|
}
|
|
7756
7989
|
}
|
|
@@ -7763,10 +7996,10 @@ function detectHostMismatch(graph, svcId, svc) {
|
|
|
7763
7996
|
const out = [];
|
|
7764
7997
|
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
7765
7998
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
7766
|
-
if (edge.type !==
|
|
7767
|
-
if (edge.provenance !==
|
|
7999
|
+
if (edge.type !== import_types31.EdgeType.CONNECTS_TO) continue;
|
|
8000
|
+
if (edge.provenance !== import_types31.Provenance.OBSERVED) continue;
|
|
7768
8001
|
const target = graph.getNodeAttributes(edge.target);
|
|
7769
|
-
if (target.type !==
|
|
8002
|
+
if (target.type !== import_types31.NodeType.DatabaseNode) continue;
|
|
7770
8003
|
const observedHost = target.host?.trim();
|
|
7771
8004
|
if (!observedHost) continue;
|
|
7772
8005
|
if (observedHost === declaredHost) continue;
|
|
@@ -7788,10 +8021,10 @@ function detectCompatDivergences(graph, svcId, svc) {
|
|
|
7788
8021
|
const deps = svc.dependencies ?? {};
|
|
7789
8022
|
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
7790
8023
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
7791
|
-
if (edge.type !==
|
|
7792
|
-
if (edge.provenance !==
|
|
8024
|
+
if (edge.type !== import_types31.EdgeType.CONNECTS_TO) continue;
|
|
8025
|
+
if (edge.provenance !== import_types31.Provenance.OBSERVED) continue;
|
|
7793
8026
|
const target = graph.getNodeAttributes(edge.target);
|
|
7794
|
-
if (target.type !==
|
|
8027
|
+
if (target.type !== import_types31.NodeType.DatabaseNode) continue;
|
|
7795
8028
|
for (const pair of compatPairs()) {
|
|
7796
8029
|
if (pair.engine !== target.engine) continue;
|
|
7797
8030
|
const declared = deps[pair.driver];
|
|
@@ -7850,7 +8083,7 @@ function suppressHostMismatchHalves(all) {
|
|
|
7850
8083
|
for (const d of all) {
|
|
7851
8084
|
if (d.type !== "host-mismatch") continue;
|
|
7852
8085
|
observedHalf.add(`${d.source}->${d.target}`);
|
|
7853
|
-
declaredHalf.add((0,
|
|
8086
|
+
declaredHalf.add((0, import_types31.databaseId)(d.extractedHost));
|
|
7854
8087
|
}
|
|
7855
8088
|
if (observedHalf.size === 0) return all;
|
|
7856
8089
|
return all.filter((d) => {
|
|
@@ -7869,7 +8102,7 @@ function computeDivergences(graph, opts = {}) {
|
|
|
7869
8102
|
}
|
|
7870
8103
|
graph.forEachNode((nodeId, attrs) => {
|
|
7871
8104
|
const n = attrs;
|
|
7872
|
-
if (n.type !==
|
|
8105
|
+
if (n.type !== import_types31.NodeType.ServiceNode) return;
|
|
7873
8106
|
const svc = n;
|
|
7874
8107
|
for (const d of detectHostMismatch(graph, nodeId, svc)) all.push(d);
|
|
7875
8108
|
for (const d of detectCompatDivergences(graph, nodeId, svc)) all.push(d);
|
|
@@ -7903,7 +8136,7 @@ function computeDivergences(graph, opts = {}) {
|
|
|
7903
8136
|
if (a.source !== b.source) return a.source.localeCompare(b.source);
|
|
7904
8137
|
return a.target.localeCompare(b.target);
|
|
7905
8138
|
});
|
|
7906
|
-
return
|
|
8139
|
+
return import_types31.DivergenceResultSchema.parse({
|
|
7907
8140
|
divergences: filtered,
|
|
7908
8141
|
totalAffected: filtered.length,
|
|
7909
8142
|
computedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -7957,7 +8190,7 @@ function queryLogEntries(opts) {
|
|
|
7957
8190
|
|
|
7958
8191
|
// src/diff.ts
|
|
7959
8192
|
init_cjs_shims();
|
|
7960
|
-
var
|
|
8193
|
+
var import_node_fs27 = require("fs");
|
|
7961
8194
|
async function loadSnapshotForDiff(target) {
|
|
7962
8195
|
if (/^https?:\/\//i.test(target)) {
|
|
7963
8196
|
const res = await fetch(target);
|
|
@@ -7966,7 +8199,7 @@ async function loadSnapshotForDiff(target) {
|
|
|
7966
8199
|
}
|
|
7967
8200
|
return await res.json();
|
|
7968
8201
|
}
|
|
7969
|
-
const raw = await
|
|
8202
|
+
const raw = await import_node_fs27.promises.readFile(target, "utf8");
|
|
7970
8203
|
return JSON.parse(raw);
|
|
7971
8204
|
}
|
|
7972
8205
|
function indexEntries(entries) {
|
|
@@ -8034,25 +8267,25 @@ function canonicalJson(value) {
|
|
|
8034
8267
|
|
|
8035
8268
|
// src/registry.ts
|
|
8036
8269
|
init_cjs_shims();
|
|
8037
|
-
var
|
|
8270
|
+
var import_node_fs28 = require("fs");
|
|
8038
8271
|
var import_node_os3 = __toESM(require("os"), 1);
|
|
8039
|
-
var
|
|
8040
|
-
var
|
|
8272
|
+
var import_node_path45 = __toESM(require("path"), 1);
|
|
8273
|
+
var import_types32 = require("@neat.is/types");
|
|
8041
8274
|
var LOCK_TIMEOUT_MS = 5e3;
|
|
8042
8275
|
var LOCK_RETRY_MS = 50;
|
|
8043
8276
|
function neatHome() {
|
|
8044
8277
|
const override = process.env.NEAT_HOME;
|
|
8045
|
-
if (override && override.length > 0) return
|
|
8046
|
-
return
|
|
8278
|
+
if (override && override.length > 0) return import_node_path45.default.resolve(override);
|
|
8279
|
+
return import_node_path45.default.join(import_node_os3.default.homedir(), ".neat");
|
|
8047
8280
|
}
|
|
8048
8281
|
function registryPath() {
|
|
8049
|
-
return
|
|
8282
|
+
return import_node_path45.default.join(neatHome(), "projects.json");
|
|
8050
8283
|
}
|
|
8051
8284
|
function registryLockPath() {
|
|
8052
|
-
return
|
|
8285
|
+
return import_node_path45.default.join(neatHome(), "projects.json.lock");
|
|
8053
8286
|
}
|
|
8054
8287
|
function daemonPidPath() {
|
|
8055
|
-
return
|
|
8288
|
+
return import_node_path45.default.join(neatHome(), "neatd.pid");
|
|
8056
8289
|
}
|
|
8057
8290
|
function isPidAliveDefault(pid) {
|
|
8058
8291
|
try {
|
|
@@ -8064,7 +8297,7 @@ function isPidAliveDefault(pid) {
|
|
|
8064
8297
|
}
|
|
8065
8298
|
async function readPidFile(file) {
|
|
8066
8299
|
try {
|
|
8067
|
-
const raw = await
|
|
8300
|
+
const raw = await import_node_fs28.promises.readFile(file, "utf8");
|
|
8068
8301
|
const pid = Number.parseInt(raw.trim(), 10);
|
|
8069
8302
|
return Number.isInteger(pid) && pid > 0 ? pid : void 0;
|
|
8070
8303
|
} catch {
|
|
@@ -8112,24 +8345,24 @@ function lockHolderMessage(holder, lockPath, timeoutMs) {
|
|
|
8112
8345
|
}
|
|
8113
8346
|
}
|
|
8114
8347
|
async function writeAtomically(target, contents) {
|
|
8115
|
-
await
|
|
8348
|
+
await import_node_fs28.promises.mkdir(import_node_path45.default.dirname(target), { recursive: true });
|
|
8116
8349
|
const tmp = `${target}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
|
|
8117
|
-
const fd = await
|
|
8350
|
+
const fd = await import_node_fs28.promises.open(tmp, "w");
|
|
8118
8351
|
try {
|
|
8119
8352
|
await fd.writeFile(contents, "utf8");
|
|
8120
8353
|
await fd.sync();
|
|
8121
8354
|
} finally {
|
|
8122
8355
|
await fd.close();
|
|
8123
8356
|
}
|
|
8124
|
-
await
|
|
8357
|
+
await import_node_fs28.promises.rename(tmp, target);
|
|
8125
8358
|
}
|
|
8126
8359
|
async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaultLockHolderProbe) {
|
|
8127
8360
|
const deadline = Date.now() + timeoutMs;
|
|
8128
|
-
await
|
|
8361
|
+
await import_node_fs28.promises.mkdir(import_node_path45.default.dirname(lockPath), { recursive: true });
|
|
8129
8362
|
let probedHolder = false;
|
|
8130
8363
|
while (true) {
|
|
8131
8364
|
try {
|
|
8132
|
-
const fd = await
|
|
8365
|
+
const fd = await import_node_fs28.promises.open(lockPath, "wx");
|
|
8133
8366
|
try {
|
|
8134
8367
|
await fd.writeFile(`${process.pid}
|
|
8135
8368
|
`, "utf8");
|
|
@@ -8154,7 +8387,7 @@ async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaul
|
|
|
8154
8387
|
}
|
|
8155
8388
|
}
|
|
8156
8389
|
async function releaseLock(lockPath) {
|
|
8157
|
-
await
|
|
8390
|
+
await import_node_fs28.promises.unlink(lockPath).catch(() => {
|
|
8158
8391
|
});
|
|
8159
8392
|
}
|
|
8160
8393
|
async function withLock(fn) {
|
|
@@ -8170,7 +8403,7 @@ async function readRegistry() {
|
|
|
8170
8403
|
const file = registryPath();
|
|
8171
8404
|
let raw;
|
|
8172
8405
|
try {
|
|
8173
|
-
raw = await
|
|
8406
|
+
raw = await import_node_fs28.promises.readFile(file, "utf8");
|
|
8174
8407
|
} catch (err) {
|
|
8175
8408
|
if (err.code === "ENOENT") {
|
|
8176
8409
|
return { version: 1, projects: [] };
|
|
@@ -8178,10 +8411,10 @@ async function readRegistry() {
|
|
|
8178
8411
|
throw err;
|
|
8179
8412
|
}
|
|
8180
8413
|
const parsed = JSON.parse(raw);
|
|
8181
|
-
return
|
|
8414
|
+
return import_types32.RegistryFileSchema.parse(parsed);
|
|
8182
8415
|
}
|
|
8183
8416
|
async function writeRegistry(reg) {
|
|
8184
|
-
const validated =
|
|
8417
|
+
const validated = import_types32.RegistryFileSchema.parse(reg);
|
|
8185
8418
|
await writeAtomically(registryPath(), JSON.stringify(validated, null, 2) + "\n");
|
|
8186
8419
|
}
|
|
8187
8420
|
async function getProject(name) {
|
|
@@ -8225,7 +8458,7 @@ function pruneTtlMs() {
|
|
|
8225
8458
|
}
|
|
8226
8459
|
async function statPathStatus(p) {
|
|
8227
8460
|
try {
|
|
8228
|
-
const stat = await
|
|
8461
|
+
const stat = await import_node_fs28.promises.stat(p);
|
|
8229
8462
|
return stat.isDirectory() ? "present" : "unknown";
|
|
8230
8463
|
} catch (err) {
|
|
8231
8464
|
return err.code === "ENOENT" ? "gone" : "unknown";
|
|
@@ -8322,65 +8555,274 @@ data: ${JSON.stringify(envelope.payload)}
|
|
|
8322
8555
|
|
|
8323
8556
|
// src/api.ts
|
|
8324
8557
|
init_auth();
|
|
8325
|
-
|
|
8326
|
-
|
|
8327
|
-
|
|
8328
|
-
|
|
8329
|
-
|
|
8330
|
-
|
|
8331
|
-
|
|
8332
|
-
|
|
8333
|
-
|
|
8334
|
-
|
|
8335
|
-
|
|
8336
|
-
|
|
8337
|
-
|
|
8338
|
-
|
|
8339
|
-
|
|
8340
|
-
return named === void 0 || named === DEFAULT_PROJECT ? singleProject : named;
|
|
8558
|
+
|
|
8559
|
+
// src/connectors-config.ts
|
|
8560
|
+
init_cjs_shims();
|
|
8561
|
+
var import_node_os4 = __toESM(require("os"), 1);
|
|
8562
|
+
var import_node_path46 = __toESM(require("path"), 1);
|
|
8563
|
+
var import_node_fs29 = require("fs");
|
|
8564
|
+
var CONNECTORS_CONFIG_VERSION = 1;
|
|
8565
|
+
var EnvRefUnsetError = class extends Error {
|
|
8566
|
+
ref;
|
|
8567
|
+
varName;
|
|
8568
|
+
constructor(ref, varName) {
|
|
8569
|
+
super(`${ref} is unset`);
|
|
8570
|
+
this.name = "EnvRefUnsetError";
|
|
8571
|
+
this.ref = ref;
|
|
8572
|
+
this.varName = varName;
|
|
8341
8573
|
}
|
|
8342
|
-
|
|
8574
|
+
};
|
|
8575
|
+
function neatHome2() {
|
|
8576
|
+
const override = process.env.NEAT_HOME;
|
|
8577
|
+
if (override && override.length > 0) return import_node_path46.default.resolve(override);
|
|
8578
|
+
return import_node_path46.default.join(import_node_os4.default.homedir(), ".neat");
|
|
8343
8579
|
}
|
|
8344
|
-
function
|
|
8345
|
-
|
|
8346
|
-
|
|
8347
|
-
|
|
8348
|
-
|
|
8349
|
-
|
|
8350
|
-
|
|
8351
|
-
|
|
8352
|
-
|
|
8353
|
-
|
|
8354
|
-
|
|
8355
|
-
|
|
8580
|
+
function connectorsConfigPath(home = neatHome2()) {
|
|
8581
|
+
return import_node_path46.default.join(home, "connectors.json");
|
|
8582
|
+
}
|
|
8583
|
+
var MODE_MASK_LOOSER_THAN_0600 = 63;
|
|
8584
|
+
async function warnIfModeLooserThan0600(file) {
|
|
8585
|
+
if (process.platform === "win32") return;
|
|
8586
|
+
try {
|
|
8587
|
+
const stat = await import_node_fs29.promises.stat(file);
|
|
8588
|
+
if ((stat.mode & MODE_MASK_LOOSER_THAN_0600) !== 0) {
|
|
8589
|
+
const mode = (stat.mode & 511).toString(8).padStart(3, "0");
|
|
8590
|
+
console.warn(
|
|
8591
|
+
`[neat] ${file} is mode 0${mode}, looser than the 0600 this file's secrets call for \u2014 run \`chmod 600 ${file}\``
|
|
8592
|
+
);
|
|
8356
8593
|
}
|
|
8357
|
-
|
|
8358
|
-
return null;
|
|
8594
|
+
} catch {
|
|
8359
8595
|
}
|
|
8360
|
-
return ctx;
|
|
8361
8596
|
}
|
|
8362
|
-
function
|
|
8363
|
-
|
|
8364
|
-
|
|
8365
|
-
|
|
8597
|
+
async function readConnectorsConfig(home = neatHome2()) {
|
|
8598
|
+
const file = connectorsConfigPath(home);
|
|
8599
|
+
let raw;
|
|
8600
|
+
try {
|
|
8601
|
+
raw = await import_node_fs29.promises.readFile(file, "utf8");
|
|
8602
|
+
} catch (err) {
|
|
8603
|
+
if (err.code === "ENOENT") {
|
|
8604
|
+
return { version: CONNECTORS_CONFIG_VERSION, connectors: [] };
|
|
8605
|
+
}
|
|
8606
|
+
throw err;
|
|
8366
8607
|
}
|
|
8367
|
-
|
|
8368
|
-
|
|
8369
|
-
|
|
8370
|
-
|
|
8371
|
-
|
|
8372
|
-
|
|
8373
|
-
|
|
8374
|
-
|
|
8375
|
-
staleEventsPath: opts.staleEventsPath ?? paths.staleEventsPath,
|
|
8376
|
-
embeddingsCachePath: paths.embeddingsCachePath,
|
|
8377
|
-
policyViolationsPath: paths.policyViolationsPath
|
|
8378
|
-
},
|
|
8379
|
-
searchIndex: opts.searchIndex
|
|
8380
|
-
});
|
|
8381
|
-
return registry;
|
|
8608
|
+
await warnIfModeLooserThan0600(file);
|
|
8609
|
+
let parsed;
|
|
8610
|
+
try {
|
|
8611
|
+
parsed = JSON.parse(raw);
|
|
8612
|
+
} catch (err) {
|
|
8613
|
+
throw new Error(`${file} is not valid JSON: ${err.message}`);
|
|
8614
|
+
}
|
|
8615
|
+
return validateConfig(parsed, file);
|
|
8382
8616
|
}
|
|
8383
|
-
function
|
|
8617
|
+
function validateConfig(parsed, file) {
|
|
8618
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
8619
|
+
throw new Error(`${file} must be a JSON object with a "connectors" array`);
|
|
8620
|
+
}
|
|
8621
|
+
const obj = parsed;
|
|
8622
|
+
const version = obj.version === void 0 ? CONNECTORS_CONFIG_VERSION : obj.version;
|
|
8623
|
+
if (typeof version !== "number" || !Number.isInteger(version)) {
|
|
8624
|
+
throw new Error(`${file}: "version" must be an integer`);
|
|
8625
|
+
}
|
|
8626
|
+
const rawConnectors = obj.connectors;
|
|
8627
|
+
if (!Array.isArray(rawConnectors)) {
|
|
8628
|
+
throw new Error(`${file}: "connectors" must be an array`);
|
|
8629
|
+
}
|
|
8630
|
+
const connectors = rawConnectors.map((entry2, i) => validateEntry(entry2, i, file));
|
|
8631
|
+
return { version, connectors };
|
|
8632
|
+
}
|
|
8633
|
+
function validateEntry(entry2, index, file) {
|
|
8634
|
+
const where = `${file}: connectors[${index}]`;
|
|
8635
|
+
if (typeof entry2 !== "object" || entry2 === null || Array.isArray(entry2)) {
|
|
8636
|
+
throw new Error(`${where} must be an object`);
|
|
8637
|
+
}
|
|
8638
|
+
const e = entry2;
|
|
8639
|
+
const id = e.id;
|
|
8640
|
+
if (typeof id !== "string" || id.length === 0) {
|
|
8641
|
+
throw new Error(`${where}.id must be a non-empty string`);
|
|
8642
|
+
}
|
|
8643
|
+
const provider = e.provider;
|
|
8644
|
+
if (typeof provider !== "string" || provider.length === 0) {
|
|
8645
|
+
throw new Error(`${where}.provider must be a non-empty string`);
|
|
8646
|
+
}
|
|
8647
|
+
if (e.project !== void 0 && typeof e.project !== "string") {
|
|
8648
|
+
throw new Error(`${where}.project must be a string when present`);
|
|
8649
|
+
}
|
|
8650
|
+
const credential = validateCredentialRef(e.credential, `${where}.credential`);
|
|
8651
|
+
let options;
|
|
8652
|
+
if (e.options !== void 0) {
|
|
8653
|
+
if (typeof e.options !== "object" || e.options === null || Array.isArray(e.options)) {
|
|
8654
|
+
throw new Error(`${where}.options must be an object when present`);
|
|
8655
|
+
}
|
|
8656
|
+
options = e.options;
|
|
8657
|
+
}
|
|
8658
|
+
return {
|
|
8659
|
+
id,
|
|
8660
|
+
provider,
|
|
8661
|
+
...typeof e.project === "string" ? { project: e.project } : {},
|
|
8662
|
+
credential,
|
|
8663
|
+
...options ? { options } : {}
|
|
8664
|
+
};
|
|
8665
|
+
}
|
|
8666
|
+
function validateCredentialRef(raw, where) {
|
|
8667
|
+
if (typeof raw === "string") {
|
|
8668
|
+
if (raw.length === 0) throw new Error(`${where} must be a non-empty string`);
|
|
8669
|
+
return raw;
|
|
8670
|
+
}
|
|
8671
|
+
if (typeof raw === "object" && raw !== null && !Array.isArray(raw)) {
|
|
8672
|
+
const fields = raw;
|
|
8673
|
+
const keys = Object.keys(fields);
|
|
8674
|
+
if (keys.length === 0) throw new Error(`${where} object must have at least one field`);
|
|
8675
|
+
for (const key of keys) {
|
|
8676
|
+
const v = fields[key];
|
|
8677
|
+
if (typeof v !== "string" || v.length === 0) {
|
|
8678
|
+
throw new Error(`${where}.${key} must be a non-empty string`);
|
|
8679
|
+
}
|
|
8680
|
+
}
|
|
8681
|
+
return fields;
|
|
8682
|
+
}
|
|
8683
|
+
throw new Error(`${where} must be a string or an object of strings`);
|
|
8684
|
+
}
|
|
8685
|
+
function resolveCredential(ref, env = process.env) {
|
|
8686
|
+
if (typeof ref === "string") {
|
|
8687
|
+
return { kind: "single", value: resolveRef(ref, env) };
|
|
8688
|
+
}
|
|
8689
|
+
const fields = {};
|
|
8690
|
+
for (const [key, value] of Object.entries(ref)) {
|
|
8691
|
+
fields[key] = resolveRef(value, env);
|
|
8692
|
+
}
|
|
8693
|
+
return { kind: "fields", fields };
|
|
8694
|
+
}
|
|
8695
|
+
function resolveRef(value, env) {
|
|
8696
|
+
if (value.length > 1 && value.startsWith("$")) {
|
|
8697
|
+
const varName = value.slice(1);
|
|
8698
|
+
const resolved = env[varName];
|
|
8699
|
+
if (resolved === void 0 || resolved.length === 0) {
|
|
8700
|
+
throw new EnvRefUnsetError(value, varName);
|
|
8701
|
+
}
|
|
8702
|
+
return resolved;
|
|
8703
|
+
}
|
|
8704
|
+
return value;
|
|
8705
|
+
}
|
|
8706
|
+
function connectorMatchesProject(entry2, project) {
|
|
8707
|
+
return entry2.project === void 0 || entry2.project === project;
|
|
8708
|
+
}
|
|
8709
|
+
function isEnvRef(value) {
|
|
8710
|
+
return value.length > 1 && value.startsWith("$");
|
|
8711
|
+
}
|
|
8712
|
+
function redactCredentialRef(ref) {
|
|
8713
|
+
const one = (value) => isEnvRef(value) ? value : "****";
|
|
8714
|
+
if (typeof ref === "string") return one(ref);
|
|
8715
|
+
const out = {};
|
|
8716
|
+
for (const [key, value] of Object.entries(ref)) out[key] = one(value);
|
|
8717
|
+
return out;
|
|
8718
|
+
}
|
|
8719
|
+
|
|
8720
|
+
// src/connectors/status.ts
|
|
8721
|
+
init_cjs_shims();
|
|
8722
|
+
var CONNECTOR_STALE_THRESHOLD_MS = 5 * 6e4;
|
|
8723
|
+
var records = /* @__PURE__ */ new Map();
|
|
8724
|
+
var MAX_ERROR_LEN = 200;
|
|
8725
|
+
function sanitizePollError(err) {
|
|
8726
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
8727
|
+
const collapsed = msg.replace(/\s+/g, " ").trim();
|
|
8728
|
+
return collapsed.length > MAX_ERROR_LEN ? `${collapsed.slice(0, MAX_ERROR_LEN - 1)}\u2026` : collapsed;
|
|
8729
|
+
}
|
|
8730
|
+
function recordConnectorPoll(id, tick) {
|
|
8731
|
+
const prev = records.get(id);
|
|
8732
|
+
records.set(id, {
|
|
8733
|
+
lastPollAt: tick.at,
|
|
8734
|
+
lastOutcome: tick.outcome,
|
|
8735
|
+
lastError: tick.outcome === "error" ? tick.error ?? null : null,
|
|
8736
|
+
signalsLastPoll: tick.signalsLastPoll ?? 0,
|
|
8737
|
+
lastOkAt: tick.outcome === "ok" ? tick.at : prev?.lastOkAt ?? null
|
|
8738
|
+
});
|
|
8739
|
+
}
|
|
8740
|
+
function deriveState(rec, now, thresholdMs) {
|
|
8741
|
+
if (rec.lastOutcome === "error") return "error";
|
|
8742
|
+
const okAt = rec.lastOkAt ? Date.parse(rec.lastOkAt) : NaN;
|
|
8743
|
+
if (!Number.isNaN(okAt) && now - okAt > thresholdMs) return "stale";
|
|
8744
|
+
return "healthy";
|
|
8745
|
+
}
|
|
8746
|
+
function getConnectorStatus(id, now = Date.now(), thresholdMs = CONNECTOR_STALE_THRESHOLD_MS) {
|
|
8747
|
+
const rec = records.get(id);
|
|
8748
|
+
if (!rec) {
|
|
8749
|
+
return {
|
|
8750
|
+
state: "idle",
|
|
8751
|
+
lastPollAt: null,
|
|
8752
|
+
lastOutcome: null,
|
|
8753
|
+
lastError: null,
|
|
8754
|
+
signalsLastPoll: 0
|
|
8755
|
+
};
|
|
8756
|
+
}
|
|
8757
|
+
return {
|
|
8758
|
+
state: deriveState(rec, now, thresholdMs),
|
|
8759
|
+
lastPollAt: rec.lastPollAt,
|
|
8760
|
+
lastOutcome: rec.lastOutcome,
|
|
8761
|
+
lastError: rec.lastError,
|
|
8762
|
+
signalsLastPoll: rec.signalsLastPoll
|
|
8763
|
+
};
|
|
8764
|
+
}
|
|
8765
|
+
|
|
8766
|
+
// src/api.ts
|
|
8767
|
+
function serializeGraph(graph) {
|
|
8768
|
+
const nodes = [];
|
|
8769
|
+
graph.forEachNode((_id, attrs) => {
|
|
8770
|
+
nodes.push(attrs);
|
|
8771
|
+
});
|
|
8772
|
+
const edges = [];
|
|
8773
|
+
graph.forEachEdge((_id, attrs) => {
|
|
8774
|
+
edges.push(attrs);
|
|
8775
|
+
});
|
|
8776
|
+
return { nodes, edges };
|
|
8777
|
+
}
|
|
8778
|
+
function projectFromReq(req2, singleProject) {
|
|
8779
|
+
const params = req2.params;
|
|
8780
|
+
const named = params.project;
|
|
8781
|
+
if (singleProject) {
|
|
8782
|
+
return named === void 0 || named === DEFAULT_PROJECT ? singleProject : named;
|
|
8783
|
+
}
|
|
8784
|
+
return named ?? DEFAULT_PROJECT;
|
|
8785
|
+
}
|
|
8786
|
+
function resolveProject(registry, req2, reply, bootstrap, singleProject) {
|
|
8787
|
+
const name = projectFromReq(req2, singleProject);
|
|
8788
|
+
const ctx = registry.get(name);
|
|
8789
|
+
if (!ctx) {
|
|
8790
|
+
const phase = bootstrap?.status(name);
|
|
8791
|
+
if (phase === "bootstrapping") {
|
|
8792
|
+
void reply.code(503).send({ ready: false, project: name, status: "bootstrapping" });
|
|
8793
|
+
return null;
|
|
8794
|
+
}
|
|
8795
|
+
if (phase === "broken") {
|
|
8796
|
+
void reply.code(503).send({ ready: false, project: name, status: "broken" });
|
|
8797
|
+
return null;
|
|
8798
|
+
}
|
|
8799
|
+
void reply.code(404).send({ error: "project not found", project: name });
|
|
8800
|
+
return null;
|
|
8801
|
+
}
|
|
8802
|
+
return ctx;
|
|
8803
|
+
}
|
|
8804
|
+
function buildLegacyRegistry(opts) {
|
|
8805
|
+
if (opts.projects) return opts.projects;
|
|
8806
|
+
if (!opts.graph) {
|
|
8807
|
+
throw new Error("buildApi: either `projects` or `graph` must be provided");
|
|
8808
|
+
}
|
|
8809
|
+
const registry = new Projects();
|
|
8810
|
+
const paths = pathsForProject(DEFAULT_PROJECT, "");
|
|
8811
|
+
registry.set(DEFAULT_PROJECT, {
|
|
8812
|
+
graph: opts.graph,
|
|
8813
|
+
scanPath: opts.scanPath,
|
|
8814
|
+
paths: {
|
|
8815
|
+
snapshotPath: paths.snapshotPath,
|
|
8816
|
+
errorsPath: opts.errorsPath ?? paths.errorsPath,
|
|
8817
|
+
staleEventsPath: opts.staleEventsPath ?? paths.staleEventsPath,
|
|
8818
|
+
embeddingsCachePath: paths.embeddingsCachePath,
|
|
8819
|
+
policyViolationsPath: paths.policyViolationsPath
|
|
8820
|
+
},
|
|
8821
|
+
searchIndex: opts.searchIndex
|
|
8822
|
+
});
|
|
8823
|
+
return registry;
|
|
8824
|
+
}
|
|
8825
|
+
function registerRoutes(scope, ctx) {
|
|
8384
8826
|
const { registry, startedAt, errorsPathFor, staleEventsPathFor } = ctx;
|
|
8385
8827
|
scope.get("/events", (req2, reply) => {
|
|
8386
8828
|
const proj = resolveProject(registry, req2, reply, ctx.bootstrap, ctx.singleProject);
|
|
@@ -8472,11 +8914,11 @@ function registerRoutes(scope, ctx) {
|
|
|
8472
8914
|
const candidates = req2.query.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
8473
8915
|
const parsed = [];
|
|
8474
8916
|
for (const c of candidates) {
|
|
8475
|
-
const r =
|
|
8917
|
+
const r = import_types33.DivergenceTypeSchema.safeParse(c);
|
|
8476
8918
|
if (!r.success) {
|
|
8477
8919
|
return reply.code(400).send({
|
|
8478
8920
|
error: `unknown divergence type "${c}"`,
|
|
8479
|
-
allowed:
|
|
8921
|
+
allowed: import_types33.DivergenceTypeSchema.options
|
|
8480
8922
|
});
|
|
8481
8923
|
}
|
|
8482
8924
|
parsed.push(r.data);
|
|
@@ -8541,6 +8983,26 @@ function registerRoutes(scope, ctx) {
|
|
|
8541
8983
|
const sliced = filtered.slice(0, safeLimit);
|
|
8542
8984
|
return { count: sliced.length, total, logs: sliced };
|
|
8543
8985
|
});
|
|
8986
|
+
scope.get("/connectors", async (req2, reply) => {
|
|
8987
|
+
const proj = resolveProject(registry, req2, reply, ctx.bootstrap, ctx.singleProject);
|
|
8988
|
+
if (!proj) return;
|
|
8989
|
+
let entries;
|
|
8990
|
+
try {
|
|
8991
|
+
entries = (await readConnectorsConfig(ctx.connectorsHome)).connectors;
|
|
8992
|
+
} catch (err) {
|
|
8993
|
+
return reply.code(500).send({
|
|
8994
|
+
error: "connectors.json failed to read",
|
|
8995
|
+
details: err.message
|
|
8996
|
+
});
|
|
8997
|
+
}
|
|
8998
|
+
const connectors = entries.filter((entry2) => connectorMatchesProject(entry2, proj.name)).map((entry2) => ({
|
|
8999
|
+
id: entry2.id,
|
|
9000
|
+
provider: entry2.provider,
|
|
9001
|
+
credentialRef: redactCredentialRef(entry2.credential),
|
|
9002
|
+
status: getConnectorStatus(entry2.id)
|
|
9003
|
+
}));
|
|
9004
|
+
return { connectors };
|
|
9005
|
+
});
|
|
8544
9006
|
const incidentHistoryHandler = async (req2, reply) => {
|
|
8545
9007
|
const proj = resolveProject(registry, req2, reply, ctx.bootstrap, ctx.singleProject);
|
|
8546
9008
|
if (!proj) return;
|
|
@@ -8727,7 +9189,7 @@ function registerRoutes(scope, ctx) {
|
|
|
8727
9189
|
const log = new PolicyViolationsLog(proj.paths.policyViolationsPath);
|
|
8728
9190
|
let violations = await log.readAll();
|
|
8729
9191
|
if (req2.query.severity) {
|
|
8730
|
-
const sev =
|
|
9192
|
+
const sev = import_types33.PolicySeveritySchema.safeParse(req2.query.severity);
|
|
8731
9193
|
if (!sev.success) {
|
|
8732
9194
|
return reply.code(400).send({
|
|
8733
9195
|
error: "invalid severity",
|
|
@@ -8766,7 +9228,7 @@ function registerRoutes(scope, ctx) {
|
|
|
8766
9228
|
scope.post("/policies/check", async (req2, reply) => {
|
|
8767
9229
|
const proj = resolveProject(registry, req2, reply, ctx.bootstrap, ctx.singleProject);
|
|
8768
9230
|
if (!proj) return;
|
|
8769
|
-
const parsed =
|
|
9231
|
+
const parsed = import_types33.PoliciesCheckBodySchema.safeParse(req2.body ?? {});
|
|
8770
9232
|
if (!parsed.success) {
|
|
8771
9233
|
return reply.code(400).send({
|
|
8772
9234
|
error: "invalid /policies/check body",
|
|
@@ -8943,7 +9405,8 @@ async function buildApi(opts) {
|
|
|
8943
9405
|
staleEventsPathFor,
|
|
8944
9406
|
policyFilePathFor,
|
|
8945
9407
|
bootstrap: opts.bootstrap,
|
|
8946
|
-
singleProject: opts.singleProject?.name
|
|
9408
|
+
singleProject: opts.singleProject?.name,
|
|
9409
|
+
connectorsHome: opts.connectorsHome
|
|
8947
9410
|
};
|
|
8948
9411
|
app.get("/health", async () => {
|
|
8949
9412
|
const uptimeMs = Date.now() - startedAt;
|
|
@@ -9085,16 +9548,31 @@ function startConnectorPollLoop(connector, ctx, graph, resolveTarget, options =
|
|
|
9085
9548
|
let stopped = false;
|
|
9086
9549
|
let since = ctx.since;
|
|
9087
9550
|
const intervalMs = options.intervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
9551
|
+
const connectorId = options.connectorId;
|
|
9088
9552
|
const onError = options.onError ?? ((err) => console.error(`[neatd] connector poll failed (${connector.provider})`, err));
|
|
9089
9553
|
const tick = () => {
|
|
9090
9554
|
if (stopped) return;
|
|
9091
9555
|
void (async () => {
|
|
9092
9556
|
const tickStartedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
9093
9557
|
try {
|
|
9094
|
-
await runConnectorPoll(connector, { ...ctx, since }, graph, resolveTarget);
|
|
9558
|
+
const result = await runConnectorPoll(connector, { ...ctx, since }, graph, resolveTarget);
|
|
9095
9559
|
since = tickStartedAt;
|
|
9560
|
+
if (connectorId) {
|
|
9561
|
+
recordConnectorPoll(connectorId, {
|
|
9562
|
+
outcome: "ok",
|
|
9563
|
+
at: tickStartedAt,
|
|
9564
|
+
signalsLastPoll: result.signalCount
|
|
9565
|
+
});
|
|
9566
|
+
}
|
|
9096
9567
|
} catch (err) {
|
|
9097
9568
|
onError(err);
|
|
9569
|
+
if (connectorId) {
|
|
9570
|
+
recordConnectorPoll(connectorId, {
|
|
9571
|
+
outcome: "error",
|
|
9572
|
+
at: tickStartedAt,
|
|
9573
|
+
error: sanitizePollError(err)
|
|
9574
|
+
});
|
|
9575
|
+
}
|
|
9098
9576
|
}
|
|
9099
9577
|
})();
|
|
9100
9578
|
};
|
|
@@ -9393,6 +9871,30 @@ function buildEdgeLogsQuery(limit) {
|
|
|
9393
9871
|
`limit ${safeLimit}`
|
|
9394
9872
|
].join("\n");
|
|
9395
9873
|
}
|
|
9874
|
+
function logsAllHttpFailureMessage(status2) {
|
|
9875
|
+
if (status2 === 401 || status2 === 403) {
|
|
9876
|
+
return `supabase connector: logs.all request rejected (HTTP ${status2}). Check the Management API token, its analytics read scope, and --api-project-ref.`;
|
|
9877
|
+
}
|
|
9878
|
+
if (status2 === 404) {
|
|
9879
|
+
return "supabase connector: logs.all project not found (HTTP 404). Check --api-project-ref.";
|
|
9880
|
+
}
|
|
9881
|
+
if (status2 === 429) {
|
|
9882
|
+
return "supabase connector: logs.all rate-limited (HTTP 429). The connector will retry on the next poll; reduce poll cadence if this persists.";
|
|
9883
|
+
}
|
|
9884
|
+
if (status2 === 400) {
|
|
9885
|
+
return "supabase connector: logs.all query rejected (HTTP 400). Provider details redacted; confirm the live log-query dialect before shipping this connector.";
|
|
9886
|
+
}
|
|
9887
|
+
return `supabase connector: logs.all request failed (HTTP ${status2}); provider details redacted.`;
|
|
9888
|
+
}
|
|
9889
|
+
function providerErrorDetails(error) {
|
|
9890
|
+
if (!error || typeof error === "string") return "";
|
|
9891
|
+
const parts = [];
|
|
9892
|
+
if (typeof error.code === "number") parts.push(`code ${error.code}`);
|
|
9893
|
+
if (typeof error.status === "string" && /^[A-Z0-9_.-]+$/.test(error.status)) {
|
|
9894
|
+
parts.push(`status ${error.status}`);
|
|
9895
|
+
}
|
|
9896
|
+
return parts.length > 0 ? ` (${parts.join(", ")})` : "";
|
|
9897
|
+
}
|
|
9396
9898
|
async function fetchSupabaseEdgeLogs(config, token, startIso, endIso, fetchImpl = fetch) {
|
|
9397
9899
|
const baseUrl = config.managementApiUrl ?? DEFAULT_SUPABASE_MANAGEMENT_API_URL;
|
|
9398
9900
|
const url = new URL(`${baseUrl}/v1/projects/${config.apiProjectRef}/analytics/endpoints/logs.all`);
|
|
@@ -9407,12 +9909,18 @@ async function fetchSupabaseEdgeLogs(config, token, startIso, endIso, fetchImpl
|
|
|
9407
9909
|
{ provider: "supabase", accountKey: config.apiProjectRef, fetchImpl }
|
|
9408
9910
|
);
|
|
9409
9911
|
if (!res.ok) {
|
|
9410
|
-
throw new Error(
|
|
9912
|
+
throw new Error(logsAllHttpFailureMessage(res.status));
|
|
9913
|
+
}
|
|
9914
|
+
let body;
|
|
9915
|
+
try {
|
|
9916
|
+
body = await res.json();
|
|
9917
|
+
} catch {
|
|
9918
|
+
throw new Error("supabase connector: logs.all returned invalid JSON; provider details redacted.");
|
|
9411
9919
|
}
|
|
9412
|
-
const body = await res.json();
|
|
9413
9920
|
if (body.error) {
|
|
9414
|
-
|
|
9415
|
-
|
|
9921
|
+
throw new Error(
|
|
9922
|
+
`supabase connector: logs.all returned a provider error${providerErrorDetails(body.error)}; provider message redacted.`
|
|
9923
|
+
);
|
|
9416
9924
|
}
|
|
9417
9925
|
return body.result ?? [];
|
|
9418
9926
|
}
|
|
@@ -9444,10 +9952,10 @@ var SUPABASE_RPC_TARGET_KIND = "supabase-rpc";
|
|
|
9444
9952
|
// src/connectors/supabase/map.ts
|
|
9445
9953
|
var REST_RPC_PATH_RE = /^\/rest\/v1\/rpc\/([^/?]+)/;
|
|
9446
9954
|
var REST_TABLE_PATH_RE = /^\/rest\/v1\/([^/?]+)/;
|
|
9447
|
-
function targetFromRestPath(
|
|
9448
|
-
const rpcMatch = REST_RPC_PATH_RE.exec(
|
|
9955
|
+
function targetFromRestPath(path53) {
|
|
9956
|
+
const rpcMatch = REST_RPC_PATH_RE.exec(path53);
|
|
9449
9957
|
if (rpcMatch) return { targetKind: SUPABASE_RPC_TARGET_KIND, name: rpcMatch[1] };
|
|
9450
|
-
const tableMatch = REST_TABLE_PATH_RE.exec(
|
|
9958
|
+
const tableMatch = REST_TABLE_PATH_RE.exec(path53);
|
|
9451
9959
|
if (tableMatch) return { targetKind: SUPABASE_TABLE_TARGET_KIND, name: tableMatch[1] };
|
|
9452
9960
|
return null;
|
|
9453
9961
|
}
|
|
@@ -9550,19 +10058,19 @@ async function fetchPgStatStatements(connectionString, limit = DEFAULT_STATEMENT
|
|
|
9550
10058
|
|
|
9551
10059
|
// src/connectors/supabase/resolve.ts
|
|
9552
10060
|
init_cjs_shims();
|
|
9553
|
-
var
|
|
10061
|
+
var import_types35 = require("@neat.is/types");
|
|
9554
10062
|
function createSupabaseResolveTarget(graph, config) {
|
|
9555
10063
|
return (signal, _ctx) => {
|
|
9556
10064
|
if (signal.targetKind !== SUPABASE_TABLE_TARGET_KIND && signal.targetKind !== SUPABASE_RPC_TARGET_KIND) {
|
|
9557
10065
|
return null;
|
|
9558
10066
|
}
|
|
9559
|
-
const subResourceId = (0,
|
|
10067
|
+
const subResourceId = (0, import_types35.infraId)(signal.targetKind, `${config.nodeRef}/${signal.targetName}`);
|
|
9560
10068
|
if (graph.hasNode(subResourceId)) {
|
|
9561
|
-
return { targetNodeId: subResourceId, serviceName: config.serviceName, edgeType:
|
|
10069
|
+
return { targetNodeId: subResourceId, serviceName: config.serviceName, edgeType: import_types35.EdgeType.CALLS };
|
|
9562
10070
|
}
|
|
9563
|
-
const projectLevelId = (0,
|
|
10071
|
+
const projectLevelId = (0, import_types35.infraId)("supabase", config.nodeRef);
|
|
9564
10072
|
if (graph.hasNode(projectLevelId)) {
|
|
9565
|
-
return { targetNodeId: projectLevelId, serviceName: config.serviceName, edgeType:
|
|
10073
|
+
return { targetNodeId: projectLevelId, serviceName: config.serviceName, edgeType: import_types35.EdgeType.CALLS };
|
|
9566
10074
|
}
|
|
9567
10075
|
return null;
|
|
9568
10076
|
};
|
|
@@ -9570,6 +10078,35 @@ function createSupabaseResolveTarget(graph, config) {
|
|
|
9570
10078
|
|
|
9571
10079
|
// src/connectors/supabase/index.ts
|
|
9572
10080
|
var DEFAULT_MAX_LOOKBACK_MS = 24 * 60 * 60 * 1e3;
|
|
10081
|
+
function errorCode(err) {
|
|
10082
|
+
const code = err?.code;
|
|
10083
|
+
return typeof code === "string" && code.length > 0 ? code : void 0;
|
|
10084
|
+
}
|
|
10085
|
+
function describeSupabasePostgresSurfaceFailure(projectRef, err) {
|
|
10086
|
+
const code = errorCode(err);
|
|
10087
|
+
let reason;
|
|
10088
|
+
switch (code) {
|
|
10089
|
+
case "42501":
|
|
10090
|
+
reason = "permission denied; grant pg_read_all_stats to the configured Postgres role";
|
|
10091
|
+
break;
|
|
10092
|
+
case "42P01":
|
|
10093
|
+
case "42704":
|
|
10094
|
+
reason = "pg_stat_statements is not enabled or visible to the configured Postgres role";
|
|
10095
|
+
break;
|
|
10096
|
+
case "28P01":
|
|
10097
|
+
case "28000":
|
|
10098
|
+
reason = "Postgres credential rejected";
|
|
10099
|
+
break;
|
|
10100
|
+
case "3D000":
|
|
10101
|
+
reason = "database not found";
|
|
10102
|
+
break;
|
|
10103
|
+
default: {
|
|
10104
|
+
const name = err instanceof Error && err.name ? err.name : "Error";
|
|
10105
|
+
reason = code ? `${name} ${code}` : name;
|
|
10106
|
+
}
|
|
10107
|
+
}
|
|
10108
|
+
return `supabase connector: pg_stat_statements surface unavailable for project ${projectRef} (${reason}); continuing with Management API log surface.`;
|
|
10109
|
+
}
|
|
9573
10110
|
var SupabaseConnector = class {
|
|
9574
10111
|
// `deps.fetchPgStatStatements` defaults to the real Postgres-backed
|
|
9575
10112
|
// implementation; tests override it to exercise the "both surfaces
|
|
@@ -9601,12 +10138,18 @@ var SupabaseConnector = class {
|
|
|
9601
10138
|
const signals = mapEdgeLogRowsToSignals(logRows);
|
|
9602
10139
|
if (creds.postgresConnectionString) {
|
|
9603
10140
|
const fetchStatements = this.deps.fetchPgStatStatements ?? fetchPgStatStatements;
|
|
9604
|
-
|
|
9605
|
-
|
|
9606
|
-
|
|
9607
|
-
|
|
9608
|
-
|
|
9609
|
-
|
|
10141
|
+
try {
|
|
10142
|
+
const statementRows = await fetchStatements(
|
|
10143
|
+
creds.postgresConnectionString,
|
|
10144
|
+
this.config.statementLimit ?? DEFAULT_STATEMENT_LIMIT,
|
|
10145
|
+
this.config.apiProjectRef
|
|
10146
|
+
);
|
|
10147
|
+
signals.push(...diffPgStatStatementsToSignals(statementRows, this.statementBaselines, now.toISOString()));
|
|
10148
|
+
} catch (err) {
|
|
10149
|
+
const summary = describeSupabasePostgresSurfaceFailure(this.config.apiProjectRef, err);
|
|
10150
|
+
if (this.deps.onPostgresSurfaceError) this.deps.onPostgresSurfaceError(err, summary);
|
|
10151
|
+
else console.warn(summary);
|
|
10152
|
+
}
|
|
9610
10153
|
}
|
|
9611
10154
|
return signals;
|
|
9612
10155
|
}
|
|
@@ -9620,7 +10163,7 @@ function createSupabaseConnector(graph, config, deps = {}) {
|
|
|
9620
10163
|
|
|
9621
10164
|
// src/connectors/railway/index.ts
|
|
9622
10165
|
init_cjs_shims();
|
|
9623
|
-
var
|
|
10166
|
+
var import_types39 = require("@neat.is/types");
|
|
9624
10167
|
|
|
9625
10168
|
// src/connectors/railway/client.ts
|
|
9626
10169
|
init_cjs_shims();
|
|
@@ -9765,7 +10308,7 @@ function buildRailwayRouteIndex(graph, serviceName) {
|
|
|
9765
10308
|
const out = [];
|
|
9766
10309
|
graph.forEachNode((_id, attrs) => {
|
|
9767
10310
|
const node = attrs;
|
|
9768
|
-
if (node.type !==
|
|
10311
|
+
if (node.type !== import_types39.NodeType.RouteNode) return;
|
|
9769
10312
|
const route = attrs;
|
|
9770
10313
|
if (route.service !== serviceName) return;
|
|
9771
10314
|
out.push({
|
|
@@ -9861,12 +10404,12 @@ function createRailwayResolveTarget(config) {
|
|
|
9861
10404
|
const serviceName = config.serviceNameById[config.serviceId];
|
|
9862
10405
|
if (!serviceName) return null;
|
|
9863
10406
|
if (signal.targetKind === ROUTE_TARGET_KIND) {
|
|
9864
|
-
return { targetNodeId: signal.targetName, serviceName, edgeType:
|
|
10407
|
+
return { targetNodeId: signal.targetName, serviceName, edgeType: import_types39.EdgeType.CALLS };
|
|
9865
10408
|
}
|
|
9866
10409
|
if (signal.targetKind === PEER_SERVICE_TARGET_KIND) {
|
|
9867
10410
|
const peerName = config.serviceNameById[signal.targetName];
|
|
9868
10411
|
if (!peerName) return null;
|
|
9869
|
-
return { targetNodeId: (0,
|
|
10412
|
+
return { targetNodeId: (0, import_types39.serviceId)(peerName), serviceName, edgeType: import_types39.EdgeType.CONNECTS_TO };
|
|
9870
10413
|
}
|
|
9871
10414
|
return null;
|
|
9872
10415
|
};
|
|
@@ -9981,9 +10524,9 @@ function parseFirebaseTargetName(targetName) {
|
|
|
9981
10524
|
const secondSep = rest.indexOf(FIELD_SEP);
|
|
9982
10525
|
if (secondSep === -1) return null;
|
|
9983
10526
|
const method = rest.slice(0, secondSep);
|
|
9984
|
-
const
|
|
9985
|
-
if (!resourceName || !method || !
|
|
9986
|
-
return { resourceName, method, path:
|
|
10527
|
+
const path53 = rest.slice(secondSep + 1);
|
|
10528
|
+
if (!resourceName || !method || !path53) return null;
|
|
10529
|
+
return { resourceName, method, path: path53 };
|
|
9987
10530
|
}
|
|
9988
10531
|
function resourceNameFor(type, labels) {
|
|
9989
10532
|
if (!labels) return null;
|
|
@@ -10020,14 +10563,14 @@ function mapLogEntryToSignal(entry2) {
|
|
|
10020
10563
|
if (!req2) return null;
|
|
10021
10564
|
if (!req2.requestMethod) return null;
|
|
10022
10565
|
const method = req2.requestMethod.toUpperCase();
|
|
10023
|
-
const
|
|
10024
|
-
if (
|
|
10566
|
+
const path53 = pathFromRequestUrl(req2.requestUrl);
|
|
10567
|
+
if (path53 === null) return null;
|
|
10025
10568
|
const timestamp = entry2.timestamp;
|
|
10026
10569
|
if (!timestamp) return null;
|
|
10027
10570
|
const isError = typeof req2.status === "number" && req2.status >= ERROR_STATUS_THRESHOLD2;
|
|
10028
10571
|
return {
|
|
10029
10572
|
targetKind: resourceType,
|
|
10030
|
-
targetName: packFirebaseTargetName({ resourceName, method, path:
|
|
10573
|
+
targetName: packFirebaseTargetName({ resourceName, method, path: path53 }),
|
|
10031
10574
|
callCount: 1,
|
|
10032
10575
|
errorCount: isError ? 1 : 0,
|
|
10033
10576
|
lastObservedIso: timestamp
|
|
@@ -10044,7 +10587,7 @@ function mapLogEntriesToSignals(entries) {
|
|
|
10044
10587
|
|
|
10045
10588
|
// src/connectors/firebase/resolve.ts
|
|
10046
10589
|
init_cjs_shims();
|
|
10047
|
-
var
|
|
10590
|
+
var import_types40 = require("@neat.is/types");
|
|
10048
10591
|
function neatServiceNameFor(resourceType, resourceName, serviceMap) {
|
|
10049
10592
|
switch (resourceType) {
|
|
10050
10593
|
case "cloud_function":
|
|
@@ -10059,7 +10602,7 @@ function routeEntriesFor(graph, serviceName) {
|
|
|
10059
10602
|
const entries = [];
|
|
10060
10603
|
graph.forEachNode((_id, attrs) => {
|
|
10061
10604
|
const node = attrs;
|
|
10062
|
-
if (node.type !==
|
|
10605
|
+
if (node.type !== import_types40.NodeType.RouteNode) return;
|
|
10063
10606
|
const route = attrs;
|
|
10064
10607
|
if (route.service !== serviceName) return;
|
|
10065
10608
|
entries.push({
|
|
@@ -10091,7 +10634,7 @@ function createFirebaseResolveTarget(graph, serviceMap) {
|
|
|
10091
10634
|
return {
|
|
10092
10635
|
targetNodeId: match.routeNodeId,
|
|
10093
10636
|
serviceName,
|
|
10094
|
-
edgeType:
|
|
10637
|
+
edgeType: import_types40.EdgeType.CALLS
|
|
10095
10638
|
};
|
|
10096
10639
|
};
|
|
10097
10640
|
}
|
|
@@ -10118,7 +10661,7 @@ init_cjs_shims();
|
|
|
10118
10661
|
|
|
10119
10662
|
// src/connectors/cloudflare/connector.ts
|
|
10120
10663
|
init_cjs_shims();
|
|
10121
|
-
var
|
|
10664
|
+
var import_types42 = require("@neat.is/types");
|
|
10122
10665
|
|
|
10123
10666
|
// src/connectors/cloudflare/client.ts
|
|
10124
10667
|
init_cjs_shims();
|
|
@@ -10225,7 +10768,7 @@ function mapEventToSignal(event) {
|
|
|
10225
10768
|
if (typeof timestampMs !== "number" || !Number.isFinite(timestampMs)) return null;
|
|
10226
10769
|
const statusCode = metadata?.statusCode;
|
|
10227
10770
|
const isError = typeof statusCode === "number" && statusCode >= ERROR_STATUS_THRESHOLD3;
|
|
10228
|
-
const
|
|
10771
|
+
const path53 = metadata?.trigger ? parsePathFromTrigger(metadata.trigger) : void 0;
|
|
10229
10772
|
return {
|
|
10230
10773
|
targetKind: CLOUDFLARE_TARGET_KIND,
|
|
10231
10774
|
targetName: scriptName,
|
|
@@ -10233,7 +10776,7 @@ function mapEventToSignal(event) {
|
|
|
10233
10776
|
errorCount: isError ? 1 : 0,
|
|
10234
10777
|
lastObservedIso: new Date(timestampMs).toISOString(),
|
|
10235
10778
|
method,
|
|
10236
|
-
...
|
|
10779
|
+
...path53 ? { path: path53 } : {},
|
|
10237
10780
|
...typeof statusCode === "number" ? { statusCode } : {},
|
|
10238
10781
|
...typeof metadata?.duration === "number" ? { duration: metadata.duration } : {}
|
|
10239
10782
|
};
|
|
@@ -10273,19 +10816,19 @@ function findTaggedWorkerFileNode(graph, workerName) {
|
|
|
10273
10816
|
graph.forEachNode((id, attrs) => {
|
|
10274
10817
|
if (found) return;
|
|
10275
10818
|
const a = attrs;
|
|
10276
|
-
if (a.type ===
|
|
10819
|
+
if (a.type === import_types42.NodeType.FileNode && a.platform === "cloudflare" && a.platformName === workerName) {
|
|
10277
10820
|
found = id;
|
|
10278
10821
|
}
|
|
10279
10822
|
});
|
|
10280
10823
|
return found;
|
|
10281
10824
|
}
|
|
10282
|
-
function findMatchingRouteNode(graph, serviceName, method,
|
|
10283
|
-
const normalizedPath = normalizePathTemplate(
|
|
10825
|
+
function findMatchingRouteNode(graph, serviceName, method, path53) {
|
|
10826
|
+
const normalizedPath = normalizePathTemplate(path53);
|
|
10284
10827
|
let found = null;
|
|
10285
10828
|
graph.forEachNode((id, attrs) => {
|
|
10286
10829
|
if (found) return;
|
|
10287
10830
|
const a = attrs;
|
|
10288
|
-
if (a.type !==
|
|
10831
|
+
if (a.type !== import_types42.NodeType.RouteNode || a.service !== serviceName) return;
|
|
10289
10832
|
if (!a.pathTemplate || normalizePathTemplate(a.pathTemplate) !== normalizedPath) return;
|
|
10290
10833
|
const routeMethod = (a.method ?? "").toUpperCase();
|
|
10291
10834
|
if (routeMethod !== "ALL" && routeMethod !== method) return;
|
|
@@ -10297,18 +10840,18 @@ function createCloudflareResolveTarget(config, graph) {
|
|
|
10297
10840
|
return (signal) => {
|
|
10298
10841
|
if (signal.targetKind !== CLOUDFLARE_TARGET_KIND) return null;
|
|
10299
10842
|
const scriptName = signal.targetName;
|
|
10300
|
-
const { method, path:
|
|
10843
|
+
const { method, path: path53 } = signal;
|
|
10301
10844
|
const resolveRouteGrain = (serviceName, wholeFileId) => {
|
|
10302
|
-
if (!method || !
|
|
10303
|
-
return findMatchingRouteNode(graph, serviceName, method,
|
|
10845
|
+
if (!method || !path53) return wholeFileId;
|
|
10846
|
+
return findMatchingRouteNode(graph, serviceName, method, path53) ?? wholeFileId;
|
|
10304
10847
|
};
|
|
10305
10848
|
const mapping = config.workers?.[scriptName];
|
|
10306
10849
|
if (mapping) {
|
|
10307
|
-
const wholeFileId = (0,
|
|
10850
|
+
const wholeFileId = (0, import_types42.fileId)(mapping.service, mapping.entryFile);
|
|
10308
10851
|
return {
|
|
10309
10852
|
targetNodeId: resolveRouteGrain(mapping.service, wholeFileId),
|
|
10310
10853
|
serviceName: mapping.service,
|
|
10311
|
-
edgeType:
|
|
10854
|
+
edgeType: import_types42.EdgeType.CALLS
|
|
10312
10855
|
};
|
|
10313
10856
|
}
|
|
10314
10857
|
const taggedFileId = findTaggedWorkerFileNode(graph, scriptName);
|
|
@@ -10317,169 +10860,18 @@ function createCloudflareResolveTarget(config, graph) {
|
|
|
10317
10860
|
return {
|
|
10318
10861
|
targetNodeId: resolveRouteGrain(fileNode.service, taggedFileId),
|
|
10319
10862
|
serviceName: fileNode.service,
|
|
10320
|
-
edgeType:
|
|
10863
|
+
edgeType: import_types42.EdgeType.CALLS
|
|
10321
10864
|
};
|
|
10322
10865
|
}
|
|
10323
10866
|
return {
|
|
10324
|
-
targetNodeId: (0,
|
|
10867
|
+
targetNodeId: (0, import_types42.infraId)("cloudflare-worker", scriptName),
|
|
10325
10868
|
serviceName: scriptName,
|
|
10326
|
-
edgeType:
|
|
10869
|
+
edgeType: import_types42.EdgeType.CALLS,
|
|
10327
10870
|
ensureInfraNode: { kind: "cloudflare-worker", name: scriptName, provider: "cloudflare" }
|
|
10328
10871
|
};
|
|
10329
10872
|
};
|
|
10330
10873
|
}
|
|
10331
10874
|
|
|
10332
|
-
// src/connectors-config.ts
|
|
10333
|
-
init_cjs_shims();
|
|
10334
|
-
var import_node_os4 = __toESM(require("os"), 1);
|
|
10335
|
-
var import_node_path45 = __toESM(require("path"), 1);
|
|
10336
|
-
var import_node_fs26 = require("fs");
|
|
10337
|
-
var CONNECTORS_CONFIG_VERSION = 1;
|
|
10338
|
-
var EnvRefUnsetError = class extends Error {
|
|
10339
|
-
ref;
|
|
10340
|
-
varName;
|
|
10341
|
-
constructor(ref, varName) {
|
|
10342
|
-
super(`${ref} is unset`);
|
|
10343
|
-
this.name = "EnvRefUnsetError";
|
|
10344
|
-
this.ref = ref;
|
|
10345
|
-
this.varName = varName;
|
|
10346
|
-
}
|
|
10347
|
-
};
|
|
10348
|
-
function neatHome2() {
|
|
10349
|
-
const override = process.env.NEAT_HOME;
|
|
10350
|
-
if (override && override.length > 0) return import_node_path45.default.resolve(override);
|
|
10351
|
-
return import_node_path45.default.join(import_node_os4.default.homedir(), ".neat");
|
|
10352
|
-
}
|
|
10353
|
-
function connectorsConfigPath(home = neatHome2()) {
|
|
10354
|
-
return import_node_path45.default.join(home, "connectors.json");
|
|
10355
|
-
}
|
|
10356
|
-
var MODE_MASK_LOOSER_THAN_0600 = 63;
|
|
10357
|
-
async function warnIfModeLooserThan0600(file) {
|
|
10358
|
-
if (process.platform === "win32") return;
|
|
10359
|
-
try {
|
|
10360
|
-
const stat = await import_node_fs26.promises.stat(file);
|
|
10361
|
-
if ((stat.mode & MODE_MASK_LOOSER_THAN_0600) !== 0) {
|
|
10362
|
-
const mode = (stat.mode & 511).toString(8).padStart(3, "0");
|
|
10363
|
-
console.warn(
|
|
10364
|
-
`[neat] ${file} is mode 0${mode}, looser than the 0600 this file's secrets call for \u2014 run \`chmod 600 ${file}\``
|
|
10365
|
-
);
|
|
10366
|
-
}
|
|
10367
|
-
} catch {
|
|
10368
|
-
}
|
|
10369
|
-
}
|
|
10370
|
-
async function readConnectorsConfig(home = neatHome2()) {
|
|
10371
|
-
const file = connectorsConfigPath(home);
|
|
10372
|
-
let raw;
|
|
10373
|
-
try {
|
|
10374
|
-
raw = await import_node_fs26.promises.readFile(file, "utf8");
|
|
10375
|
-
} catch (err) {
|
|
10376
|
-
if (err.code === "ENOENT") {
|
|
10377
|
-
return { version: CONNECTORS_CONFIG_VERSION, connectors: [] };
|
|
10378
|
-
}
|
|
10379
|
-
throw err;
|
|
10380
|
-
}
|
|
10381
|
-
await warnIfModeLooserThan0600(file);
|
|
10382
|
-
let parsed;
|
|
10383
|
-
try {
|
|
10384
|
-
parsed = JSON.parse(raw);
|
|
10385
|
-
} catch (err) {
|
|
10386
|
-
throw new Error(`${file} is not valid JSON: ${err.message}`);
|
|
10387
|
-
}
|
|
10388
|
-
return validateConfig(parsed, file);
|
|
10389
|
-
}
|
|
10390
|
-
function validateConfig(parsed, file) {
|
|
10391
|
-
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
10392
|
-
throw new Error(`${file} must be a JSON object with a "connectors" array`);
|
|
10393
|
-
}
|
|
10394
|
-
const obj = parsed;
|
|
10395
|
-
const version = obj.version === void 0 ? CONNECTORS_CONFIG_VERSION : obj.version;
|
|
10396
|
-
if (typeof version !== "number" || !Number.isInteger(version)) {
|
|
10397
|
-
throw new Error(`${file}: "version" must be an integer`);
|
|
10398
|
-
}
|
|
10399
|
-
const rawConnectors = obj.connectors;
|
|
10400
|
-
if (!Array.isArray(rawConnectors)) {
|
|
10401
|
-
throw new Error(`${file}: "connectors" must be an array`);
|
|
10402
|
-
}
|
|
10403
|
-
const connectors = rawConnectors.map((entry2, i) => validateEntry(entry2, i, file));
|
|
10404
|
-
return { version, connectors };
|
|
10405
|
-
}
|
|
10406
|
-
function validateEntry(entry2, index, file) {
|
|
10407
|
-
const where = `${file}: connectors[${index}]`;
|
|
10408
|
-
if (typeof entry2 !== "object" || entry2 === null || Array.isArray(entry2)) {
|
|
10409
|
-
throw new Error(`${where} must be an object`);
|
|
10410
|
-
}
|
|
10411
|
-
const e = entry2;
|
|
10412
|
-
const id = e.id;
|
|
10413
|
-
if (typeof id !== "string" || id.length === 0) {
|
|
10414
|
-
throw new Error(`${where}.id must be a non-empty string`);
|
|
10415
|
-
}
|
|
10416
|
-
const provider = e.provider;
|
|
10417
|
-
if (typeof provider !== "string" || provider.length === 0) {
|
|
10418
|
-
throw new Error(`${where}.provider must be a non-empty string`);
|
|
10419
|
-
}
|
|
10420
|
-
if (e.project !== void 0 && typeof e.project !== "string") {
|
|
10421
|
-
throw new Error(`${where}.project must be a string when present`);
|
|
10422
|
-
}
|
|
10423
|
-
const credential = validateCredentialRef(e.credential, `${where}.credential`);
|
|
10424
|
-
let options;
|
|
10425
|
-
if (e.options !== void 0) {
|
|
10426
|
-
if (typeof e.options !== "object" || e.options === null || Array.isArray(e.options)) {
|
|
10427
|
-
throw new Error(`${where}.options must be an object when present`);
|
|
10428
|
-
}
|
|
10429
|
-
options = e.options;
|
|
10430
|
-
}
|
|
10431
|
-
return {
|
|
10432
|
-
id,
|
|
10433
|
-
provider,
|
|
10434
|
-
...typeof e.project === "string" ? { project: e.project } : {},
|
|
10435
|
-
credential,
|
|
10436
|
-
...options ? { options } : {}
|
|
10437
|
-
};
|
|
10438
|
-
}
|
|
10439
|
-
function validateCredentialRef(raw, where) {
|
|
10440
|
-
if (typeof raw === "string") {
|
|
10441
|
-
if (raw.length === 0) throw new Error(`${where} must be a non-empty string`);
|
|
10442
|
-
return raw;
|
|
10443
|
-
}
|
|
10444
|
-
if (typeof raw === "object" && raw !== null && !Array.isArray(raw)) {
|
|
10445
|
-
const fields = raw;
|
|
10446
|
-
const keys = Object.keys(fields);
|
|
10447
|
-
if (keys.length === 0) throw new Error(`${where} object must have at least one field`);
|
|
10448
|
-
for (const key of keys) {
|
|
10449
|
-
const v = fields[key];
|
|
10450
|
-
if (typeof v !== "string" || v.length === 0) {
|
|
10451
|
-
throw new Error(`${where}.${key} must be a non-empty string`);
|
|
10452
|
-
}
|
|
10453
|
-
}
|
|
10454
|
-
return fields;
|
|
10455
|
-
}
|
|
10456
|
-
throw new Error(`${where} must be a string or an object of strings`);
|
|
10457
|
-
}
|
|
10458
|
-
function resolveCredential(ref, env = process.env) {
|
|
10459
|
-
if (typeof ref === "string") {
|
|
10460
|
-
return { kind: "single", value: resolveRef(ref, env) };
|
|
10461
|
-
}
|
|
10462
|
-
const fields = {};
|
|
10463
|
-
for (const [key, value] of Object.entries(ref)) {
|
|
10464
|
-
fields[key] = resolveRef(value, env);
|
|
10465
|
-
}
|
|
10466
|
-
return { kind: "fields", fields };
|
|
10467
|
-
}
|
|
10468
|
-
function resolveRef(value, env) {
|
|
10469
|
-
if (value.length > 1 && value.startsWith("$")) {
|
|
10470
|
-
const varName = value.slice(1);
|
|
10471
|
-
const resolved = env[varName];
|
|
10472
|
-
if (resolved === void 0 || resolved.length === 0) {
|
|
10473
|
-
throw new EnvRefUnsetError(value, varName);
|
|
10474
|
-
}
|
|
10475
|
-
return resolved;
|
|
10476
|
-
}
|
|
10477
|
-
return value;
|
|
10478
|
-
}
|
|
10479
|
-
function connectorMatchesProject(entry2, project) {
|
|
10480
|
-
return entry2.project === void 0 || entry2.project === project;
|
|
10481
|
-
}
|
|
10482
|
-
|
|
10483
10875
|
// src/connectors/registry.ts
|
|
10484
10876
|
var CLOUDFLARE_API_BASE_URL = "https://api.cloudflare.com/client/v4";
|
|
10485
10877
|
async function authProbe(input) {
|
|
@@ -10665,6 +11057,9 @@ function buildRegistration(entry2, graph, env = process.env) {
|
|
|
10665
11057
|
return {
|
|
10666
11058
|
ok: true,
|
|
10667
11059
|
registration: {
|
|
11060
|
+
// Carry the entry id so the daemon can key this connector's poll-status
|
|
11061
|
+
// records to it (ADR-136).
|
|
11062
|
+
id: entry2.id,
|
|
10668
11063
|
connector: built.connector,
|
|
10669
11064
|
credentials,
|
|
10670
11065
|
resolveTarget: built.resolveTarget,
|
|
@@ -10699,8 +11094,8 @@ init_auth();
|
|
|
10699
11094
|
|
|
10700
11095
|
// src/unrouted.ts
|
|
10701
11096
|
init_cjs_shims();
|
|
10702
|
-
var
|
|
10703
|
-
var
|
|
11097
|
+
var import_node_fs30 = require("fs");
|
|
11098
|
+
var import_node_path49 = __toESM(require("path"), 1);
|
|
10704
11099
|
function buildUnroutedSpanRecord(serviceName, traceId, now = /* @__PURE__ */ new Date()) {
|
|
10705
11100
|
return {
|
|
10706
11101
|
timestamp: now.toISOString(),
|
|
@@ -10710,34 +11105,34 @@ function buildUnroutedSpanRecord(serviceName, traceId, now = /* @__PURE__ */ new
|
|
|
10710
11105
|
};
|
|
10711
11106
|
}
|
|
10712
11107
|
async function appendUnroutedSpan(neatHome4, record) {
|
|
10713
|
-
const target =
|
|
10714
|
-
await
|
|
10715
|
-
await
|
|
11108
|
+
const target = import_node_path49.default.join(neatHome4, "errors.ndjson");
|
|
11109
|
+
await import_node_fs30.promises.mkdir(neatHome4, { recursive: true });
|
|
11110
|
+
await import_node_fs30.promises.appendFile(target, JSON.stringify(record) + "\n", "utf8");
|
|
10716
11111
|
}
|
|
10717
11112
|
function unroutedErrorsPath(neatHome4) {
|
|
10718
|
-
return
|
|
11113
|
+
return import_node_path49.default.join(neatHome4, "errors.ndjson");
|
|
10719
11114
|
}
|
|
10720
11115
|
|
|
10721
11116
|
// src/daemon.ts
|
|
10722
|
-
var
|
|
11117
|
+
var import_types45 = require("@neat.is/types");
|
|
10723
11118
|
function daemonJsonPath(scanPath) {
|
|
10724
|
-
return
|
|
11119
|
+
return import_node_path50.default.join(scanPath, "neat-out", "daemon.json");
|
|
10725
11120
|
}
|
|
10726
11121
|
function daemonsDiscoveryDir(home) {
|
|
10727
11122
|
const base = home && home.length > 0 ? home : neatHomeFromEnv();
|
|
10728
|
-
return
|
|
11123
|
+
return import_node_path50.default.join(base, "daemons");
|
|
10729
11124
|
}
|
|
10730
11125
|
function daemonDiscoveryPath(project, home) {
|
|
10731
|
-
return
|
|
11126
|
+
return import_node_path50.default.join(daemonsDiscoveryDir(home), `${sanitizeDiscoveryName(project)}.json`);
|
|
10732
11127
|
}
|
|
10733
11128
|
function sanitizeDiscoveryName(project) {
|
|
10734
11129
|
return project.replace(/[^A-Za-z0-9._-]/g, "_");
|
|
10735
11130
|
}
|
|
10736
11131
|
function neatHomeFromEnv() {
|
|
10737
11132
|
const env = process.env.NEAT_HOME;
|
|
10738
|
-
if (env && env.length > 0) return
|
|
11133
|
+
if (env && env.length > 0) return import_node_path50.default.resolve(env);
|
|
10739
11134
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
10740
|
-
return
|
|
11135
|
+
return import_node_path50.default.join(home, ".neat");
|
|
10741
11136
|
}
|
|
10742
11137
|
function resolveNeatVersion() {
|
|
10743
11138
|
if (process.env.NEAT_LOCAL_VERSION && process.env.NEAT_LOCAL_VERSION.length > 0) {
|
|
@@ -10769,7 +11164,7 @@ async function clearDaemonRecord(record, home) {
|
|
|
10769
11164
|
} catch {
|
|
10770
11165
|
}
|
|
10771
11166
|
try {
|
|
10772
|
-
await
|
|
11167
|
+
await import_node_fs31.promises.unlink(daemonDiscoveryPath(record.project, home));
|
|
10773
11168
|
} catch {
|
|
10774
11169
|
}
|
|
10775
11170
|
}
|
|
@@ -10778,12 +11173,12 @@ function reconcileDaemonRecordSync(record, home) {
|
|
|
10778
11173
|
const stopped = { ...record, status: "stopped" };
|
|
10779
11174
|
const target = daemonJsonPath(record.projectPath);
|
|
10780
11175
|
const tmp = `${target}.${process.pid}.tmp`;
|
|
10781
|
-
(0,
|
|
10782
|
-
(0,
|
|
11176
|
+
(0, import_node_fs31.writeFileSync)(tmp, JSON.stringify(stopped, null, 2) + "\n");
|
|
11177
|
+
(0, import_node_fs31.renameSync)(tmp, target);
|
|
10783
11178
|
} catch {
|
|
10784
11179
|
}
|
|
10785
11180
|
try {
|
|
10786
|
-
(0,
|
|
11181
|
+
(0, import_node_fs31.unlinkSync)(daemonDiscoveryPath(record.project, home));
|
|
10787
11182
|
} catch {
|
|
10788
11183
|
}
|
|
10789
11184
|
}
|
|
@@ -10806,11 +11201,11 @@ function teardownSlot(slot) {
|
|
|
10806
11201
|
}
|
|
10807
11202
|
}
|
|
10808
11203
|
function neatHomeFor(opts) {
|
|
10809
|
-
if (opts.neatHome && opts.neatHome.length > 0) return
|
|
11204
|
+
if (opts.neatHome && opts.neatHome.length > 0) return import_node_path50.default.resolve(opts.neatHome);
|
|
10810
11205
|
const env = process.env.NEAT_HOME;
|
|
10811
|
-
if (env && env.length > 0) return
|
|
11206
|
+
if (env && env.length > 0) return import_node_path50.default.resolve(env);
|
|
10812
11207
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
10813
|
-
return
|
|
11208
|
+
return import_node_path50.default.join(home, ".neat");
|
|
10814
11209
|
}
|
|
10815
11210
|
function routeSpanToProject(serviceName, projects) {
|
|
10816
11211
|
if (!serviceName) return DEFAULT_PROJECT;
|
|
@@ -10854,13 +11249,13 @@ function spanBelongsToSingleProject(graph, project, serviceName) {
|
|
|
10854
11249
|
if (!serviceName) return true;
|
|
10855
11250
|
if (serviceNameMatchesProject(serviceName, project)) return true;
|
|
10856
11251
|
return graph.someNode(
|
|
10857
|
-
(_id, attrs) => attrs.type ===
|
|
11252
|
+
(_id, attrs) => attrs.type === import_types45.NodeType.ServiceNode && attrs.name === serviceName
|
|
10858
11253
|
);
|
|
10859
11254
|
}
|
|
10860
11255
|
async function bootstrapProject(entry2, connectors = [], neatHome4) {
|
|
10861
|
-
const paths = pathsForProject(entry2.name,
|
|
11256
|
+
const paths = pathsForProject(entry2.name, import_node_path50.default.join(entry2.path, "neat-out"));
|
|
10862
11257
|
try {
|
|
10863
|
-
const stat = await
|
|
11258
|
+
const stat = await import_node_fs31.promises.stat(entry2.path);
|
|
10864
11259
|
if (!stat.isDirectory()) {
|
|
10865
11260
|
throw new Error(`registered path ${entry2.path} is not a directory`);
|
|
10866
11261
|
}
|
|
@@ -10913,7 +11308,11 @@ async function bootstrapProject(entry2, connectors = [], neatHome4) {
|
|
|
10913
11308
|
{ projectDir: entry2.path, credentials: registration.credentials },
|
|
10914
11309
|
graph,
|
|
10915
11310
|
registration.resolveTarget,
|
|
10916
|
-
|
|
11311
|
+
// `connectorId` is threaded through so every tick lands in the
|
|
11312
|
+
// in-process status tracker the connector-status endpoint reads
|
|
11313
|
+
// (ADR-136). Undefined for a programmatic registration, which records
|
|
11314
|
+
// nothing.
|
|
11315
|
+
{ intervalMs: registration.intervalMs, connectorId: registration.id }
|
|
10917
11316
|
)
|
|
10918
11317
|
);
|
|
10919
11318
|
const stopConnectors = () => {
|
|
@@ -10985,7 +11384,7 @@ async function startDaemon(opts = {}) {
|
|
|
10985
11384
|
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;
|
|
10986
11385
|
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;
|
|
10987
11386
|
const singleProject = projectArg;
|
|
10988
|
-
const singleProjectPath = singleProject && projectPathArg ?
|
|
11387
|
+
const singleProjectPath = singleProject && projectPathArg ? import_node_path50.default.resolve(projectPathArg) : null;
|
|
10989
11388
|
if (singleProject && !singleProjectPath) {
|
|
10990
11389
|
throw new Error(
|
|
10991
11390
|
`neatd: project "${singleProject}" given without a projectPath; pass NEAT_PROJECT_PATH alongside NEAT_PROJECT.`
|
|
@@ -10993,14 +11392,14 @@ async function startDaemon(opts = {}) {
|
|
|
10993
11392
|
}
|
|
10994
11393
|
if (!singleProject) {
|
|
10995
11394
|
try {
|
|
10996
|
-
await
|
|
11395
|
+
await import_node_fs31.promises.access(regPath);
|
|
10997
11396
|
} catch {
|
|
10998
11397
|
throw new Error(
|
|
10999
11398
|
`neatd: registry not found at ${regPath}. Run \`neat init <path>\` to register a project before starting the daemon.`
|
|
11000
11399
|
);
|
|
11001
11400
|
}
|
|
11002
11401
|
}
|
|
11003
|
-
const pidPath =
|
|
11402
|
+
const pidPath = import_node_path50.default.join(home, "neatd.pid");
|
|
11004
11403
|
await writeAtomically(pidPath, `${process.pid}
|
|
11005
11404
|
`);
|
|
11006
11405
|
const slots = /* @__PURE__ */ new Map();
|
|
@@ -11182,7 +11581,12 @@ async function startDaemon(opts = {}) {
|
|
|
11182
11581
|
// only this project (the dashboard pins to it), and the daemon-wide
|
|
11183
11582
|
// `/health` carries it at the top level for the spawn-reuse identity
|
|
11184
11583
|
// check. Absent for the legacy multi-project daemon.
|
|
11185
|
-
singleProject: singleProject && singleProjectPath ? { name: singleProject, path: singleProjectPath } : void 0
|
|
11584
|
+
singleProject: singleProject && singleProjectPath ? { name: singleProject, path: singleProjectPath } : void 0,
|
|
11585
|
+
// ADR-136 — the connector-status endpoint reads ~/.neat/connectors.json
|
|
11586
|
+
// through the same resolved home the slot bootstrap read it from, so a
|
|
11587
|
+
// daemon given an explicit NEAT_HOME serves status for the same file it
|
|
11588
|
+
// polls.
|
|
11589
|
+
connectorsHome: home
|
|
11186
11590
|
});
|
|
11187
11591
|
restAddress = await restApp.listen({ port: restPort, host });
|
|
11188
11592
|
console.log(
|
|
@@ -11194,7 +11598,7 @@ async function startDaemon(opts = {}) {
|
|
|
11194
11598
|
}
|
|
11195
11599
|
if (restApp) await restApp.close().catch(() => {
|
|
11196
11600
|
});
|
|
11197
|
-
await
|
|
11601
|
+
await import_node_fs31.promises.unlink(pidPath).catch(() => {
|
|
11198
11602
|
});
|
|
11199
11603
|
throw new Error(
|
|
11200
11604
|
`neatd: failed to bind REST on port ${restPort} \u2014 ${err.message}`
|
|
@@ -11320,7 +11724,7 @@ async function startDaemon(opts = {}) {
|
|
|
11320
11724
|
});
|
|
11321
11725
|
if (otlpApp) await otlpApp.close().catch(() => {
|
|
11322
11726
|
});
|
|
11323
|
-
await
|
|
11727
|
+
await import_node_fs31.promises.unlink(pidPath).catch(() => {
|
|
11324
11728
|
});
|
|
11325
11729
|
throw new Error(
|
|
11326
11730
|
`neatd: failed to bind OTLP on port ${otlpPort} \u2014 ${err.message}`
|
|
@@ -11355,7 +11759,7 @@ async function startDaemon(opts = {}) {
|
|
|
11355
11759
|
});
|
|
11356
11760
|
if (otlpApp) await otlpApp.close().catch(() => {
|
|
11357
11761
|
});
|
|
11358
|
-
await
|
|
11762
|
+
await import_node_fs31.promises.unlink(pidPath).catch(() => {
|
|
11359
11763
|
});
|
|
11360
11764
|
throw new Error(
|
|
11361
11765
|
`neatd: failed to write daemon.json for "${singleProject}" \u2014 ${err.message}`
|
|
@@ -11402,9 +11806,9 @@ async function startDaemon(opts = {}) {
|
|
|
11402
11806
|
let registryWatcher = null;
|
|
11403
11807
|
let reloadTimer = null;
|
|
11404
11808
|
if (!singleProject) try {
|
|
11405
|
-
const regDir =
|
|
11406
|
-
const regBase =
|
|
11407
|
-
registryWatcher = (0,
|
|
11809
|
+
const regDir = import_node_path50.default.dirname(regPath);
|
|
11810
|
+
const regBase = import_node_path50.default.basename(regPath);
|
|
11811
|
+
registryWatcher = (0, import_node_fs31.watch)(regDir, (_eventType, filename) => {
|
|
11408
11812
|
if (filename !== null && filename !== regBase) return;
|
|
11409
11813
|
if (reloadTimer) clearTimeout(reloadTimer);
|
|
11410
11814
|
reloadTimer = setTimeout(() => {
|
|
@@ -11453,7 +11857,7 @@ async function startDaemon(opts = {}) {
|
|
|
11453
11857
|
if (daemonRecord) {
|
|
11454
11858
|
await clearDaemonRecord(daemonRecord, home);
|
|
11455
11859
|
}
|
|
11456
|
-
await
|
|
11860
|
+
await import_node_fs31.promises.unlink(pidPath).catch(() => {
|
|
11457
11861
|
});
|
|
11458
11862
|
};
|
|
11459
11863
|
return {
|
|
@@ -11476,9 +11880,9 @@ init_auth();
|
|
|
11476
11880
|
// src/web-spawn.ts
|
|
11477
11881
|
init_cjs_shims();
|
|
11478
11882
|
var import_node_child_process2 = require("child_process");
|
|
11479
|
-
var
|
|
11883
|
+
var import_node_fs32 = require("fs");
|
|
11480
11884
|
var import_node_net = __toESM(require("net"), 1);
|
|
11481
|
-
var
|
|
11885
|
+
var import_node_path51 = __toESM(require("path"), 1);
|
|
11482
11886
|
var DEFAULT_WEB_PORT = 6328;
|
|
11483
11887
|
var DEFAULT_REST_PORT = 8080;
|
|
11484
11888
|
function asValidPort(value) {
|
|
@@ -11487,11 +11891,11 @@ function asValidPort(value) {
|
|
|
11487
11891
|
}
|
|
11488
11892
|
function projectRoot() {
|
|
11489
11893
|
const fromEnv = process.env.NEAT_SCAN_PATH;
|
|
11490
|
-
return
|
|
11894
|
+
return import_node_path51.default.resolve(fromEnv && fromEnv.length > 0 ? fromEnv : process.cwd());
|
|
11491
11895
|
}
|
|
11492
11896
|
async function readDaemonPorts(root) {
|
|
11493
11897
|
try {
|
|
11494
|
-
const raw = await
|
|
11898
|
+
const raw = await import_node_fs32.promises.readFile(import_node_path51.default.join(root, "neat-out", "daemon.json"), "utf8");
|
|
11495
11899
|
const parsed = JSON.parse(raw);
|
|
11496
11900
|
const ports = parsed?.ports ?? {};
|
|
11497
11901
|
return { web: asValidPort(ports.web), rest: asValidPort(ports.rest) };
|
|
@@ -11536,10 +11940,10 @@ function resolveWebPackageDir() {
|
|
|
11536
11940
|
eval("require")
|
|
11537
11941
|
);
|
|
11538
11942
|
const pkgJsonPath = req.resolve("@neat.is/web/package.json");
|
|
11539
|
-
return
|
|
11943
|
+
return import_node_path51.default.dirname(pkgJsonPath);
|
|
11540
11944
|
}
|
|
11541
11945
|
function resolveStandaloneServerEntry(webDir) {
|
|
11542
|
-
return
|
|
11946
|
+
return import_node_path51.default.join(webDir, ".next/standalone/packages/web/server.js");
|
|
11543
11947
|
}
|
|
11544
11948
|
async function pickInternalPort() {
|
|
11545
11949
|
return new Promise((resolve, reject) => {
|
|
@@ -11592,7 +11996,7 @@ async function spawnWebUI(restPort, opts = {}) {
|
|
|
11592
11996
|
NEAT_API_URL: apiUrl
|
|
11593
11997
|
};
|
|
11594
11998
|
child = (0, import_node_child_process2.spawn)(process.execPath, [serverEntry], {
|
|
11595
|
-
cwd:
|
|
11999
|
+
cwd: import_node_path51.default.dirname(serverEntry),
|
|
11596
12000
|
env,
|
|
11597
12001
|
stdio: ["ignore", "inherit", "inherit"],
|
|
11598
12002
|
detached: false
|
|
@@ -11768,14 +12172,14 @@ function localVersion() {
|
|
|
11768
12172
|
}
|
|
11769
12173
|
function neatHome3() {
|
|
11770
12174
|
if (process.env.NEAT_HOME && process.env.NEAT_HOME.length > 0) {
|
|
11771
|
-
return
|
|
12175
|
+
return import_node_path52.default.resolve(process.env.NEAT_HOME);
|
|
11772
12176
|
}
|
|
11773
12177
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
11774
|
-
return
|
|
12178
|
+
return import_node_path52.default.join(home, ".neat");
|
|
11775
12179
|
}
|
|
11776
12180
|
async function readPid() {
|
|
11777
12181
|
try {
|
|
11778
|
-
const raw = await
|
|
12182
|
+
const raw = await import_node_fs33.promises.readFile(import_node_path52.default.join(neatHome3(), "neatd.pid"), "utf8");
|
|
11779
12183
|
const n = Number.parseInt(raw.trim(), 10);
|
|
11780
12184
|
return Number.isFinite(n) ? n : null;
|
|
11781
12185
|
} catch {
|