@neat.is/core 0.4.28-dev.20260712 → 0.4.28-dev.20260714
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-5T6J3WKC.js → chunk-6HPRD53A.js} +734 -144
- package/dist/chunk-6HPRD53A.js.map +1 -0
- package/dist/{chunk-UJ6WBLIE.js → chunk-AOLXZWRU.js} +163 -345
- package/dist/chunk-AOLXZWRU.js.map +1 -0
- package/dist/cli.cjs +1177 -814
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +9 -9
- package/dist/index.cjs +787 -381
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -2
- package/dist/neatd.cjs +852 -446
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +2 -2
- package/dist/server.cjs +510 -92
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +1 -1
- package/package.json +2 -2
- package/dist/chunk-5T6J3WKC.js.map +0 -1
- package/dist/chunk-UJ6WBLIE.js.map +0 -1
package/dist/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",
|
|
@@ -8911,9 +9373,11 @@ async function buildApi(opts) {
|
|
|
8911
9373
|
trustProxy,
|
|
8912
9374
|
publicRead
|
|
8913
9375
|
});
|
|
9376
|
+
const requiresAuth = authToken !== void 0 && authToken.length > 0 && trustProxy !== true;
|
|
8914
9377
|
app.get("/api/config", async () => ({
|
|
8915
9378
|
publicRead: publicRead === true,
|
|
8916
|
-
authProxy: trustProxy === true
|
|
9379
|
+
authProxy: trustProxy === true,
|
|
9380
|
+
requiresAuth
|
|
8917
9381
|
}));
|
|
8918
9382
|
const startedAt = opts.startedAt ?? Date.now();
|
|
8919
9383
|
const registry = buildLegacyRegistry(opts);
|
|
@@ -8943,7 +9407,8 @@ async function buildApi(opts) {
|
|
|
8943
9407
|
staleEventsPathFor,
|
|
8944
9408
|
policyFilePathFor,
|
|
8945
9409
|
bootstrap: opts.bootstrap,
|
|
8946
|
-
singleProject: opts.singleProject?.name
|
|
9410
|
+
singleProject: opts.singleProject?.name,
|
|
9411
|
+
connectorsHome: opts.connectorsHome
|
|
8947
9412
|
};
|
|
8948
9413
|
app.get("/health", async () => {
|
|
8949
9414
|
const uptimeMs = Date.now() - startedAt;
|
|
@@ -9085,16 +9550,31 @@ function startConnectorPollLoop(connector, ctx, graph, resolveTarget, options =
|
|
|
9085
9550
|
let stopped = false;
|
|
9086
9551
|
let since = ctx.since;
|
|
9087
9552
|
const intervalMs = options.intervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
9553
|
+
const connectorId = options.connectorId;
|
|
9088
9554
|
const onError = options.onError ?? ((err) => console.error(`[neatd] connector poll failed (${connector.provider})`, err));
|
|
9089
9555
|
const tick = () => {
|
|
9090
9556
|
if (stopped) return;
|
|
9091
9557
|
void (async () => {
|
|
9092
9558
|
const tickStartedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
9093
9559
|
try {
|
|
9094
|
-
await runConnectorPoll(connector, { ...ctx, since }, graph, resolveTarget);
|
|
9560
|
+
const result = await runConnectorPoll(connector, { ...ctx, since }, graph, resolveTarget);
|
|
9095
9561
|
since = tickStartedAt;
|
|
9562
|
+
if (connectorId) {
|
|
9563
|
+
recordConnectorPoll(connectorId, {
|
|
9564
|
+
outcome: "ok",
|
|
9565
|
+
at: tickStartedAt,
|
|
9566
|
+
signalsLastPoll: result.signalCount
|
|
9567
|
+
});
|
|
9568
|
+
}
|
|
9096
9569
|
} catch (err) {
|
|
9097
9570
|
onError(err);
|
|
9571
|
+
if (connectorId) {
|
|
9572
|
+
recordConnectorPoll(connectorId, {
|
|
9573
|
+
outcome: "error",
|
|
9574
|
+
at: tickStartedAt,
|
|
9575
|
+
error: sanitizePollError(err)
|
|
9576
|
+
});
|
|
9577
|
+
}
|
|
9098
9578
|
}
|
|
9099
9579
|
})();
|
|
9100
9580
|
};
|
|
@@ -9393,6 +9873,30 @@ function buildEdgeLogsQuery(limit) {
|
|
|
9393
9873
|
`limit ${safeLimit}`
|
|
9394
9874
|
].join("\n");
|
|
9395
9875
|
}
|
|
9876
|
+
function logsAllHttpFailureMessage(status2) {
|
|
9877
|
+
if (status2 === 401 || status2 === 403) {
|
|
9878
|
+
return `supabase connector: logs.all request rejected (HTTP ${status2}). Check the Management API token, its analytics read scope, and --api-project-ref.`;
|
|
9879
|
+
}
|
|
9880
|
+
if (status2 === 404) {
|
|
9881
|
+
return "supabase connector: logs.all project not found (HTTP 404). Check --api-project-ref.";
|
|
9882
|
+
}
|
|
9883
|
+
if (status2 === 429) {
|
|
9884
|
+
return "supabase connector: logs.all rate-limited (HTTP 429). The connector will retry on the next poll; reduce poll cadence if this persists.";
|
|
9885
|
+
}
|
|
9886
|
+
if (status2 === 400) {
|
|
9887
|
+
return "supabase connector: logs.all query rejected (HTTP 400). Provider details redacted; confirm the live log-query dialect before shipping this connector.";
|
|
9888
|
+
}
|
|
9889
|
+
return `supabase connector: logs.all request failed (HTTP ${status2}); provider details redacted.`;
|
|
9890
|
+
}
|
|
9891
|
+
function providerErrorDetails(error) {
|
|
9892
|
+
if (!error || typeof error === "string") return "";
|
|
9893
|
+
const parts = [];
|
|
9894
|
+
if (typeof error.code === "number") parts.push(`code ${error.code}`);
|
|
9895
|
+
if (typeof error.status === "string" && /^[A-Z0-9_.-]+$/.test(error.status)) {
|
|
9896
|
+
parts.push(`status ${error.status}`);
|
|
9897
|
+
}
|
|
9898
|
+
return parts.length > 0 ? ` (${parts.join(", ")})` : "";
|
|
9899
|
+
}
|
|
9396
9900
|
async function fetchSupabaseEdgeLogs(config, token, startIso, endIso, fetchImpl = fetch) {
|
|
9397
9901
|
const baseUrl = config.managementApiUrl ?? DEFAULT_SUPABASE_MANAGEMENT_API_URL;
|
|
9398
9902
|
const url = new URL(`${baseUrl}/v1/projects/${config.apiProjectRef}/analytics/endpoints/logs.all`);
|
|
@@ -9407,12 +9911,18 @@ async function fetchSupabaseEdgeLogs(config, token, startIso, endIso, fetchImpl
|
|
|
9407
9911
|
{ provider: "supabase", accountKey: config.apiProjectRef, fetchImpl }
|
|
9408
9912
|
);
|
|
9409
9913
|
if (!res.ok) {
|
|
9410
|
-
throw new Error(
|
|
9914
|
+
throw new Error(logsAllHttpFailureMessage(res.status));
|
|
9915
|
+
}
|
|
9916
|
+
let body;
|
|
9917
|
+
try {
|
|
9918
|
+
body = await res.json();
|
|
9919
|
+
} catch {
|
|
9920
|
+
throw new Error("supabase connector: logs.all returned invalid JSON; provider details redacted.");
|
|
9411
9921
|
}
|
|
9412
|
-
const body = await res.json();
|
|
9413
9922
|
if (body.error) {
|
|
9414
|
-
|
|
9415
|
-
|
|
9923
|
+
throw new Error(
|
|
9924
|
+
`supabase connector: logs.all returned a provider error${providerErrorDetails(body.error)}; provider message redacted.`
|
|
9925
|
+
);
|
|
9416
9926
|
}
|
|
9417
9927
|
return body.result ?? [];
|
|
9418
9928
|
}
|
|
@@ -9444,10 +9954,10 @@ var SUPABASE_RPC_TARGET_KIND = "supabase-rpc";
|
|
|
9444
9954
|
// src/connectors/supabase/map.ts
|
|
9445
9955
|
var REST_RPC_PATH_RE = /^\/rest\/v1\/rpc\/([^/?]+)/;
|
|
9446
9956
|
var REST_TABLE_PATH_RE = /^\/rest\/v1\/([^/?]+)/;
|
|
9447
|
-
function targetFromRestPath(
|
|
9448
|
-
const rpcMatch = REST_RPC_PATH_RE.exec(
|
|
9957
|
+
function targetFromRestPath(path53) {
|
|
9958
|
+
const rpcMatch = REST_RPC_PATH_RE.exec(path53);
|
|
9449
9959
|
if (rpcMatch) return { targetKind: SUPABASE_RPC_TARGET_KIND, name: rpcMatch[1] };
|
|
9450
|
-
const tableMatch = REST_TABLE_PATH_RE.exec(
|
|
9960
|
+
const tableMatch = REST_TABLE_PATH_RE.exec(path53);
|
|
9451
9961
|
if (tableMatch) return { targetKind: SUPABASE_TABLE_TARGET_KIND, name: tableMatch[1] };
|
|
9452
9962
|
return null;
|
|
9453
9963
|
}
|
|
@@ -9550,19 +10060,19 @@ async function fetchPgStatStatements(connectionString, limit = DEFAULT_STATEMENT
|
|
|
9550
10060
|
|
|
9551
10061
|
// src/connectors/supabase/resolve.ts
|
|
9552
10062
|
init_cjs_shims();
|
|
9553
|
-
var
|
|
10063
|
+
var import_types35 = require("@neat.is/types");
|
|
9554
10064
|
function createSupabaseResolveTarget(graph, config) {
|
|
9555
10065
|
return (signal, _ctx) => {
|
|
9556
10066
|
if (signal.targetKind !== SUPABASE_TABLE_TARGET_KIND && signal.targetKind !== SUPABASE_RPC_TARGET_KIND) {
|
|
9557
10067
|
return null;
|
|
9558
10068
|
}
|
|
9559
|
-
const subResourceId = (0,
|
|
10069
|
+
const subResourceId = (0, import_types35.infraId)(signal.targetKind, `${config.nodeRef}/${signal.targetName}`);
|
|
9560
10070
|
if (graph.hasNode(subResourceId)) {
|
|
9561
|
-
return { targetNodeId: subResourceId, serviceName: config.serviceName, edgeType:
|
|
10071
|
+
return { targetNodeId: subResourceId, serviceName: config.serviceName, edgeType: import_types35.EdgeType.CALLS };
|
|
9562
10072
|
}
|
|
9563
|
-
const projectLevelId = (0,
|
|
10073
|
+
const projectLevelId = (0, import_types35.infraId)("supabase", config.nodeRef);
|
|
9564
10074
|
if (graph.hasNode(projectLevelId)) {
|
|
9565
|
-
return { targetNodeId: projectLevelId, serviceName: config.serviceName, edgeType:
|
|
10075
|
+
return { targetNodeId: projectLevelId, serviceName: config.serviceName, edgeType: import_types35.EdgeType.CALLS };
|
|
9566
10076
|
}
|
|
9567
10077
|
return null;
|
|
9568
10078
|
};
|
|
@@ -9570,6 +10080,35 @@ function createSupabaseResolveTarget(graph, config) {
|
|
|
9570
10080
|
|
|
9571
10081
|
// src/connectors/supabase/index.ts
|
|
9572
10082
|
var DEFAULT_MAX_LOOKBACK_MS = 24 * 60 * 60 * 1e3;
|
|
10083
|
+
function errorCode(err) {
|
|
10084
|
+
const code = err?.code;
|
|
10085
|
+
return typeof code === "string" && code.length > 0 ? code : void 0;
|
|
10086
|
+
}
|
|
10087
|
+
function describeSupabasePostgresSurfaceFailure(projectRef, err) {
|
|
10088
|
+
const code = errorCode(err);
|
|
10089
|
+
let reason;
|
|
10090
|
+
switch (code) {
|
|
10091
|
+
case "42501":
|
|
10092
|
+
reason = "permission denied; grant pg_read_all_stats to the configured Postgres role";
|
|
10093
|
+
break;
|
|
10094
|
+
case "42P01":
|
|
10095
|
+
case "42704":
|
|
10096
|
+
reason = "pg_stat_statements is not enabled or visible to the configured Postgres role";
|
|
10097
|
+
break;
|
|
10098
|
+
case "28P01":
|
|
10099
|
+
case "28000":
|
|
10100
|
+
reason = "Postgres credential rejected";
|
|
10101
|
+
break;
|
|
10102
|
+
case "3D000":
|
|
10103
|
+
reason = "database not found";
|
|
10104
|
+
break;
|
|
10105
|
+
default: {
|
|
10106
|
+
const name = err instanceof Error && err.name ? err.name : "Error";
|
|
10107
|
+
reason = code ? `${name} ${code}` : name;
|
|
10108
|
+
}
|
|
10109
|
+
}
|
|
10110
|
+
return `supabase connector: pg_stat_statements surface unavailable for project ${projectRef} (${reason}); continuing with Management API log surface.`;
|
|
10111
|
+
}
|
|
9573
10112
|
var SupabaseConnector = class {
|
|
9574
10113
|
// `deps.fetchPgStatStatements` defaults to the real Postgres-backed
|
|
9575
10114
|
// implementation; tests override it to exercise the "both surfaces
|
|
@@ -9601,12 +10140,18 @@ var SupabaseConnector = class {
|
|
|
9601
10140
|
const signals = mapEdgeLogRowsToSignals(logRows);
|
|
9602
10141
|
if (creds.postgresConnectionString) {
|
|
9603
10142
|
const fetchStatements = this.deps.fetchPgStatStatements ?? fetchPgStatStatements;
|
|
9604
|
-
|
|
9605
|
-
|
|
9606
|
-
|
|
9607
|
-
|
|
9608
|
-
|
|
9609
|
-
|
|
10143
|
+
try {
|
|
10144
|
+
const statementRows = await fetchStatements(
|
|
10145
|
+
creds.postgresConnectionString,
|
|
10146
|
+
this.config.statementLimit ?? DEFAULT_STATEMENT_LIMIT,
|
|
10147
|
+
this.config.apiProjectRef
|
|
10148
|
+
);
|
|
10149
|
+
signals.push(...diffPgStatStatementsToSignals(statementRows, this.statementBaselines, now.toISOString()));
|
|
10150
|
+
} catch (err) {
|
|
10151
|
+
const summary = describeSupabasePostgresSurfaceFailure(this.config.apiProjectRef, err);
|
|
10152
|
+
if (this.deps.onPostgresSurfaceError) this.deps.onPostgresSurfaceError(err, summary);
|
|
10153
|
+
else console.warn(summary);
|
|
10154
|
+
}
|
|
9610
10155
|
}
|
|
9611
10156
|
return signals;
|
|
9612
10157
|
}
|
|
@@ -9620,7 +10165,7 @@ function createSupabaseConnector(graph, config, deps = {}) {
|
|
|
9620
10165
|
|
|
9621
10166
|
// src/connectors/railway/index.ts
|
|
9622
10167
|
init_cjs_shims();
|
|
9623
|
-
var
|
|
10168
|
+
var import_types39 = require("@neat.is/types");
|
|
9624
10169
|
|
|
9625
10170
|
// src/connectors/railway/client.ts
|
|
9626
10171
|
init_cjs_shims();
|
|
@@ -9765,7 +10310,7 @@ function buildRailwayRouteIndex(graph, serviceName) {
|
|
|
9765
10310
|
const out = [];
|
|
9766
10311
|
graph.forEachNode((_id, attrs) => {
|
|
9767
10312
|
const node = attrs;
|
|
9768
|
-
if (node.type !==
|
|
10313
|
+
if (node.type !== import_types39.NodeType.RouteNode) return;
|
|
9769
10314
|
const route = attrs;
|
|
9770
10315
|
if (route.service !== serviceName) return;
|
|
9771
10316
|
out.push({
|
|
@@ -9861,12 +10406,12 @@ function createRailwayResolveTarget(config) {
|
|
|
9861
10406
|
const serviceName = config.serviceNameById[config.serviceId];
|
|
9862
10407
|
if (!serviceName) return null;
|
|
9863
10408
|
if (signal.targetKind === ROUTE_TARGET_KIND) {
|
|
9864
|
-
return { targetNodeId: signal.targetName, serviceName, edgeType:
|
|
10409
|
+
return { targetNodeId: signal.targetName, serviceName, edgeType: import_types39.EdgeType.CALLS };
|
|
9865
10410
|
}
|
|
9866
10411
|
if (signal.targetKind === PEER_SERVICE_TARGET_KIND) {
|
|
9867
10412
|
const peerName = config.serviceNameById[signal.targetName];
|
|
9868
10413
|
if (!peerName) return null;
|
|
9869
|
-
return { targetNodeId: (0,
|
|
10414
|
+
return { targetNodeId: (0, import_types39.serviceId)(peerName), serviceName, edgeType: import_types39.EdgeType.CONNECTS_TO };
|
|
9870
10415
|
}
|
|
9871
10416
|
return null;
|
|
9872
10417
|
};
|
|
@@ -9981,9 +10526,9 @@ function parseFirebaseTargetName(targetName) {
|
|
|
9981
10526
|
const secondSep = rest.indexOf(FIELD_SEP);
|
|
9982
10527
|
if (secondSep === -1) return null;
|
|
9983
10528
|
const method = rest.slice(0, secondSep);
|
|
9984
|
-
const
|
|
9985
|
-
if (!resourceName || !method || !
|
|
9986
|
-
return { resourceName, method, path:
|
|
10529
|
+
const path53 = rest.slice(secondSep + 1);
|
|
10530
|
+
if (!resourceName || !method || !path53) return null;
|
|
10531
|
+
return { resourceName, method, path: path53 };
|
|
9987
10532
|
}
|
|
9988
10533
|
function resourceNameFor(type, labels) {
|
|
9989
10534
|
if (!labels) return null;
|
|
@@ -10020,14 +10565,14 @@ function mapLogEntryToSignal(entry2) {
|
|
|
10020
10565
|
if (!req2) return null;
|
|
10021
10566
|
if (!req2.requestMethod) return null;
|
|
10022
10567
|
const method = req2.requestMethod.toUpperCase();
|
|
10023
|
-
const
|
|
10024
|
-
if (
|
|
10568
|
+
const path53 = pathFromRequestUrl(req2.requestUrl);
|
|
10569
|
+
if (path53 === null) return null;
|
|
10025
10570
|
const timestamp = entry2.timestamp;
|
|
10026
10571
|
if (!timestamp) return null;
|
|
10027
10572
|
const isError = typeof req2.status === "number" && req2.status >= ERROR_STATUS_THRESHOLD2;
|
|
10028
10573
|
return {
|
|
10029
10574
|
targetKind: resourceType,
|
|
10030
|
-
targetName: packFirebaseTargetName({ resourceName, method, path:
|
|
10575
|
+
targetName: packFirebaseTargetName({ resourceName, method, path: path53 }),
|
|
10031
10576
|
callCount: 1,
|
|
10032
10577
|
errorCount: isError ? 1 : 0,
|
|
10033
10578
|
lastObservedIso: timestamp
|
|
@@ -10044,7 +10589,7 @@ function mapLogEntriesToSignals(entries) {
|
|
|
10044
10589
|
|
|
10045
10590
|
// src/connectors/firebase/resolve.ts
|
|
10046
10591
|
init_cjs_shims();
|
|
10047
|
-
var
|
|
10592
|
+
var import_types40 = require("@neat.is/types");
|
|
10048
10593
|
function neatServiceNameFor(resourceType, resourceName, serviceMap) {
|
|
10049
10594
|
switch (resourceType) {
|
|
10050
10595
|
case "cloud_function":
|
|
@@ -10059,7 +10604,7 @@ function routeEntriesFor(graph, serviceName) {
|
|
|
10059
10604
|
const entries = [];
|
|
10060
10605
|
graph.forEachNode((_id, attrs) => {
|
|
10061
10606
|
const node = attrs;
|
|
10062
|
-
if (node.type !==
|
|
10607
|
+
if (node.type !== import_types40.NodeType.RouteNode) return;
|
|
10063
10608
|
const route = attrs;
|
|
10064
10609
|
if (route.service !== serviceName) return;
|
|
10065
10610
|
entries.push({
|
|
@@ -10091,7 +10636,7 @@ function createFirebaseResolveTarget(graph, serviceMap) {
|
|
|
10091
10636
|
return {
|
|
10092
10637
|
targetNodeId: match.routeNodeId,
|
|
10093
10638
|
serviceName,
|
|
10094
|
-
edgeType:
|
|
10639
|
+
edgeType: import_types40.EdgeType.CALLS
|
|
10095
10640
|
};
|
|
10096
10641
|
};
|
|
10097
10642
|
}
|
|
@@ -10118,7 +10663,7 @@ init_cjs_shims();
|
|
|
10118
10663
|
|
|
10119
10664
|
// src/connectors/cloudflare/connector.ts
|
|
10120
10665
|
init_cjs_shims();
|
|
10121
|
-
var
|
|
10666
|
+
var import_types42 = require("@neat.is/types");
|
|
10122
10667
|
|
|
10123
10668
|
// src/connectors/cloudflare/client.ts
|
|
10124
10669
|
init_cjs_shims();
|
|
@@ -10225,7 +10770,7 @@ function mapEventToSignal(event) {
|
|
|
10225
10770
|
if (typeof timestampMs !== "number" || !Number.isFinite(timestampMs)) return null;
|
|
10226
10771
|
const statusCode = metadata?.statusCode;
|
|
10227
10772
|
const isError = typeof statusCode === "number" && statusCode >= ERROR_STATUS_THRESHOLD3;
|
|
10228
|
-
const
|
|
10773
|
+
const path53 = metadata?.trigger ? parsePathFromTrigger(metadata.trigger) : void 0;
|
|
10229
10774
|
return {
|
|
10230
10775
|
targetKind: CLOUDFLARE_TARGET_KIND,
|
|
10231
10776
|
targetName: scriptName,
|
|
@@ -10233,7 +10778,7 @@ function mapEventToSignal(event) {
|
|
|
10233
10778
|
errorCount: isError ? 1 : 0,
|
|
10234
10779
|
lastObservedIso: new Date(timestampMs).toISOString(),
|
|
10235
10780
|
method,
|
|
10236
|
-
...
|
|
10781
|
+
...path53 ? { path: path53 } : {},
|
|
10237
10782
|
...typeof statusCode === "number" ? { statusCode } : {},
|
|
10238
10783
|
...typeof metadata?.duration === "number" ? { duration: metadata.duration } : {}
|
|
10239
10784
|
};
|
|
@@ -10273,19 +10818,19 @@ function findTaggedWorkerFileNode(graph, workerName) {
|
|
|
10273
10818
|
graph.forEachNode((id, attrs) => {
|
|
10274
10819
|
if (found) return;
|
|
10275
10820
|
const a = attrs;
|
|
10276
|
-
if (a.type ===
|
|
10821
|
+
if (a.type === import_types42.NodeType.FileNode && a.platform === "cloudflare" && a.platformName === workerName) {
|
|
10277
10822
|
found = id;
|
|
10278
10823
|
}
|
|
10279
10824
|
});
|
|
10280
10825
|
return found;
|
|
10281
10826
|
}
|
|
10282
|
-
function findMatchingRouteNode(graph, serviceName, method,
|
|
10283
|
-
const normalizedPath = normalizePathTemplate(
|
|
10827
|
+
function findMatchingRouteNode(graph, serviceName, method, path53) {
|
|
10828
|
+
const normalizedPath = normalizePathTemplate(path53);
|
|
10284
10829
|
let found = null;
|
|
10285
10830
|
graph.forEachNode((id, attrs) => {
|
|
10286
10831
|
if (found) return;
|
|
10287
10832
|
const a = attrs;
|
|
10288
|
-
if (a.type !==
|
|
10833
|
+
if (a.type !== import_types42.NodeType.RouteNode || a.service !== serviceName) return;
|
|
10289
10834
|
if (!a.pathTemplate || normalizePathTemplate(a.pathTemplate) !== normalizedPath) return;
|
|
10290
10835
|
const routeMethod = (a.method ?? "").toUpperCase();
|
|
10291
10836
|
if (routeMethod !== "ALL" && routeMethod !== method) return;
|
|
@@ -10297,18 +10842,18 @@ function createCloudflareResolveTarget(config, graph) {
|
|
|
10297
10842
|
return (signal) => {
|
|
10298
10843
|
if (signal.targetKind !== CLOUDFLARE_TARGET_KIND) return null;
|
|
10299
10844
|
const scriptName = signal.targetName;
|
|
10300
|
-
const { method, path:
|
|
10845
|
+
const { method, path: path53 } = signal;
|
|
10301
10846
|
const resolveRouteGrain = (serviceName, wholeFileId) => {
|
|
10302
|
-
if (!method || !
|
|
10303
|
-
return findMatchingRouteNode(graph, serviceName, method,
|
|
10847
|
+
if (!method || !path53) return wholeFileId;
|
|
10848
|
+
return findMatchingRouteNode(graph, serviceName, method, path53) ?? wholeFileId;
|
|
10304
10849
|
};
|
|
10305
10850
|
const mapping = config.workers?.[scriptName];
|
|
10306
10851
|
if (mapping) {
|
|
10307
|
-
const wholeFileId = (0,
|
|
10852
|
+
const wholeFileId = (0, import_types42.fileId)(mapping.service, mapping.entryFile);
|
|
10308
10853
|
return {
|
|
10309
10854
|
targetNodeId: resolveRouteGrain(mapping.service, wholeFileId),
|
|
10310
10855
|
serviceName: mapping.service,
|
|
10311
|
-
edgeType:
|
|
10856
|
+
edgeType: import_types42.EdgeType.CALLS
|
|
10312
10857
|
};
|
|
10313
10858
|
}
|
|
10314
10859
|
const taggedFileId = findTaggedWorkerFileNode(graph, scriptName);
|
|
@@ -10317,169 +10862,18 @@ function createCloudflareResolveTarget(config, graph) {
|
|
|
10317
10862
|
return {
|
|
10318
10863
|
targetNodeId: resolveRouteGrain(fileNode.service, taggedFileId),
|
|
10319
10864
|
serviceName: fileNode.service,
|
|
10320
|
-
edgeType:
|
|
10865
|
+
edgeType: import_types42.EdgeType.CALLS
|
|
10321
10866
|
};
|
|
10322
10867
|
}
|
|
10323
10868
|
return {
|
|
10324
|
-
targetNodeId: (0,
|
|
10869
|
+
targetNodeId: (0, import_types42.infraId)("cloudflare-worker", scriptName),
|
|
10325
10870
|
serviceName: scriptName,
|
|
10326
|
-
edgeType:
|
|
10871
|
+
edgeType: import_types42.EdgeType.CALLS,
|
|
10327
10872
|
ensureInfraNode: { kind: "cloudflare-worker", name: scriptName, provider: "cloudflare" }
|
|
10328
10873
|
};
|
|
10329
10874
|
};
|
|
10330
10875
|
}
|
|
10331
10876
|
|
|
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
10877
|
// src/connectors/registry.ts
|
|
10484
10878
|
var CLOUDFLARE_API_BASE_URL = "https://api.cloudflare.com/client/v4";
|
|
10485
10879
|
async function authProbe(input) {
|
|
@@ -10665,6 +11059,9 @@ function buildRegistration(entry2, graph, env = process.env) {
|
|
|
10665
11059
|
return {
|
|
10666
11060
|
ok: true,
|
|
10667
11061
|
registration: {
|
|
11062
|
+
// Carry the entry id so the daemon can key this connector's poll-status
|
|
11063
|
+
// records to it (ADR-136).
|
|
11064
|
+
id: entry2.id,
|
|
10668
11065
|
connector: built.connector,
|
|
10669
11066
|
credentials,
|
|
10670
11067
|
resolveTarget: built.resolveTarget,
|
|
@@ -10699,8 +11096,8 @@ init_auth();
|
|
|
10699
11096
|
|
|
10700
11097
|
// src/unrouted.ts
|
|
10701
11098
|
init_cjs_shims();
|
|
10702
|
-
var
|
|
10703
|
-
var
|
|
11099
|
+
var import_node_fs30 = require("fs");
|
|
11100
|
+
var import_node_path49 = __toESM(require("path"), 1);
|
|
10704
11101
|
function buildUnroutedSpanRecord(serviceName, traceId, now = /* @__PURE__ */ new Date()) {
|
|
10705
11102
|
return {
|
|
10706
11103
|
timestamp: now.toISOString(),
|
|
@@ -10710,34 +11107,34 @@ function buildUnroutedSpanRecord(serviceName, traceId, now = /* @__PURE__ */ new
|
|
|
10710
11107
|
};
|
|
10711
11108
|
}
|
|
10712
11109
|
async function appendUnroutedSpan(neatHome4, record) {
|
|
10713
|
-
const target =
|
|
10714
|
-
await
|
|
10715
|
-
await
|
|
11110
|
+
const target = import_node_path49.default.join(neatHome4, "errors.ndjson");
|
|
11111
|
+
await import_node_fs30.promises.mkdir(neatHome4, { recursive: true });
|
|
11112
|
+
await import_node_fs30.promises.appendFile(target, JSON.stringify(record) + "\n", "utf8");
|
|
10716
11113
|
}
|
|
10717
11114
|
function unroutedErrorsPath(neatHome4) {
|
|
10718
|
-
return
|
|
11115
|
+
return import_node_path49.default.join(neatHome4, "errors.ndjson");
|
|
10719
11116
|
}
|
|
10720
11117
|
|
|
10721
11118
|
// src/daemon.ts
|
|
10722
|
-
var
|
|
11119
|
+
var import_types45 = require("@neat.is/types");
|
|
10723
11120
|
function daemonJsonPath(scanPath) {
|
|
10724
|
-
return
|
|
11121
|
+
return import_node_path50.default.join(scanPath, "neat-out", "daemon.json");
|
|
10725
11122
|
}
|
|
10726
11123
|
function daemonsDiscoveryDir(home) {
|
|
10727
11124
|
const base = home && home.length > 0 ? home : neatHomeFromEnv();
|
|
10728
|
-
return
|
|
11125
|
+
return import_node_path50.default.join(base, "daemons");
|
|
10729
11126
|
}
|
|
10730
11127
|
function daemonDiscoveryPath(project, home) {
|
|
10731
|
-
return
|
|
11128
|
+
return import_node_path50.default.join(daemonsDiscoveryDir(home), `${sanitizeDiscoveryName(project)}.json`);
|
|
10732
11129
|
}
|
|
10733
11130
|
function sanitizeDiscoveryName(project) {
|
|
10734
11131
|
return project.replace(/[^A-Za-z0-9._-]/g, "_");
|
|
10735
11132
|
}
|
|
10736
11133
|
function neatHomeFromEnv() {
|
|
10737
11134
|
const env = process.env.NEAT_HOME;
|
|
10738
|
-
if (env && env.length > 0) return
|
|
11135
|
+
if (env && env.length > 0) return import_node_path50.default.resolve(env);
|
|
10739
11136
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
10740
|
-
return
|
|
11137
|
+
return import_node_path50.default.join(home, ".neat");
|
|
10741
11138
|
}
|
|
10742
11139
|
function resolveNeatVersion() {
|
|
10743
11140
|
if (process.env.NEAT_LOCAL_VERSION && process.env.NEAT_LOCAL_VERSION.length > 0) {
|
|
@@ -10769,7 +11166,7 @@ async function clearDaemonRecord(record, home) {
|
|
|
10769
11166
|
} catch {
|
|
10770
11167
|
}
|
|
10771
11168
|
try {
|
|
10772
|
-
await
|
|
11169
|
+
await import_node_fs31.promises.unlink(daemonDiscoveryPath(record.project, home));
|
|
10773
11170
|
} catch {
|
|
10774
11171
|
}
|
|
10775
11172
|
}
|
|
@@ -10778,12 +11175,12 @@ function reconcileDaemonRecordSync(record, home) {
|
|
|
10778
11175
|
const stopped = { ...record, status: "stopped" };
|
|
10779
11176
|
const target = daemonJsonPath(record.projectPath);
|
|
10780
11177
|
const tmp = `${target}.${process.pid}.tmp`;
|
|
10781
|
-
(0,
|
|
10782
|
-
(0,
|
|
11178
|
+
(0, import_node_fs31.writeFileSync)(tmp, JSON.stringify(stopped, null, 2) + "\n");
|
|
11179
|
+
(0, import_node_fs31.renameSync)(tmp, target);
|
|
10783
11180
|
} catch {
|
|
10784
11181
|
}
|
|
10785
11182
|
try {
|
|
10786
|
-
(0,
|
|
11183
|
+
(0, import_node_fs31.unlinkSync)(daemonDiscoveryPath(record.project, home));
|
|
10787
11184
|
} catch {
|
|
10788
11185
|
}
|
|
10789
11186
|
}
|
|
@@ -10806,11 +11203,11 @@ function teardownSlot(slot) {
|
|
|
10806
11203
|
}
|
|
10807
11204
|
}
|
|
10808
11205
|
function neatHomeFor(opts) {
|
|
10809
|
-
if (opts.neatHome && opts.neatHome.length > 0) return
|
|
11206
|
+
if (opts.neatHome && opts.neatHome.length > 0) return import_node_path50.default.resolve(opts.neatHome);
|
|
10810
11207
|
const env = process.env.NEAT_HOME;
|
|
10811
|
-
if (env && env.length > 0) return
|
|
11208
|
+
if (env && env.length > 0) return import_node_path50.default.resolve(env);
|
|
10812
11209
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
10813
|
-
return
|
|
11210
|
+
return import_node_path50.default.join(home, ".neat");
|
|
10814
11211
|
}
|
|
10815
11212
|
function routeSpanToProject(serviceName, projects) {
|
|
10816
11213
|
if (!serviceName) return DEFAULT_PROJECT;
|
|
@@ -10854,13 +11251,13 @@ function spanBelongsToSingleProject(graph, project, serviceName) {
|
|
|
10854
11251
|
if (!serviceName) return true;
|
|
10855
11252
|
if (serviceNameMatchesProject(serviceName, project)) return true;
|
|
10856
11253
|
return graph.someNode(
|
|
10857
|
-
(_id, attrs) => attrs.type ===
|
|
11254
|
+
(_id, attrs) => attrs.type === import_types45.NodeType.ServiceNode && attrs.name === serviceName
|
|
10858
11255
|
);
|
|
10859
11256
|
}
|
|
10860
11257
|
async function bootstrapProject(entry2, connectors = [], neatHome4) {
|
|
10861
|
-
const paths = pathsForProject(entry2.name,
|
|
11258
|
+
const paths = pathsForProject(entry2.name, import_node_path50.default.join(entry2.path, "neat-out"));
|
|
10862
11259
|
try {
|
|
10863
|
-
const stat = await
|
|
11260
|
+
const stat = await import_node_fs31.promises.stat(entry2.path);
|
|
10864
11261
|
if (!stat.isDirectory()) {
|
|
10865
11262
|
throw new Error(`registered path ${entry2.path} is not a directory`);
|
|
10866
11263
|
}
|
|
@@ -10913,7 +11310,11 @@ async function bootstrapProject(entry2, connectors = [], neatHome4) {
|
|
|
10913
11310
|
{ projectDir: entry2.path, credentials: registration.credentials },
|
|
10914
11311
|
graph,
|
|
10915
11312
|
registration.resolveTarget,
|
|
10916
|
-
|
|
11313
|
+
// `connectorId` is threaded through so every tick lands in the
|
|
11314
|
+
// in-process status tracker the connector-status endpoint reads
|
|
11315
|
+
// (ADR-136). Undefined for a programmatic registration, which records
|
|
11316
|
+
// nothing.
|
|
11317
|
+
{ intervalMs: registration.intervalMs, connectorId: registration.id }
|
|
10917
11318
|
)
|
|
10918
11319
|
);
|
|
10919
11320
|
const stopConnectors = () => {
|
|
@@ -10985,7 +11386,7 @@ async function startDaemon(opts = {}) {
|
|
|
10985
11386
|
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
11387
|
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
11388
|
const singleProject = projectArg;
|
|
10988
|
-
const singleProjectPath = singleProject && projectPathArg ?
|
|
11389
|
+
const singleProjectPath = singleProject && projectPathArg ? import_node_path50.default.resolve(projectPathArg) : null;
|
|
10989
11390
|
if (singleProject && !singleProjectPath) {
|
|
10990
11391
|
throw new Error(
|
|
10991
11392
|
`neatd: project "${singleProject}" given without a projectPath; pass NEAT_PROJECT_PATH alongside NEAT_PROJECT.`
|
|
@@ -10993,14 +11394,14 @@ async function startDaemon(opts = {}) {
|
|
|
10993
11394
|
}
|
|
10994
11395
|
if (!singleProject) {
|
|
10995
11396
|
try {
|
|
10996
|
-
await
|
|
11397
|
+
await import_node_fs31.promises.access(regPath);
|
|
10997
11398
|
} catch {
|
|
10998
11399
|
throw new Error(
|
|
10999
11400
|
`neatd: registry not found at ${regPath}. Run \`neat init <path>\` to register a project before starting the daemon.`
|
|
11000
11401
|
);
|
|
11001
11402
|
}
|
|
11002
11403
|
}
|
|
11003
|
-
const pidPath =
|
|
11404
|
+
const pidPath = import_node_path50.default.join(home, "neatd.pid");
|
|
11004
11405
|
await writeAtomically(pidPath, `${process.pid}
|
|
11005
11406
|
`);
|
|
11006
11407
|
const slots = /* @__PURE__ */ new Map();
|
|
@@ -11182,7 +11583,12 @@ async function startDaemon(opts = {}) {
|
|
|
11182
11583
|
// only this project (the dashboard pins to it), and the daemon-wide
|
|
11183
11584
|
// `/health` carries it at the top level for the spawn-reuse identity
|
|
11184
11585
|
// check. Absent for the legacy multi-project daemon.
|
|
11185
|
-
singleProject: singleProject && singleProjectPath ? { name: singleProject, path: singleProjectPath } : void 0
|
|
11586
|
+
singleProject: singleProject && singleProjectPath ? { name: singleProject, path: singleProjectPath } : void 0,
|
|
11587
|
+
// ADR-136 — the connector-status endpoint reads ~/.neat/connectors.json
|
|
11588
|
+
// through the same resolved home the slot bootstrap read it from, so a
|
|
11589
|
+
// daemon given an explicit NEAT_HOME serves status for the same file it
|
|
11590
|
+
// polls.
|
|
11591
|
+
connectorsHome: home
|
|
11186
11592
|
});
|
|
11187
11593
|
restAddress = await restApp.listen({ port: restPort, host });
|
|
11188
11594
|
console.log(
|
|
@@ -11194,7 +11600,7 @@ async function startDaemon(opts = {}) {
|
|
|
11194
11600
|
}
|
|
11195
11601
|
if (restApp) await restApp.close().catch(() => {
|
|
11196
11602
|
});
|
|
11197
|
-
await
|
|
11603
|
+
await import_node_fs31.promises.unlink(pidPath).catch(() => {
|
|
11198
11604
|
});
|
|
11199
11605
|
throw new Error(
|
|
11200
11606
|
`neatd: failed to bind REST on port ${restPort} \u2014 ${err.message}`
|
|
@@ -11320,7 +11726,7 @@ async function startDaemon(opts = {}) {
|
|
|
11320
11726
|
});
|
|
11321
11727
|
if (otlpApp) await otlpApp.close().catch(() => {
|
|
11322
11728
|
});
|
|
11323
|
-
await
|
|
11729
|
+
await import_node_fs31.promises.unlink(pidPath).catch(() => {
|
|
11324
11730
|
});
|
|
11325
11731
|
throw new Error(
|
|
11326
11732
|
`neatd: failed to bind OTLP on port ${otlpPort} \u2014 ${err.message}`
|
|
@@ -11355,7 +11761,7 @@ async function startDaemon(opts = {}) {
|
|
|
11355
11761
|
});
|
|
11356
11762
|
if (otlpApp) await otlpApp.close().catch(() => {
|
|
11357
11763
|
});
|
|
11358
|
-
await
|
|
11764
|
+
await import_node_fs31.promises.unlink(pidPath).catch(() => {
|
|
11359
11765
|
});
|
|
11360
11766
|
throw new Error(
|
|
11361
11767
|
`neatd: failed to write daemon.json for "${singleProject}" \u2014 ${err.message}`
|
|
@@ -11402,9 +11808,9 @@ async function startDaemon(opts = {}) {
|
|
|
11402
11808
|
let registryWatcher = null;
|
|
11403
11809
|
let reloadTimer = null;
|
|
11404
11810
|
if (!singleProject) try {
|
|
11405
|
-
const regDir =
|
|
11406
|
-
const regBase =
|
|
11407
|
-
registryWatcher = (0,
|
|
11811
|
+
const regDir = import_node_path50.default.dirname(regPath);
|
|
11812
|
+
const regBase = import_node_path50.default.basename(regPath);
|
|
11813
|
+
registryWatcher = (0, import_node_fs31.watch)(regDir, (_eventType, filename) => {
|
|
11408
11814
|
if (filename !== null && filename !== regBase) return;
|
|
11409
11815
|
if (reloadTimer) clearTimeout(reloadTimer);
|
|
11410
11816
|
reloadTimer = setTimeout(() => {
|
|
@@ -11453,7 +11859,7 @@ async function startDaemon(opts = {}) {
|
|
|
11453
11859
|
if (daemonRecord) {
|
|
11454
11860
|
await clearDaemonRecord(daemonRecord, home);
|
|
11455
11861
|
}
|
|
11456
|
-
await
|
|
11862
|
+
await import_node_fs31.promises.unlink(pidPath).catch(() => {
|
|
11457
11863
|
});
|
|
11458
11864
|
};
|
|
11459
11865
|
return {
|
|
@@ -11476,9 +11882,9 @@ init_auth();
|
|
|
11476
11882
|
// src/web-spawn.ts
|
|
11477
11883
|
init_cjs_shims();
|
|
11478
11884
|
var import_node_child_process2 = require("child_process");
|
|
11479
|
-
var
|
|
11885
|
+
var import_node_fs32 = require("fs");
|
|
11480
11886
|
var import_node_net = __toESM(require("net"), 1);
|
|
11481
|
-
var
|
|
11887
|
+
var import_node_path51 = __toESM(require("path"), 1);
|
|
11482
11888
|
var DEFAULT_WEB_PORT = 6328;
|
|
11483
11889
|
var DEFAULT_REST_PORT = 8080;
|
|
11484
11890
|
function asValidPort(value) {
|
|
@@ -11487,11 +11893,11 @@ function asValidPort(value) {
|
|
|
11487
11893
|
}
|
|
11488
11894
|
function projectRoot() {
|
|
11489
11895
|
const fromEnv = process.env.NEAT_SCAN_PATH;
|
|
11490
|
-
return
|
|
11896
|
+
return import_node_path51.default.resolve(fromEnv && fromEnv.length > 0 ? fromEnv : process.cwd());
|
|
11491
11897
|
}
|
|
11492
11898
|
async function readDaemonPorts(root) {
|
|
11493
11899
|
try {
|
|
11494
|
-
const raw = await
|
|
11900
|
+
const raw = await import_node_fs32.promises.readFile(import_node_path51.default.join(root, "neat-out", "daemon.json"), "utf8");
|
|
11495
11901
|
const parsed = JSON.parse(raw);
|
|
11496
11902
|
const ports = parsed?.ports ?? {};
|
|
11497
11903
|
return { web: asValidPort(ports.web), rest: asValidPort(ports.rest) };
|
|
@@ -11536,10 +11942,10 @@ function resolveWebPackageDir() {
|
|
|
11536
11942
|
eval("require")
|
|
11537
11943
|
);
|
|
11538
11944
|
const pkgJsonPath = req.resolve("@neat.is/web/package.json");
|
|
11539
|
-
return
|
|
11945
|
+
return import_node_path51.default.dirname(pkgJsonPath);
|
|
11540
11946
|
}
|
|
11541
11947
|
function resolveStandaloneServerEntry(webDir) {
|
|
11542
|
-
return
|
|
11948
|
+
return import_node_path51.default.join(webDir, ".next/standalone/packages/web/server.js");
|
|
11543
11949
|
}
|
|
11544
11950
|
async function pickInternalPort() {
|
|
11545
11951
|
return new Promise((resolve, reject) => {
|
|
@@ -11592,7 +11998,7 @@ async function spawnWebUI(restPort, opts = {}) {
|
|
|
11592
11998
|
NEAT_API_URL: apiUrl
|
|
11593
11999
|
};
|
|
11594
12000
|
child = (0, import_node_child_process2.spawn)(process.execPath, [serverEntry], {
|
|
11595
|
-
cwd:
|
|
12001
|
+
cwd: import_node_path51.default.dirname(serverEntry),
|
|
11596
12002
|
env,
|
|
11597
12003
|
stdio: ["ignore", "inherit", "inherit"],
|
|
11598
12004
|
detached: false
|
|
@@ -11768,14 +12174,14 @@ function localVersion() {
|
|
|
11768
12174
|
}
|
|
11769
12175
|
function neatHome3() {
|
|
11770
12176
|
if (process.env.NEAT_HOME && process.env.NEAT_HOME.length > 0) {
|
|
11771
|
-
return
|
|
12177
|
+
return import_node_path52.default.resolve(process.env.NEAT_HOME);
|
|
11772
12178
|
}
|
|
11773
12179
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
11774
|
-
return
|
|
12180
|
+
return import_node_path52.default.join(home, ".neat");
|
|
11775
12181
|
}
|
|
11776
12182
|
async function readPid() {
|
|
11777
12183
|
try {
|
|
11778
|
-
const raw = await
|
|
12184
|
+
const raw = await import_node_fs33.promises.readFile(import_node_path52.default.join(neatHome3(), "neatd.pid"), "utf8");
|
|
11779
12185
|
const n = Number.parseInt(raw.trim(), 10);
|
|
11780
12186
|
return Number.isFinite(n) ? n : null;
|
|
11781
12187
|
} catch {
|