@neat.is/core 0.4.28-dev.20260712 → 0.4.28-dev.20260713
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-5T6J3WKC.js → chunk-N7HEYLOF.js} +731 -143
- package/dist/chunk-N7HEYLOF.js.map +1 -0
- package/dist/{chunk-UJ6WBLIE.js → chunk-V2HUBS5J.js} +163 -345
- package/dist/chunk-V2HUBS5J.js.map +1 -0
- package/dist/cli.cjs +1174 -813
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +9 -9
- package/dist/index.cjs +784 -380
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -2
- package/dist/neatd.cjs +849 -445
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +2 -2
- package/dist/server.cjs +507 -91
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +1 -1
- package/package.json +2 -2
- package/dist/chunk-5T6J3WKC.js.map +0 -1
- package/dist/chunk-UJ6WBLIE.js.map +0 -1
package/dist/cli.cjs
CHANGED
|
@@ -61,8 +61,8 @@ function mountBearerAuth(app, opts) {
|
|
|
61
61
|
]);
|
|
62
62
|
const publicRead = opts.publicRead === true;
|
|
63
63
|
app.addHook("preHandler", (req, reply, done) => {
|
|
64
|
-
const
|
|
65
|
-
if (exactUnauthPaths.has(
|
|
64
|
+
const path61 = (req.url.split("?")[0] ?? "").replace(/\/+$/, "");
|
|
65
|
+
if (exactUnauthPaths.has(path61) || PROJECT_SCOPED_UNAUTH_PATTERN.test(path61)) {
|
|
66
66
|
done();
|
|
67
67
|
return;
|
|
68
68
|
}
|
|
@@ -194,8 +194,8 @@ function reshapeGrpcRequest(req) {
|
|
|
194
194
|
};
|
|
195
195
|
}
|
|
196
196
|
function resolveProtoRoot() {
|
|
197
|
-
const here =
|
|
198
|
-
return
|
|
197
|
+
const here = import_node_path49.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
|
|
198
|
+
return import_node_path49.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_url2,
|
|
266
|
+
var import_node_url2, import_node_path49, 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_url2 = require("url");
|
|
272
|
-
|
|
272
|
+
import_node_path49 = __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 path61 = q === -1 ? v : v.slice(0, q);
|
|
366
|
+
if (path61.length > 0) return path61;
|
|
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_path50.default.dirname((0, import_node_url3.fileURLToPath)(importMetaUrl));
|
|
421
|
+
const protoRoot = import_node_path50.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_path50.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_path50, import_node_url3, 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_path50 = __toESM(require("path"), 1);
|
|
657
657
|
import_node_url3 = require("url");
|
|
658
658
|
import_fastify2 = __toESM(require("fastify"), 1);
|
|
659
659
|
import_protobufjs = __toESM(require("protobufjs"), 1);
|
|
@@ -690,8 +690,8 @@ __export(cli_exports, {
|
|
|
690
690
|
});
|
|
691
691
|
module.exports = __toCommonJS(cli_exports);
|
|
692
692
|
init_cjs_shims();
|
|
693
|
-
var
|
|
694
|
-
var
|
|
693
|
+
var import_node_path60 = __toESM(require("path"), 1);
|
|
694
|
+
var import_node_fs40 = require("fs");
|
|
695
695
|
|
|
696
696
|
// src/banner.ts
|
|
697
697
|
init_cjs_shims();
|
|
@@ -1256,19 +1256,19 @@ function confidenceFromMix(edges, now = Date.now()) {
|
|
|
1256
1256
|
function longestIncomingWalk(graph, start, maxDepth) {
|
|
1257
1257
|
let best = { path: [start], edges: [] };
|
|
1258
1258
|
const visited = /* @__PURE__ */ new Set([start]);
|
|
1259
|
-
function step(node,
|
|
1260
|
-
if (
|
|
1261
|
-
best = { path: [...
|
|
1259
|
+
function step(node, path61, edges) {
|
|
1260
|
+
if (path61.length > best.path.length) {
|
|
1261
|
+
best = { path: [...path61], edges: [...edges] };
|
|
1262
1262
|
}
|
|
1263
|
-
if (
|
|
1263
|
+
if (path61.length - 1 >= maxDepth) return;
|
|
1264
1264
|
const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
|
|
1265
1265
|
for (const [srcId, edge] of incoming) {
|
|
1266
1266
|
if (visited.has(srcId)) continue;
|
|
1267
1267
|
visited.add(srcId);
|
|
1268
|
-
|
|
1268
|
+
path61.push(srcId);
|
|
1269
1269
|
edges.push(edge);
|
|
1270
|
-
step(srcId,
|
|
1271
|
-
|
|
1270
|
+
step(srcId, path61, edges);
|
|
1271
|
+
path61.pop();
|
|
1272
1272
|
edges.pop();
|
|
1273
1273
|
visited.delete(srcId);
|
|
1274
1274
|
}
|
|
@@ -1462,26 +1462,26 @@ function dominantFailingCall(graph, serviceId5, visited) {
|
|
|
1462
1462
|
return best;
|
|
1463
1463
|
}
|
|
1464
1464
|
function followFailingCallChain(graph, originServiceId, maxDepth) {
|
|
1465
|
-
const
|
|
1465
|
+
const path61 = [originServiceId];
|
|
1466
1466
|
const edges = [];
|
|
1467
1467
|
const visited = /* @__PURE__ */ new Set([originServiceId]);
|
|
1468
1468
|
let current = originServiceId;
|
|
1469
1469
|
for (let depth = 0; depth < maxDepth; depth++) {
|
|
1470
1470
|
const hop = dominantFailingCall(graph, current, visited);
|
|
1471
1471
|
if (!hop) break;
|
|
1472
|
-
|
|
1472
|
+
path61.push(hop.nextService);
|
|
1473
1473
|
edges.push(hop.edge);
|
|
1474
1474
|
visited.add(hop.nextService);
|
|
1475
1475
|
current = hop.nextService;
|
|
1476
1476
|
}
|
|
1477
1477
|
if (edges.length === 0) return null;
|
|
1478
|
-
return { path:
|
|
1478
|
+
return { path: path61, edges, culprit: current };
|
|
1479
1479
|
}
|
|
1480
1480
|
function crossServiceRootCause(graph, originId, incidents, errorEvent) {
|
|
1481
1481
|
const chain = followFailingCallChain(graph, originId, ROOT_CAUSE_MAX_DEPTH);
|
|
1482
1482
|
if (!chain) return null;
|
|
1483
1483
|
const culprit = chain.culprit;
|
|
1484
|
-
const
|
|
1484
|
+
const path61 = [...chain.path];
|
|
1485
1485
|
const edgeProvenances = chain.edges.map((e) => e.provenance);
|
|
1486
1486
|
const baseConfidence = confidenceFromMix(chain.edges);
|
|
1487
1487
|
const confidence = Math.max(0, Math.min(1, baseConfidence * INCIDENT_ROOT_CAUSE_CONFIDENCE));
|
|
@@ -1489,14 +1489,14 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
|
|
|
1489
1489
|
if (loc) {
|
|
1490
1490
|
let rootCauseNode = culprit;
|
|
1491
1491
|
if (loc.fileNode) {
|
|
1492
|
-
|
|
1492
|
+
path61.push(loc.fileNode);
|
|
1493
1493
|
edgeProvenances.push(import_types.Provenance.OBSERVED);
|
|
1494
1494
|
rootCauseNode = loc.fileNode;
|
|
1495
1495
|
}
|
|
1496
1496
|
return import_types.RootCauseResultSchema.parse({
|
|
1497
1497
|
rootCauseNode,
|
|
1498
1498
|
rootCauseReason: loc.rootCauseReason,
|
|
1499
|
-
traversalPath:
|
|
1499
|
+
traversalPath: path61,
|
|
1500
1500
|
edgeProvenances,
|
|
1501
1501
|
confidence,
|
|
1502
1502
|
...loc.fixRecommendation ? { fixRecommendation: loc.fixRecommendation } : {}
|
|
@@ -1508,7 +1508,7 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
|
|
|
1508
1508
|
return import_types.RootCauseResultSchema.parse({
|
|
1509
1509
|
rootCauseNode: culprit,
|
|
1510
1510
|
rootCauseReason: `${culpritName} is failing downstream calls (${errs} observed error${errs === 1 ? "" : "s"})`,
|
|
1511
|
-
traversalPath:
|
|
1511
|
+
traversalPath: path61,
|
|
1512
1512
|
edgeProvenances,
|
|
1513
1513
|
confidence,
|
|
1514
1514
|
fixRecommendation: `Inspect ${culpritName}'s failing handler`
|
|
@@ -6503,6 +6503,39 @@ function classifyImage(image) {
|
|
|
6503
6503
|
if (last.startsWith("memcached")) return "memcached";
|
|
6504
6504
|
return "container";
|
|
6505
6505
|
}
|
|
6506
|
+
function lineContaining(raw, needle) {
|
|
6507
|
+
if (!needle) return void 0;
|
|
6508
|
+
const idx = raw.indexOf(needle);
|
|
6509
|
+
if (idx === -1) return void 0;
|
|
6510
|
+
let line = 1;
|
|
6511
|
+
for (let i = 0; i < idx; i++) if (raw[i] === "\n") line++;
|
|
6512
|
+
return line;
|
|
6513
|
+
}
|
|
6514
|
+
function emitPlatformResourceEdge(graph, anchorId, edgeType, kind, name, provider, evidenceFile, line) {
|
|
6515
|
+
let nodesAdded = 0;
|
|
6516
|
+
let edgesAdded = 0;
|
|
6517
|
+
const node = makeInfraNode(kind, name, provider);
|
|
6518
|
+
if (!graph.hasNode(node.id)) {
|
|
6519
|
+
graph.addNode(node.id, node);
|
|
6520
|
+
nodesAdded++;
|
|
6521
|
+
}
|
|
6522
|
+
if (node.id === anchorId) return { nodesAdded, edgesAdded };
|
|
6523
|
+
const edgeId = (0, import_types4.extractedEdgeId)(anchorId, node.id, edgeType);
|
|
6524
|
+
if (!graph.hasEdge(edgeId)) {
|
|
6525
|
+
const edge = {
|
|
6526
|
+
id: edgeId,
|
|
6527
|
+
source: anchorId,
|
|
6528
|
+
target: node.id,
|
|
6529
|
+
type: edgeType,
|
|
6530
|
+
provenance: import_types21.Provenance.EXTRACTED,
|
|
6531
|
+
confidence: (0, import_types21.confidenceForExtracted)("structural"),
|
|
6532
|
+
evidence: { file: evidenceFile, ...line !== void 0 ? { line } : {} }
|
|
6533
|
+
};
|
|
6534
|
+
graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
|
|
6535
|
+
edgesAdded++;
|
|
6536
|
+
}
|
|
6537
|
+
return { nodesAdded, edgesAdded };
|
|
6538
|
+
}
|
|
6506
6539
|
|
|
6507
6540
|
// src/extract/infra/docker-compose.ts
|
|
6508
6541
|
function dependsOnList(value) {
|
|
@@ -6862,7 +6895,7 @@ async function readWranglerConfig(dir) {
|
|
|
6862
6895
|
}
|
|
6863
6896
|
return null;
|
|
6864
6897
|
}
|
|
6865
|
-
function
|
|
6898
|
+
function lineContaining2(raw, needle) {
|
|
6866
6899
|
if (!needle) return void 0;
|
|
6867
6900
|
const idx = raw.indexOf(needle);
|
|
6868
6901
|
if (idx === -1) return void 0;
|
|
@@ -6986,7 +7019,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
|
|
|
6986
7019
|
"cloudflare-route",
|
|
6987
7020
|
route,
|
|
6988
7021
|
evidenceFile,
|
|
6989
|
-
|
|
7022
|
+
lineContaining2(raw, route)
|
|
6990
7023
|
);
|
|
6991
7024
|
nodesAdded += result.nodesAdded;
|
|
6992
7025
|
edgesAdded += result.edgesAdded;
|
|
@@ -7010,7 +7043,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
|
|
|
7010
7043
|
group.kind,
|
|
7011
7044
|
name,
|
|
7012
7045
|
evidenceFile,
|
|
7013
|
-
|
|
7046
|
+
lineContaining2(raw, name)
|
|
7014
7047
|
);
|
|
7015
7048
|
nodesAdded += result.nodesAdded;
|
|
7016
7049
|
edgesAdded += result.edgesAdded;
|
|
@@ -7024,7 +7057,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
|
|
|
7024
7057
|
"cloudflare-cron",
|
|
7025
7058
|
cron,
|
|
7026
7059
|
evidenceFile,
|
|
7027
|
-
|
|
7060
|
+
lineContaining2(raw, cron)
|
|
7028
7061
|
);
|
|
7029
7062
|
nodesAdded += result.nodesAdded;
|
|
7030
7063
|
edgesAdded += result.edgesAdded;
|
|
@@ -7037,7 +7070,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
|
|
|
7037
7070
|
"cloudflare-env-var",
|
|
7038
7071
|
varName,
|
|
7039
7072
|
evidenceFile,
|
|
7040
|
-
|
|
7073
|
+
lineContaining2(raw, varName)
|
|
7041
7074
|
);
|
|
7042
7075
|
nodesAdded += result.nodesAdded;
|
|
7043
7076
|
edgesAdded += result.edgesAdded;
|
|
@@ -7055,7 +7088,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
|
|
|
7055
7088
|
type: import_types25.EdgeType.CALLS,
|
|
7056
7089
|
provenance: import_types25.Provenance.EXTRACTED,
|
|
7057
7090
|
confidence: (0, import_types25.confidenceForExtracted)("structural"),
|
|
7058
|
-
evidence: { file: evidenceFile, line:
|
|
7091
|
+
evidence: { file: evidenceFile, line: lineContaining2(raw, svc.service) }
|
|
7059
7092
|
};
|
|
7060
7093
|
graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
|
|
7061
7094
|
edgesAdded++;
|
|
@@ -7069,11 +7102,208 @@ async function addCloudflareWorkers(graph, services, scanPath) {
|
|
|
7069
7102
|
"cloudflare-service-binding",
|
|
7070
7103
|
svc.service,
|
|
7071
7104
|
evidenceFile,
|
|
7072
|
-
|
|
7105
|
+
lineContaining2(raw, svc.service)
|
|
7106
|
+
);
|
|
7107
|
+
nodesAdded += result.nodesAdded;
|
|
7108
|
+
edgesAdded += result.edgesAdded;
|
|
7109
|
+
}
|
|
7110
|
+
}
|
|
7111
|
+
return { nodesAdded, edgesAdded };
|
|
7112
|
+
}
|
|
7113
|
+
|
|
7114
|
+
// src/extract/infra/vercel.ts
|
|
7115
|
+
init_cjs_shims();
|
|
7116
|
+
var import_node_fs21 = require("fs");
|
|
7117
|
+
var import_node_path37 = __toESM(require("path"), 1);
|
|
7118
|
+
var import_types26 = require("@neat.is/types");
|
|
7119
|
+
var VERCEL_CONFIG_FILENAMES = ["vercel.json", "vercel.jsonc"];
|
|
7120
|
+
async function readVercelConfig(dir) {
|
|
7121
|
+
for (const filename of VERCEL_CONFIG_FILENAMES) {
|
|
7122
|
+
const abs = import_node_path37.default.join(dir, filename);
|
|
7123
|
+
if (!await exists(abs)) continue;
|
|
7124
|
+
const raw = await import_node_fs21.promises.readFile(abs, "utf8");
|
|
7125
|
+
const config = JSON.parse(maskCommentsInSource(raw));
|
|
7126
|
+
return { config, relFile: filename, raw };
|
|
7127
|
+
}
|
|
7128
|
+
return null;
|
|
7129
|
+
}
|
|
7130
|
+
async function readLinkedProjectName(dir) {
|
|
7131
|
+
const abs = import_node_path37.default.join(dir, ".vercel", "project.json");
|
|
7132
|
+
if (!await exists(abs)) return void 0;
|
|
7133
|
+
const parsed = JSON.parse(await import_node_fs21.promises.readFile(abs, "utf8"));
|
|
7134
|
+
return typeof parsed.projectName === "string" ? parsed.projectName : void 0;
|
|
7135
|
+
}
|
|
7136
|
+
function routeSource(route) {
|
|
7137
|
+
return route.source ?? route.src;
|
|
7138
|
+
}
|
|
7139
|
+
async function addVercelServices(graph, services, scanPath) {
|
|
7140
|
+
let nodesAdded = 0;
|
|
7141
|
+
let edgesAdded = 0;
|
|
7142
|
+
for (const service of services) {
|
|
7143
|
+
let read = null;
|
|
7144
|
+
let projectName;
|
|
7145
|
+
try {
|
|
7146
|
+
read = await readVercelConfig(service.dir);
|
|
7147
|
+
projectName = await readLinkedProjectName(service.dir);
|
|
7148
|
+
} catch (err) {
|
|
7149
|
+
recordExtractionError("infra vercel", import_node_path37.default.relative(scanPath, service.dir), err);
|
|
7150
|
+
continue;
|
|
7151
|
+
}
|
|
7152
|
+
if (!read && !projectName) continue;
|
|
7153
|
+
const serviceNode = graph.getNodeAttributes(service.node.id);
|
|
7154
|
+
if (serviceNode.platform !== "vercel" || projectName && serviceNode.platformName !== projectName) {
|
|
7155
|
+
const updated = {
|
|
7156
|
+
...serviceNode,
|
|
7157
|
+
platform: "vercel",
|
|
7158
|
+
...projectName ? { platformName: projectName } : {}
|
|
7159
|
+
};
|
|
7160
|
+
graph.replaceNodeAttributes(service.node.id, updated);
|
|
7161
|
+
}
|
|
7162
|
+
const anchorId = service.node.id;
|
|
7163
|
+
if (!read) continue;
|
|
7164
|
+
const { config, relFile, raw } = read;
|
|
7165
|
+
const evidenceFile = toPosix2(import_node_path37.default.relative(scanPath, import_node_path37.default.join(service.dir, relFile)));
|
|
7166
|
+
const add = (edgeType, kind, name) => {
|
|
7167
|
+
if (!name) return;
|
|
7168
|
+
const result = emitPlatformResourceEdge(
|
|
7169
|
+
graph,
|
|
7170
|
+
anchorId,
|
|
7171
|
+
edgeType,
|
|
7172
|
+
kind,
|
|
7173
|
+
name,
|
|
7174
|
+
"vercel",
|
|
7175
|
+
evidenceFile,
|
|
7176
|
+
lineContaining(raw, name)
|
|
7177
|
+
);
|
|
7178
|
+
nodesAdded += result.nodesAdded;
|
|
7179
|
+
edgesAdded += result.edgesAdded;
|
|
7180
|
+
};
|
|
7181
|
+
add(import_types26.EdgeType.RUNS_ON, "vercel", "vercel");
|
|
7182
|
+
for (const cron of config.crons ?? []) add(import_types26.EdgeType.DEPENDS_ON, "vercel-cron", cron.path ?? cron.schedule);
|
|
7183
|
+
for (const varName of Object.keys(config.env ?? {})) add(import_types26.EdgeType.DEPENDS_ON, "vercel-env-var", varName);
|
|
7184
|
+
for (const varName of Object.keys(config.build?.env ?? {})) add(import_types26.EdgeType.DEPENDS_ON, "vercel-env-var", varName);
|
|
7185
|
+
for (const route of [...config.rewrites ?? [], ...config.redirects ?? [], ...config.routes ?? []]) {
|
|
7186
|
+
add(import_types26.EdgeType.CONNECTS_TO, "vercel-route", routeSource(route));
|
|
7187
|
+
}
|
|
7188
|
+
}
|
|
7189
|
+
return { nodesAdded, edgesAdded };
|
|
7190
|
+
}
|
|
7191
|
+
|
|
7192
|
+
// src/extract/infra/railway.ts
|
|
7193
|
+
init_cjs_shims();
|
|
7194
|
+
var import_node_fs22 = require("fs");
|
|
7195
|
+
var import_node_path38 = __toESM(require("path"), 1);
|
|
7196
|
+
var import_smol_toml3 = require("smol-toml");
|
|
7197
|
+
var import_types27 = require("@neat.is/types");
|
|
7198
|
+
var RAILWAY_FILENAMES = ["railway.toml", "railway.json", "railway.jsonc"];
|
|
7199
|
+
async function readRailwayConfig(dir) {
|
|
7200
|
+
for (const filename of RAILWAY_FILENAMES) {
|
|
7201
|
+
const abs = import_node_path38.default.join(dir, filename);
|
|
7202
|
+
if (!await exists(abs)) continue;
|
|
7203
|
+
const raw = await import_node_fs22.promises.readFile(abs, "utf8");
|
|
7204
|
+
const config = filename === "railway.toml" ? (0, import_smol_toml3.parse)(raw) : JSON.parse(maskCommentsInSource(raw));
|
|
7205
|
+
return { config, relFile: filename, raw };
|
|
7206
|
+
}
|
|
7207
|
+
return null;
|
|
7208
|
+
}
|
|
7209
|
+
async function addRailwayServices(graph, services, scanPath) {
|
|
7210
|
+
let nodesAdded = 0;
|
|
7211
|
+
let edgesAdded = 0;
|
|
7212
|
+
for (const service of services) {
|
|
7213
|
+
let read = null;
|
|
7214
|
+
try {
|
|
7215
|
+
read = await readRailwayConfig(service.dir);
|
|
7216
|
+
} catch (err) {
|
|
7217
|
+
recordExtractionError("infra railway", import_node_path38.default.relative(scanPath, service.dir), err);
|
|
7218
|
+
continue;
|
|
7219
|
+
}
|
|
7220
|
+
if (!read) continue;
|
|
7221
|
+
const serviceNode = graph.getNodeAttributes(service.node.id);
|
|
7222
|
+
if (serviceNode.platform !== "railway") {
|
|
7223
|
+
graph.replaceNodeAttributes(service.node.id, { ...serviceNode, platform: "railway" });
|
|
7224
|
+
}
|
|
7225
|
+
const anchorId = service.node.id;
|
|
7226
|
+
const { config, relFile, raw } = read;
|
|
7227
|
+
const evidenceFile = toPosix2(import_node_path38.default.relative(scanPath, import_node_path38.default.join(service.dir, relFile)));
|
|
7228
|
+
const add = (edgeType, kind, name) => {
|
|
7229
|
+
if (!name) return;
|
|
7230
|
+
const result = emitPlatformResourceEdge(
|
|
7231
|
+
graph,
|
|
7232
|
+
anchorId,
|
|
7233
|
+
edgeType,
|
|
7234
|
+
kind,
|
|
7235
|
+
name,
|
|
7236
|
+
"railway",
|
|
7237
|
+
evidenceFile,
|
|
7238
|
+
lineContaining(raw, name)
|
|
7073
7239
|
);
|
|
7074
7240
|
nodesAdded += result.nodesAdded;
|
|
7075
7241
|
edgesAdded += result.edgesAdded;
|
|
7242
|
+
};
|
|
7243
|
+
add(import_types27.EdgeType.RUNS_ON, "railway", "railway");
|
|
7244
|
+
add(import_types27.EdgeType.CONNECTS_TO, "railway-route", config.deploy?.healthcheckPath);
|
|
7245
|
+
add(import_types27.EdgeType.DEPENDS_ON, "railway-cron", config.deploy?.cronSchedule);
|
|
7246
|
+
}
|
|
7247
|
+
return { nodesAdded, edgesAdded };
|
|
7248
|
+
}
|
|
7249
|
+
|
|
7250
|
+
// src/extract/infra/supabase.ts
|
|
7251
|
+
init_cjs_shims();
|
|
7252
|
+
var import_node_fs23 = require("fs");
|
|
7253
|
+
var import_node_path39 = __toESM(require("path"), 1);
|
|
7254
|
+
var import_smol_toml4 = require("smol-toml");
|
|
7255
|
+
var import_types28 = require("@neat.is/types");
|
|
7256
|
+
async function readSupabaseConfig(dir) {
|
|
7257
|
+
const relFile = import_node_path39.default.join("supabase", "config.toml");
|
|
7258
|
+
const abs = import_node_path39.default.join(dir, relFile);
|
|
7259
|
+
if (!await exists(abs)) return null;
|
|
7260
|
+
const raw = await import_node_fs23.promises.readFile(abs, "utf8");
|
|
7261
|
+
const config = (0, import_smol_toml4.parse)(raw);
|
|
7262
|
+
return { config, relFile, raw };
|
|
7263
|
+
}
|
|
7264
|
+
async function addSupabaseProjects(graph, services, scanPath) {
|
|
7265
|
+
let nodesAdded = 0;
|
|
7266
|
+
let edgesAdded = 0;
|
|
7267
|
+
for (const service of services) {
|
|
7268
|
+
let read = null;
|
|
7269
|
+
try {
|
|
7270
|
+
read = await readSupabaseConfig(service.dir);
|
|
7271
|
+
} catch (err) {
|
|
7272
|
+
recordExtractionError("infra supabase", import_node_path39.default.relative(scanPath, service.dir), err);
|
|
7273
|
+
continue;
|
|
7076
7274
|
}
|
|
7275
|
+
if (!read) continue;
|
|
7276
|
+
const { config, relFile, raw } = read;
|
|
7277
|
+
const projectId = typeof config.project_id === "string" ? config.project_id : void 0;
|
|
7278
|
+
const serviceNode = graph.getNodeAttributes(service.node.id);
|
|
7279
|
+
if (serviceNode.platform !== "supabase" || projectId && serviceNode.platformName !== projectId) {
|
|
7280
|
+
graph.replaceNodeAttributes(service.node.id, {
|
|
7281
|
+
...serviceNode,
|
|
7282
|
+
platform: "supabase",
|
|
7283
|
+
...projectId ? { platformName: projectId } : {}
|
|
7284
|
+
});
|
|
7285
|
+
}
|
|
7286
|
+
const anchorId = service.node.id;
|
|
7287
|
+
const evidenceFile = toPosix2(import_node_path39.default.relative(scanPath, import_node_path39.default.join(service.dir, relFile)));
|
|
7288
|
+
const add = (edgeType, kind, name) => {
|
|
7289
|
+
if (!name) return;
|
|
7290
|
+
const result = emitPlatformResourceEdge(
|
|
7291
|
+
graph,
|
|
7292
|
+
anchorId,
|
|
7293
|
+
edgeType,
|
|
7294
|
+
kind,
|
|
7295
|
+
name,
|
|
7296
|
+
"supabase",
|
|
7297
|
+
evidenceFile,
|
|
7298
|
+
lineContaining(raw, name)
|
|
7299
|
+
);
|
|
7300
|
+
nodesAdded += result.nodesAdded;
|
|
7301
|
+
edgesAdded += result.edgesAdded;
|
|
7302
|
+
};
|
|
7303
|
+
add(import_types28.EdgeType.RUNS_ON, "supabase", "supabase");
|
|
7304
|
+
for (const fn of Object.keys(config.functions ?? {})) add(import_types28.EdgeType.DEPENDS_ON, "supabase-function", fn);
|
|
7305
|
+
if (config.storage) add(import_types28.EdgeType.DEPENDS_ON, "supabase-storage", "storage");
|
|
7306
|
+
if (config.auth) add(import_types28.EdgeType.DEPENDS_ON, "supabase-auth", "auth");
|
|
7077
7307
|
}
|
|
7078
7308
|
return { nodesAdded, edgesAdded };
|
|
7079
7309
|
}
|
|
@@ -7085,24 +7315,27 @@ async function addInfra(graph, scanPath, services) {
|
|
|
7085
7315
|
const terraform = await addTerraformResources(graph, scanPath);
|
|
7086
7316
|
const k8s = await addK8sResources(graph, scanPath);
|
|
7087
7317
|
const cloudflare = await addCloudflareWorkers(graph, services, scanPath);
|
|
7318
|
+
const vercel = await addVercelServices(graph, services, scanPath);
|
|
7319
|
+
const railway = await addRailwayServices(graph, services, scanPath);
|
|
7320
|
+
const supabase = await addSupabaseProjects(graph, services, scanPath);
|
|
7088
7321
|
return {
|
|
7089
|
-
nodesAdded: compose.nodesAdded + dockerfile.nodesAdded + terraform.nodesAdded + k8s.nodesAdded + cloudflare.nodesAdded,
|
|
7090
|
-
edgesAdded: compose.edgesAdded + dockerfile.edgesAdded + terraform.edgesAdded + k8s.edgesAdded + cloudflare.edgesAdded
|
|
7322
|
+
nodesAdded: compose.nodesAdded + dockerfile.nodesAdded + terraform.nodesAdded + k8s.nodesAdded + cloudflare.nodesAdded + vercel.nodesAdded + railway.nodesAdded + supabase.nodesAdded,
|
|
7323
|
+
edgesAdded: compose.edgesAdded + dockerfile.edgesAdded + terraform.edgesAdded + k8s.edgesAdded + cloudflare.edgesAdded + vercel.edgesAdded + railway.edgesAdded + supabase.edgesAdded
|
|
7091
7324
|
};
|
|
7092
7325
|
}
|
|
7093
7326
|
|
|
7094
7327
|
// src/extract/index.ts
|
|
7095
|
-
var
|
|
7328
|
+
var import_node_path41 = __toESM(require("path"), 1);
|
|
7096
7329
|
|
|
7097
7330
|
// src/extract/retire.ts
|
|
7098
7331
|
init_cjs_shims();
|
|
7099
|
-
var
|
|
7100
|
-
var
|
|
7101
|
-
var
|
|
7332
|
+
var import_node_fs24 = require("fs");
|
|
7333
|
+
var import_node_path40 = __toESM(require("path"), 1);
|
|
7334
|
+
var import_types29 = require("@neat.is/types");
|
|
7102
7335
|
function dropOrphanedFileNodes(graph) {
|
|
7103
7336
|
const orphans = [];
|
|
7104
7337
|
graph.forEachNode((id, attrs) => {
|
|
7105
|
-
if (attrs.type !==
|
|
7338
|
+
if (attrs.type !== import_types29.NodeType.FileNode) return;
|
|
7106
7339
|
if (graph.inboundEdges(id).length === 0 && graph.outboundEdges(id).length === 0) {
|
|
7107
7340
|
orphans.push(id);
|
|
7108
7341
|
}
|
|
@@ -7115,7 +7348,7 @@ function retireEdgesByFile(graph, file) {
|
|
|
7115
7348
|
const toDrop = [];
|
|
7116
7349
|
graph.forEachEdge((id, attrs) => {
|
|
7117
7350
|
const edge = attrs;
|
|
7118
|
-
if (edge.provenance !==
|
|
7351
|
+
if (edge.provenance !== import_types29.Provenance.EXTRACTED) return;
|
|
7119
7352
|
if (!edge.evidence?.file) return;
|
|
7120
7353
|
if (edge.evidence.file === normalized) toDrop.push(id);
|
|
7121
7354
|
});
|
|
@@ -7128,14 +7361,14 @@ function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = []) {
|
|
|
7128
7361
|
const bases = [scanPath, ...serviceDirs];
|
|
7129
7362
|
graph.forEachEdge((id, attrs) => {
|
|
7130
7363
|
const edge = attrs;
|
|
7131
|
-
if (edge.provenance !==
|
|
7364
|
+
if (edge.provenance !== import_types29.Provenance.EXTRACTED) return;
|
|
7132
7365
|
const evidenceFile = edge.evidence?.file;
|
|
7133
7366
|
if (!evidenceFile) return;
|
|
7134
|
-
if (
|
|
7135
|
-
if (!(0,
|
|
7367
|
+
if (import_node_path40.default.isAbsolute(evidenceFile)) {
|
|
7368
|
+
if (!(0, import_node_fs24.existsSync)(evidenceFile)) toDrop.push(id);
|
|
7136
7369
|
return;
|
|
7137
7370
|
}
|
|
7138
|
-
const found = bases.some((base) => (0,
|
|
7371
|
+
const found = bases.some((base) => (0, import_node_fs24.existsSync)(import_node_path40.default.join(base, evidenceFile)));
|
|
7139
7372
|
if (!found) toDrop.push(id);
|
|
7140
7373
|
});
|
|
7141
7374
|
for (const id of toDrop) graph.dropEdge(id);
|
|
@@ -7177,7 +7410,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
7177
7410
|
}
|
|
7178
7411
|
const droppedEntries = drainDroppedExtracted();
|
|
7179
7412
|
if (isRejectedLogEnabled() && opts.errorsPath && droppedEntries.length > 0) {
|
|
7180
|
-
const rejectedPath =
|
|
7413
|
+
const rejectedPath = import_node_path41.default.join(import_node_path41.default.dirname(opts.errorsPath), "rejected.ndjson");
|
|
7181
7414
|
try {
|
|
7182
7415
|
await writeRejectedExtracted(droppedEntries, rejectedPath);
|
|
7183
7416
|
} catch (err) {
|
|
@@ -7211,7 +7444,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
7211
7444
|
|
|
7212
7445
|
// src/divergences.ts
|
|
7213
7446
|
init_cjs_shims();
|
|
7214
|
-
var
|
|
7447
|
+
var import_types30 = require("@neat.is/types");
|
|
7215
7448
|
function bucketKey(source, target, type) {
|
|
7216
7449
|
return `${type}|${source}|${target}`;
|
|
7217
7450
|
}
|
|
@@ -7219,22 +7452,22 @@ function bucketEdges(graph) {
|
|
|
7219
7452
|
const buckets2 = /* @__PURE__ */ new Map();
|
|
7220
7453
|
graph.forEachEdge((id, attrs) => {
|
|
7221
7454
|
const e = attrs;
|
|
7222
|
-
const parsed = (0,
|
|
7455
|
+
const parsed = (0, import_types30.parseEdgeId)(id);
|
|
7223
7456
|
const provenance = parsed?.provenance ?? e.provenance;
|
|
7224
7457
|
const key = bucketKey(e.source, e.target, e.type);
|
|
7225
7458
|
const cur = buckets2.get(key) ?? { source: e.source, target: e.target, type: e.type };
|
|
7226
7459
|
switch (provenance) {
|
|
7227
|
-
case
|
|
7460
|
+
case import_types30.Provenance.EXTRACTED:
|
|
7228
7461
|
cur.extracted = e;
|
|
7229
7462
|
break;
|
|
7230
|
-
case
|
|
7463
|
+
case import_types30.Provenance.OBSERVED:
|
|
7231
7464
|
cur.observed = e;
|
|
7232
7465
|
break;
|
|
7233
|
-
case
|
|
7466
|
+
case import_types30.Provenance.INFERRED:
|
|
7234
7467
|
cur.inferred = e;
|
|
7235
7468
|
break;
|
|
7236
7469
|
default:
|
|
7237
|
-
if (e.provenance ===
|
|
7470
|
+
if (e.provenance === import_types30.Provenance.STALE) cur.stale = e;
|
|
7238
7471
|
}
|
|
7239
7472
|
buckets2.set(key, cur);
|
|
7240
7473
|
});
|
|
@@ -7243,12 +7476,12 @@ function bucketEdges(graph) {
|
|
|
7243
7476
|
function nodeIsFrontier(graph, nodeId) {
|
|
7244
7477
|
if (!graph.hasNode(nodeId)) return false;
|
|
7245
7478
|
const attrs = graph.getNodeAttributes(nodeId);
|
|
7246
|
-
return attrs.type ===
|
|
7479
|
+
return attrs.type === import_types30.NodeType.FrontierNode;
|
|
7247
7480
|
}
|
|
7248
7481
|
function nodeIsWebsocketChannel(graph, nodeId) {
|
|
7249
7482
|
if (!graph.hasNode(nodeId)) return false;
|
|
7250
7483
|
const attrs = graph.getNodeAttributes(nodeId);
|
|
7251
|
-
return attrs.type ===
|
|
7484
|
+
return attrs.type === import_types30.NodeType.WebSocketChannelNode;
|
|
7252
7485
|
}
|
|
7253
7486
|
function clampConfidence(n) {
|
|
7254
7487
|
if (!Number.isFinite(n)) return 0;
|
|
@@ -7268,14 +7501,14 @@ function gradedConfidence(edge) {
|
|
|
7268
7501
|
return clampConfidence(confidenceForEdge(edge));
|
|
7269
7502
|
}
|
|
7270
7503
|
var OBSERVABLE_EDGE_TYPES = /* @__PURE__ */ new Set([
|
|
7271
|
-
|
|
7272
|
-
|
|
7273
|
-
|
|
7274
|
-
|
|
7504
|
+
import_types30.EdgeType.CALLS,
|
|
7505
|
+
import_types30.EdgeType.CONNECTS_TO,
|
|
7506
|
+
import_types30.EdgeType.PUBLISHES_TO,
|
|
7507
|
+
import_types30.EdgeType.CONSUMES_FROM
|
|
7275
7508
|
]);
|
|
7276
7509
|
function detectMissingDivergences(graph, bucket) {
|
|
7277
7510
|
const out = [];
|
|
7278
|
-
if (bucket.type ===
|
|
7511
|
+
if (bucket.type === import_types30.EdgeType.CONTAINS) return out;
|
|
7279
7512
|
if (bucket.extracted && !bucket.observed && OBSERVABLE_EDGE_TYPES.has(bucket.type)) {
|
|
7280
7513
|
if (!nodeIsFrontier(graph, bucket.target)) {
|
|
7281
7514
|
out.push({
|
|
@@ -7316,7 +7549,7 @@ function declaredHostFor(svc) {
|
|
|
7316
7549
|
function hasExtractedConfiguredBy(graph, svcId) {
|
|
7317
7550
|
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
7318
7551
|
const e = graph.getEdgeAttributes(edgeId);
|
|
7319
|
-
if (e.type ===
|
|
7552
|
+
if (e.type === import_types30.EdgeType.CONFIGURED_BY && e.provenance === import_types30.Provenance.EXTRACTED) {
|
|
7320
7553
|
return true;
|
|
7321
7554
|
}
|
|
7322
7555
|
}
|
|
@@ -7329,10 +7562,10 @@ function detectHostMismatch(graph, svcId, svc) {
|
|
|
7329
7562
|
const out = [];
|
|
7330
7563
|
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
7331
7564
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
7332
|
-
if (edge.type !==
|
|
7333
|
-
if (edge.provenance !==
|
|
7565
|
+
if (edge.type !== import_types30.EdgeType.CONNECTS_TO) continue;
|
|
7566
|
+
if (edge.provenance !== import_types30.Provenance.OBSERVED) continue;
|
|
7334
7567
|
const target = graph.getNodeAttributes(edge.target);
|
|
7335
|
-
if (target.type !==
|
|
7568
|
+
if (target.type !== import_types30.NodeType.DatabaseNode) continue;
|
|
7336
7569
|
const observedHost = target.host?.trim();
|
|
7337
7570
|
if (!observedHost) continue;
|
|
7338
7571
|
if (observedHost === declaredHost) continue;
|
|
@@ -7354,10 +7587,10 @@ function detectCompatDivergences(graph, svcId, svc) {
|
|
|
7354
7587
|
const deps = svc.dependencies ?? {};
|
|
7355
7588
|
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
7356
7589
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
7357
|
-
if (edge.type !==
|
|
7358
|
-
if (edge.provenance !==
|
|
7590
|
+
if (edge.type !== import_types30.EdgeType.CONNECTS_TO) continue;
|
|
7591
|
+
if (edge.provenance !== import_types30.Provenance.OBSERVED) continue;
|
|
7359
7592
|
const target = graph.getNodeAttributes(edge.target);
|
|
7360
|
-
if (target.type !==
|
|
7593
|
+
if (target.type !== import_types30.NodeType.DatabaseNode) continue;
|
|
7361
7594
|
for (const pair of compatPairs()) {
|
|
7362
7595
|
if (pair.engine !== target.engine) continue;
|
|
7363
7596
|
const declared = deps[pair.driver];
|
|
@@ -7416,7 +7649,7 @@ function suppressHostMismatchHalves(all) {
|
|
|
7416
7649
|
for (const d of all) {
|
|
7417
7650
|
if (d.type !== "host-mismatch") continue;
|
|
7418
7651
|
observedHalf.add(`${d.source}->${d.target}`);
|
|
7419
|
-
declaredHalf.add((0,
|
|
7652
|
+
declaredHalf.add((0, import_types30.databaseId)(d.extractedHost));
|
|
7420
7653
|
}
|
|
7421
7654
|
if (observedHalf.size === 0) return all;
|
|
7422
7655
|
return all.filter((d) => {
|
|
@@ -7435,7 +7668,7 @@ function computeDivergences(graph, opts = {}) {
|
|
|
7435
7668
|
}
|
|
7436
7669
|
graph.forEachNode((nodeId, attrs) => {
|
|
7437
7670
|
const n = attrs;
|
|
7438
|
-
if (n.type !==
|
|
7671
|
+
if (n.type !== import_types30.NodeType.ServiceNode) return;
|
|
7439
7672
|
const svc = n;
|
|
7440
7673
|
for (const d of detectHostMismatch(graph, nodeId, svc)) all.push(d);
|
|
7441
7674
|
for (const d of detectCompatDivergences(graph, nodeId, svc)) all.push(d);
|
|
@@ -7469,7 +7702,7 @@ function computeDivergences(graph, opts = {}) {
|
|
|
7469
7702
|
if (a.source !== b.source) return a.source.localeCompare(b.source);
|
|
7470
7703
|
return a.target.localeCompare(b.target);
|
|
7471
7704
|
});
|
|
7472
|
-
return
|
|
7705
|
+
return import_types30.DivergenceResultSchema.parse({
|
|
7473
7706
|
divergences: filtered,
|
|
7474
7707
|
totalAffected: filtered.length,
|
|
7475
7708
|
computedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -7478,9 +7711,9 @@ function computeDivergences(graph, opts = {}) {
|
|
|
7478
7711
|
|
|
7479
7712
|
// src/persist.ts
|
|
7480
7713
|
init_cjs_shims();
|
|
7481
|
-
var
|
|
7482
|
-
var
|
|
7483
|
-
var
|
|
7714
|
+
var import_node_fs25 = require("fs");
|
|
7715
|
+
var import_node_path42 = __toESM(require("path"), 1);
|
|
7716
|
+
var import_types31 = require("@neat.is/types");
|
|
7484
7717
|
var SCHEMA_VERSION = 4;
|
|
7485
7718
|
function migrateV1ToV2(payload) {
|
|
7486
7719
|
const nodes = payload.graph.nodes;
|
|
@@ -7502,12 +7735,12 @@ function migrateV2ToV3(payload) {
|
|
|
7502
7735
|
for (const edge of edges) {
|
|
7503
7736
|
const attrs = edge.attributes;
|
|
7504
7737
|
if (!attrs || attrs.provenance !== "FRONTIER") continue;
|
|
7505
|
-
attrs.provenance =
|
|
7738
|
+
attrs.provenance = import_types31.Provenance.OBSERVED;
|
|
7506
7739
|
const type = typeof attrs.type === "string" ? attrs.type : void 0;
|
|
7507
7740
|
const source = typeof attrs.source === "string" ? attrs.source : void 0;
|
|
7508
7741
|
const target = typeof attrs.target === "string" ? attrs.target : void 0;
|
|
7509
7742
|
if (type && source && target) {
|
|
7510
|
-
const newId = (0,
|
|
7743
|
+
const newId = (0, import_types31.observedEdgeId)(source, target, type);
|
|
7511
7744
|
attrs.id = newId;
|
|
7512
7745
|
if (edge.key) edge.key = newId;
|
|
7513
7746
|
}
|
|
@@ -7516,7 +7749,7 @@ function migrateV2ToV3(payload) {
|
|
|
7516
7749
|
return { ...payload, schemaVersion: 3 };
|
|
7517
7750
|
}
|
|
7518
7751
|
async function ensureDir(filePath) {
|
|
7519
|
-
await
|
|
7752
|
+
await import_node_fs25.promises.mkdir(import_node_path42.default.dirname(filePath), { recursive: true });
|
|
7520
7753
|
}
|
|
7521
7754
|
async function saveGraphToDisk(graph, outPath) {
|
|
7522
7755
|
await ensureDir(outPath);
|
|
@@ -7526,13 +7759,13 @@ async function saveGraphToDisk(graph, outPath) {
|
|
|
7526
7759
|
graph: graph.export()
|
|
7527
7760
|
};
|
|
7528
7761
|
const tmp = `${outPath}.tmp`;
|
|
7529
|
-
await
|
|
7530
|
-
await
|
|
7762
|
+
await import_node_fs25.promises.writeFile(tmp, JSON.stringify(payload), "utf8");
|
|
7763
|
+
await import_node_fs25.promises.rename(tmp, outPath);
|
|
7531
7764
|
}
|
|
7532
7765
|
async function loadGraphFromDisk(graph, outPath) {
|
|
7533
7766
|
let raw;
|
|
7534
7767
|
try {
|
|
7535
|
-
raw = await
|
|
7768
|
+
raw = await import_node_fs25.promises.readFile(outPath, "utf8");
|
|
7536
7769
|
} catch (err) {
|
|
7537
7770
|
if (err.code === "ENOENT") return;
|
|
7538
7771
|
throw err;
|
|
@@ -7597,8 +7830,8 @@ function startPersistLoop(graph, outPath, opts = {}) {
|
|
|
7597
7830
|
|
|
7598
7831
|
// src/gitignore.ts
|
|
7599
7832
|
init_cjs_shims();
|
|
7600
|
-
var
|
|
7601
|
-
var
|
|
7833
|
+
var import_node_fs26 = require("fs");
|
|
7834
|
+
var import_node_path43 = __toESM(require("path"), 1);
|
|
7602
7835
|
var NEAT_OUT_LINE = "neat-out/";
|
|
7603
7836
|
var NEAT_HEADER = "# NEAT \u2014 machine-local snapshots and events";
|
|
7604
7837
|
function isNeatOutLine(line) {
|
|
@@ -7606,15 +7839,15 @@ function isNeatOutLine(line) {
|
|
|
7606
7839
|
return trimmed === "neat-out/" || trimmed === "neat-out";
|
|
7607
7840
|
}
|
|
7608
7841
|
async function ensureNeatOutIgnored(projectDir) {
|
|
7609
|
-
const file =
|
|
7842
|
+
const file = import_node_path43.default.join(projectDir, ".gitignore");
|
|
7610
7843
|
let existing = null;
|
|
7611
7844
|
try {
|
|
7612
|
-
existing = await
|
|
7845
|
+
existing = await import_node_fs26.promises.readFile(file, "utf8");
|
|
7613
7846
|
} catch (err) {
|
|
7614
7847
|
if (err.code !== "ENOENT") throw err;
|
|
7615
7848
|
}
|
|
7616
7849
|
if (existing === null) {
|
|
7617
|
-
await
|
|
7850
|
+
await import_node_fs26.promises.writeFile(file, `${NEAT_HEADER}
|
|
7618
7851
|
${NEAT_OUT_LINE}
|
|
7619
7852
|
`, "utf8");
|
|
7620
7853
|
return { action: "created", file };
|
|
@@ -7627,13 +7860,13 @@ ${NEAT_OUT_LINE}
|
|
|
7627
7860
|
${NEAT_HEADER}
|
|
7628
7861
|
${NEAT_OUT_LINE}
|
|
7629
7862
|
`;
|
|
7630
|
-
await
|
|
7863
|
+
await import_node_fs26.promises.writeFile(file, existing + appended, "utf8");
|
|
7631
7864
|
return { action: "added", file };
|
|
7632
7865
|
}
|
|
7633
7866
|
|
|
7634
7867
|
// src/summary.ts
|
|
7635
7868
|
init_cjs_shims();
|
|
7636
|
-
var
|
|
7869
|
+
var import_types32 = require("@neat.is/types");
|
|
7637
7870
|
function renderOtelEnvBlock() {
|
|
7638
7871
|
return [
|
|
7639
7872
|
"for prod OTel routing, set these in your deploy platform's env:",
|
|
@@ -7643,19 +7876,19 @@ function renderOtelEnvBlock() {
|
|
|
7643
7876
|
}
|
|
7644
7877
|
function findIncompatServices(nodes) {
|
|
7645
7878
|
return nodes.filter(
|
|
7646
|
-
(n) => n.type ===
|
|
7879
|
+
(n) => n.type === import_types32.NodeType.ServiceNode && Array.isArray(n.incompatibilities) && (n.incompatibilities ?? []).length > 0
|
|
7647
7880
|
);
|
|
7648
7881
|
}
|
|
7649
7882
|
function servicesWithoutObserved(nodes, edges) {
|
|
7650
7883
|
const seen = /* @__PURE__ */ new Set();
|
|
7651
7884
|
for (const e of edges) {
|
|
7652
|
-
if (e.provenance ===
|
|
7885
|
+
if (e.provenance === import_types32.Provenance.OBSERVED) {
|
|
7653
7886
|
seen.add(e.source);
|
|
7654
7887
|
seen.add(e.target);
|
|
7655
7888
|
}
|
|
7656
7889
|
}
|
|
7657
7890
|
return nodes.filter(
|
|
7658
|
-
(n) => n.type ===
|
|
7891
|
+
(n) => n.type === import_types32.NodeType.ServiceNode && !seen.has(n.id)
|
|
7659
7892
|
);
|
|
7660
7893
|
}
|
|
7661
7894
|
function formatDivergence(d) {
|
|
@@ -7729,27 +7962,27 @@ function formatIncompat(inc) {
|
|
|
7729
7962
|
|
|
7730
7963
|
// src/watch.ts
|
|
7731
7964
|
init_cjs_shims();
|
|
7732
|
-
var
|
|
7733
|
-
var
|
|
7965
|
+
var import_node_fs35 = __toESM(require("fs"), 1);
|
|
7966
|
+
var import_node_path54 = __toESM(require("path"), 1);
|
|
7734
7967
|
var import_chokidar = __toESM(require("chokidar"), 1);
|
|
7735
7968
|
|
|
7736
7969
|
// src/api.ts
|
|
7737
7970
|
init_cjs_shims();
|
|
7738
7971
|
var import_fastify = __toESM(require("fastify"), 1);
|
|
7739
7972
|
var import_cors = __toESM(require("@fastify/cors"), 1);
|
|
7740
|
-
var
|
|
7973
|
+
var import_types34 = require("@neat.is/types");
|
|
7741
7974
|
|
|
7742
7975
|
// src/extend/index.ts
|
|
7743
7976
|
init_cjs_shims();
|
|
7744
|
-
var
|
|
7745
|
-
var
|
|
7977
|
+
var import_node_fs28 = require("fs");
|
|
7978
|
+
var import_node_path45 = __toESM(require("path"), 1);
|
|
7746
7979
|
var import_node_os2 = __toESM(require("os"), 1);
|
|
7747
7980
|
var import_instrumentation_registry = require("@neat.is/instrumentation-registry");
|
|
7748
7981
|
|
|
7749
7982
|
// src/installers/package-manager.ts
|
|
7750
7983
|
init_cjs_shims();
|
|
7751
|
-
var
|
|
7752
|
-
var
|
|
7984
|
+
var import_node_fs27 = require("fs");
|
|
7985
|
+
var import_node_path44 = __toESM(require("path"), 1);
|
|
7753
7986
|
var import_node_child_process = require("child_process");
|
|
7754
7987
|
var LOCKFILE_PRIORITY = [
|
|
7755
7988
|
{ lockfile: "bun.lockb", pm: "bun", args: ["install", "--no-summary"] },
|
|
@@ -7764,29 +7997,29 @@ var LOCKFILE_PRIORITY = [
|
|
|
7764
7997
|
var NPM_FALLBACK_ARGS = ["install", "--no-audit", "--no-fund", "--prefer-offline"];
|
|
7765
7998
|
async function exists2(p) {
|
|
7766
7999
|
try {
|
|
7767
|
-
await
|
|
8000
|
+
await import_node_fs27.promises.access(p);
|
|
7768
8001
|
return true;
|
|
7769
8002
|
} catch {
|
|
7770
8003
|
return false;
|
|
7771
8004
|
}
|
|
7772
8005
|
}
|
|
7773
8006
|
async function detectPackageManager(serviceDir) {
|
|
7774
|
-
let dir =
|
|
8007
|
+
let dir = import_node_path44.default.resolve(serviceDir);
|
|
7775
8008
|
const stops = /* @__PURE__ */ new Set();
|
|
7776
8009
|
for (let i = 0; i < 64; i++) {
|
|
7777
8010
|
if (stops.has(dir)) break;
|
|
7778
8011
|
stops.add(dir);
|
|
7779
8012
|
for (const candidate of LOCKFILE_PRIORITY) {
|
|
7780
|
-
const lockPath =
|
|
8013
|
+
const lockPath = import_node_path44.default.join(dir, candidate.lockfile);
|
|
7781
8014
|
if (await exists2(lockPath)) {
|
|
7782
8015
|
return { pm: candidate.pm, cwd: dir, args: [...candidate.args] };
|
|
7783
8016
|
}
|
|
7784
8017
|
}
|
|
7785
|
-
const parent =
|
|
8018
|
+
const parent = import_node_path44.default.dirname(dir);
|
|
7786
8019
|
if (parent === dir) break;
|
|
7787
8020
|
dir = parent;
|
|
7788
8021
|
}
|
|
7789
|
-
return { pm: "npm", cwd:
|
|
8022
|
+
return { pm: "npm", cwd: import_node_path44.default.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
|
|
7790
8023
|
}
|
|
7791
8024
|
async function runPackageManagerInstall(cmd) {
|
|
7792
8025
|
return new Promise((resolve) => {
|
|
@@ -7828,30 +8061,30 @@ ${err.message}`
|
|
|
7828
8061
|
// src/extend/index.ts
|
|
7829
8062
|
async function fileExists2(p) {
|
|
7830
8063
|
try {
|
|
7831
|
-
await
|
|
8064
|
+
await import_node_fs28.promises.access(p);
|
|
7832
8065
|
return true;
|
|
7833
8066
|
} catch {
|
|
7834
8067
|
return false;
|
|
7835
8068
|
}
|
|
7836
8069
|
}
|
|
7837
8070
|
async function readPackageJson(scanPath) {
|
|
7838
|
-
const pkgPath =
|
|
7839
|
-
const raw = await
|
|
8071
|
+
const pkgPath = import_node_path45.default.join(scanPath, "package.json");
|
|
8072
|
+
const raw = await import_node_fs28.promises.readFile(pkgPath, "utf8");
|
|
7840
8073
|
return JSON.parse(raw);
|
|
7841
8074
|
}
|
|
7842
8075
|
async function findHookFiles(scanPath) {
|
|
7843
|
-
const entries = await
|
|
8076
|
+
const entries = await import_node_fs28.promises.readdir(scanPath);
|
|
7844
8077
|
return entries.filter(
|
|
7845
8078
|
(e) => (e.startsWith("instrumentation") || e.startsWith("otel-init")) && /\.(ts|js)$/.test(e)
|
|
7846
8079
|
).sort();
|
|
7847
8080
|
}
|
|
7848
8081
|
function extendLogPath() {
|
|
7849
|
-
return process.env.NEAT_EXTEND_LOG ??
|
|
8082
|
+
return process.env.NEAT_EXTEND_LOG ?? import_node_path45.default.join(import_node_os2.default.homedir(), ".neat", "extend-log.ndjson");
|
|
7850
8083
|
}
|
|
7851
8084
|
async function appendExtendLog(entry2) {
|
|
7852
8085
|
const logPath = extendLogPath();
|
|
7853
|
-
await
|
|
7854
|
-
await
|
|
8086
|
+
await import_node_fs28.promises.mkdir(import_node_path45.default.dirname(logPath), { recursive: true });
|
|
8087
|
+
await import_node_fs28.promises.appendFile(logPath, JSON.stringify(entry2) + "\n", "utf8");
|
|
7855
8088
|
}
|
|
7856
8089
|
function splicedContent(fileContent, snippet2) {
|
|
7857
8090
|
if (fileContent.includes("__INSTRUMENTATION_BLOCK__")) {
|
|
@@ -7909,7 +8142,7 @@ function lookupInstrumentation(library, installedVersion) {
|
|
|
7909
8142
|
}
|
|
7910
8143
|
async function describeProjectInstrumentation(ctx) {
|
|
7911
8144
|
const hookFiles = await findHookFiles(ctx.scanPath);
|
|
7912
|
-
const envNeat = await fileExists2(
|
|
8145
|
+
const envNeat = await fileExists2(import_node_path45.default.join(ctx.scanPath, ".env.neat"));
|
|
7913
8146
|
const registryInstrPackages = new Set(
|
|
7914
8147
|
(0, import_instrumentation_registry.list)().map((e) => e.instrumentation_package).filter((p) => !!p)
|
|
7915
8148
|
);
|
|
@@ -7931,31 +8164,31 @@ async function applyExtension(ctx, args, options) {
|
|
|
7931
8164
|
);
|
|
7932
8165
|
}
|
|
7933
8166
|
for (const file of hookFiles) {
|
|
7934
|
-
const content = await
|
|
8167
|
+
const content = await import_node_fs28.promises.readFile(import_node_path45.default.join(ctx.scanPath, file), "utf8");
|
|
7935
8168
|
if (content.includes(args.registration_snippet)) {
|
|
7936
8169
|
return { library: args.library, filesTouched: [], depsAdded: [], installOutput: "", alreadyApplied: true };
|
|
7937
8170
|
}
|
|
7938
8171
|
}
|
|
7939
8172
|
const primaryFile = hookFiles[0];
|
|
7940
|
-
const primaryPath =
|
|
8173
|
+
const primaryPath = import_node_path45.default.join(ctx.scanPath, primaryFile);
|
|
7941
8174
|
const filesTouched = [];
|
|
7942
8175
|
const depsAdded = [];
|
|
7943
|
-
const pkgPath =
|
|
8176
|
+
const pkgPath = import_node_path45.default.join(ctx.scanPath, "package.json");
|
|
7944
8177
|
const pkg = await readPackageJson(ctx.scanPath);
|
|
7945
8178
|
if (!(pkg.dependencies ?? {})[args.instrumentation_package]) {
|
|
7946
8179
|
pkg.dependencies = { ...pkg.dependencies ?? {}, [args.instrumentation_package]: args.version };
|
|
7947
|
-
await
|
|
8180
|
+
await import_node_fs28.promises.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
|
|
7948
8181
|
filesTouched.push("package.json");
|
|
7949
8182
|
depsAdded.push(`${args.instrumentation_package}@${args.version}`);
|
|
7950
8183
|
}
|
|
7951
|
-
const hookContent = await
|
|
8184
|
+
const hookContent = await import_node_fs28.promises.readFile(primaryPath, "utf8");
|
|
7952
8185
|
const patched = splicedContent(hookContent, args.registration_snippet);
|
|
7953
8186
|
if (!patched) {
|
|
7954
8187
|
throw new Error(
|
|
7955
8188
|
`Could not find instrumentation insertion point in ${primaryFile}. Expected __INSTRUMENTATION_BLOCK__, instrumentations.push(, or new NodeSDK(.`
|
|
7956
8189
|
);
|
|
7957
8190
|
}
|
|
7958
|
-
await
|
|
8191
|
+
await import_node_fs28.promises.writeFile(primaryPath, patched, "utf8");
|
|
7959
8192
|
filesTouched.push(primaryFile);
|
|
7960
8193
|
const cmd = await detectPackageManager(ctx.scanPath);
|
|
7961
8194
|
const installer = options?.runInstall ?? runPackageManagerInstall;
|
|
@@ -7986,7 +8219,7 @@ async function dryRunExtension(ctx, args) {
|
|
|
7986
8219
|
};
|
|
7987
8220
|
}
|
|
7988
8221
|
for (const file of hookFiles) {
|
|
7989
|
-
const content = await
|
|
8222
|
+
const content = await import_node_fs28.promises.readFile(import_node_path45.default.join(ctx.scanPath, file), "utf8");
|
|
7990
8223
|
if (content.includes(args.registration_snippet)) {
|
|
7991
8224
|
return {
|
|
7992
8225
|
library: args.library,
|
|
@@ -8008,7 +8241,7 @@ async function dryRunExtension(ctx, args) {
|
|
|
8008
8241
|
depsToAdd.push(`${args.instrumentation_package}@${args.version}`);
|
|
8009
8242
|
filesTouched.push("package.json");
|
|
8010
8243
|
}
|
|
8011
|
-
const hookContent = await
|
|
8244
|
+
const hookContent = await import_node_fs28.promises.readFile(import_node_path45.default.join(ctx.scanPath, primaryFile), "utf8");
|
|
8012
8245
|
const patched = splicedContent(hookContent, args.registration_snippet);
|
|
8013
8246
|
if (patched) {
|
|
8014
8247
|
filesTouched.push(primaryFile);
|
|
@@ -8023,28 +8256,28 @@ async function rollbackExtension(ctx, args) {
|
|
|
8023
8256
|
if (!await fileExists2(logPath)) {
|
|
8024
8257
|
return { undone: false, message: "no apply found for library" };
|
|
8025
8258
|
}
|
|
8026
|
-
const raw = await
|
|
8259
|
+
const raw = await import_node_fs28.promises.readFile(logPath, "utf8");
|
|
8027
8260
|
const entries = raw.trim().split("\n").filter(Boolean).map((line) => JSON.parse(line));
|
|
8028
8261
|
const match = [...entries].reverse().find((e) => e.project === ctx.project && e.library === args.library);
|
|
8029
8262
|
if (!match) {
|
|
8030
8263
|
return { undone: false, message: "no apply found for library" };
|
|
8031
8264
|
}
|
|
8032
|
-
const pkgPath =
|
|
8265
|
+
const pkgPath = import_node_path45.default.join(ctx.scanPath, "package.json");
|
|
8033
8266
|
if (await fileExists2(pkgPath)) {
|
|
8034
8267
|
const pkg = await readPackageJson(ctx.scanPath);
|
|
8035
8268
|
if (pkg.dependencies?.[match.instrumentation_package]) {
|
|
8036
8269
|
const { [match.instrumentation_package]: _removed, ...rest } = pkg.dependencies;
|
|
8037
8270
|
pkg.dependencies = rest;
|
|
8038
|
-
await
|
|
8271
|
+
await import_node_fs28.promises.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
|
|
8039
8272
|
}
|
|
8040
8273
|
}
|
|
8041
8274
|
const hookFiles = await findHookFiles(ctx.scanPath);
|
|
8042
8275
|
for (const file of hookFiles) {
|
|
8043
|
-
const filePath =
|
|
8044
|
-
const content = await
|
|
8276
|
+
const filePath = import_node_path45.default.join(ctx.scanPath, file);
|
|
8277
|
+
const content = await import_node_fs28.promises.readFile(filePath, "utf8");
|
|
8045
8278
|
if (content.includes(match.registration_snippet)) {
|
|
8046
8279
|
const filtered = content.split("\n").filter((line) => !line.includes(match.registration_snippet)).join("\n");
|
|
8047
|
-
await
|
|
8280
|
+
await import_node_fs28.promises.writeFile(filePath, filtered, "utf8");
|
|
8048
8281
|
break;
|
|
8049
8282
|
}
|
|
8050
8283
|
}
|
|
@@ -8101,7 +8334,7 @@ function queryLogEntries(opts) {
|
|
|
8101
8334
|
|
|
8102
8335
|
// src/diff.ts
|
|
8103
8336
|
init_cjs_shims();
|
|
8104
|
-
var
|
|
8337
|
+
var import_node_fs29 = require("fs");
|
|
8105
8338
|
async function loadSnapshotForDiff(target) {
|
|
8106
8339
|
if (/^https?:\/\//i.test(target)) {
|
|
8107
8340
|
const res = await fetch(target);
|
|
@@ -8110,7 +8343,7 @@ async function loadSnapshotForDiff(target) {
|
|
|
8110
8343
|
}
|
|
8111
8344
|
return await res.json();
|
|
8112
8345
|
}
|
|
8113
|
-
const raw = await
|
|
8346
|
+
const raw = await import_node_fs29.promises.readFile(target, "utf8");
|
|
8114
8347
|
return JSON.parse(raw);
|
|
8115
8348
|
}
|
|
8116
8349
|
function indexEntries(entries) {
|
|
@@ -8178,23 +8411,23 @@ function canonicalJson(value) {
|
|
|
8178
8411
|
|
|
8179
8412
|
// src/projects.ts
|
|
8180
8413
|
init_cjs_shims();
|
|
8181
|
-
var
|
|
8414
|
+
var import_node_path46 = __toESM(require("path"), 1);
|
|
8182
8415
|
function pathsForProject(project, baseDir) {
|
|
8183
8416
|
if (project === DEFAULT_PROJECT) {
|
|
8184
8417
|
return {
|
|
8185
|
-
snapshotPath:
|
|
8186
|
-
errorsPath:
|
|
8187
|
-
staleEventsPath:
|
|
8188
|
-
embeddingsCachePath:
|
|
8189
|
-
policyViolationsPath:
|
|
8418
|
+
snapshotPath: import_node_path46.default.join(baseDir, "graph.json"),
|
|
8419
|
+
errorsPath: import_node_path46.default.join(baseDir, "errors.ndjson"),
|
|
8420
|
+
staleEventsPath: import_node_path46.default.join(baseDir, "stale-events.ndjson"),
|
|
8421
|
+
embeddingsCachePath: import_node_path46.default.join(baseDir, "embeddings.json"),
|
|
8422
|
+
policyViolationsPath: import_node_path46.default.join(baseDir, "policy-violations.ndjson")
|
|
8190
8423
|
};
|
|
8191
8424
|
}
|
|
8192
8425
|
return {
|
|
8193
|
-
snapshotPath:
|
|
8194
|
-
errorsPath:
|
|
8195
|
-
staleEventsPath:
|
|
8196
|
-
embeddingsCachePath:
|
|
8197
|
-
policyViolationsPath:
|
|
8426
|
+
snapshotPath: import_node_path46.default.join(baseDir, `${project}.json`),
|
|
8427
|
+
errorsPath: import_node_path46.default.join(baseDir, `errors.${project}.ndjson`),
|
|
8428
|
+
staleEventsPath: import_node_path46.default.join(baseDir, `stale-events.${project}.ndjson`),
|
|
8429
|
+
embeddingsCachePath: import_node_path46.default.join(baseDir, `embeddings.${project}.json`),
|
|
8430
|
+
policyViolationsPath: import_node_path46.default.join(baseDir, `policy-violations.${project}.ndjson`)
|
|
8198
8431
|
};
|
|
8199
8432
|
}
|
|
8200
8433
|
var Projects = class {
|
|
@@ -8230,28 +8463,28 @@ var Projects = class {
|
|
|
8230
8463
|
|
|
8231
8464
|
// src/registry.ts
|
|
8232
8465
|
init_cjs_shims();
|
|
8233
|
-
var
|
|
8466
|
+
var import_node_fs30 = require("fs");
|
|
8234
8467
|
var import_node_os3 = __toESM(require("os"), 1);
|
|
8235
|
-
var
|
|
8236
|
-
var
|
|
8468
|
+
var import_node_path47 = __toESM(require("path"), 1);
|
|
8469
|
+
var import_types33 = require("@neat.is/types");
|
|
8237
8470
|
var LOCK_TIMEOUT_MS = 5e3;
|
|
8238
8471
|
var LOCK_RETRY_MS = 50;
|
|
8239
8472
|
function neatHome() {
|
|
8240
8473
|
const override = process.env.NEAT_HOME;
|
|
8241
|
-
if (override && override.length > 0) return
|
|
8242
|
-
return
|
|
8474
|
+
if (override && override.length > 0) return import_node_path47.default.resolve(override);
|
|
8475
|
+
return import_node_path47.default.join(import_node_os3.default.homedir(), ".neat");
|
|
8243
8476
|
}
|
|
8244
8477
|
function registryPath() {
|
|
8245
|
-
return
|
|
8478
|
+
return import_node_path47.default.join(neatHome(), "projects.json");
|
|
8246
8479
|
}
|
|
8247
8480
|
function registryLockPath() {
|
|
8248
|
-
return
|
|
8481
|
+
return import_node_path47.default.join(neatHome(), "projects.json.lock");
|
|
8249
8482
|
}
|
|
8250
8483
|
function daemonPidPath() {
|
|
8251
|
-
return
|
|
8484
|
+
return import_node_path47.default.join(neatHome(), "neatd.pid");
|
|
8252
8485
|
}
|
|
8253
8486
|
function daemonsDir() {
|
|
8254
|
-
return
|
|
8487
|
+
return import_node_path47.default.join(neatHome(), "daemons");
|
|
8255
8488
|
}
|
|
8256
8489
|
function isFiniteInt(v) {
|
|
8257
8490
|
return typeof v === "number" && Number.isFinite(v);
|
|
@@ -8284,7 +8517,7 @@ async function discoverDaemons(probe = defaultDiscoveryProbe) {
|
|
|
8284
8517
|
const dir = daemonsDir();
|
|
8285
8518
|
let names;
|
|
8286
8519
|
try {
|
|
8287
|
-
names = await
|
|
8520
|
+
names = await import_node_fs30.promises.readdir(dir);
|
|
8288
8521
|
} catch (err) {
|
|
8289
8522
|
if (err.code === "ENOENT") return [];
|
|
8290
8523
|
throw err;
|
|
@@ -8292,10 +8525,10 @@ async function discoverDaemons(probe = defaultDiscoveryProbe) {
|
|
|
8292
8525
|
const out = [];
|
|
8293
8526
|
for (const name of names) {
|
|
8294
8527
|
if (!name.endsWith(".json")) continue;
|
|
8295
|
-
const file =
|
|
8528
|
+
const file = import_node_path47.default.join(dir, name);
|
|
8296
8529
|
let raw;
|
|
8297
8530
|
try {
|
|
8298
|
-
raw = await
|
|
8531
|
+
raw = await import_node_fs30.promises.readFile(file, "utf8");
|
|
8299
8532
|
} catch {
|
|
8300
8533
|
continue;
|
|
8301
8534
|
}
|
|
@@ -8308,7 +8541,7 @@ async function discoverDaemons(probe = defaultDiscoveryProbe) {
|
|
|
8308
8541
|
return out;
|
|
8309
8542
|
}
|
|
8310
8543
|
async function removeDaemonRecord(source) {
|
|
8311
|
-
await
|
|
8544
|
+
await import_node_fs30.promises.unlink(source).catch(() => {
|
|
8312
8545
|
});
|
|
8313
8546
|
}
|
|
8314
8547
|
async function listMachineProjects(probe = defaultDiscoveryProbe) {
|
|
@@ -8365,7 +8598,7 @@ function isPidAliveDefault(pid) {
|
|
|
8365
8598
|
}
|
|
8366
8599
|
async function readPidFile(file) {
|
|
8367
8600
|
try {
|
|
8368
|
-
const raw = await
|
|
8601
|
+
const raw = await import_node_fs30.promises.readFile(file, "utf8");
|
|
8369
8602
|
const pid = Number.parseInt(raw.trim(), 10);
|
|
8370
8603
|
return Number.isInteger(pid) && pid > 0 ? pid : void 0;
|
|
8371
8604
|
} catch {
|
|
@@ -8413,32 +8646,32 @@ function lockHolderMessage(holder, lockPath, timeoutMs) {
|
|
|
8413
8646
|
}
|
|
8414
8647
|
}
|
|
8415
8648
|
async function normalizeProjectPath(input) {
|
|
8416
|
-
const resolved =
|
|
8649
|
+
const resolved = import_node_path47.default.resolve(input);
|
|
8417
8650
|
try {
|
|
8418
|
-
return await
|
|
8651
|
+
return await import_node_fs30.promises.realpath(resolved);
|
|
8419
8652
|
} catch {
|
|
8420
8653
|
return resolved;
|
|
8421
8654
|
}
|
|
8422
8655
|
}
|
|
8423
8656
|
async function writeAtomically(target, contents) {
|
|
8424
|
-
await
|
|
8657
|
+
await import_node_fs30.promises.mkdir(import_node_path47.default.dirname(target), { recursive: true });
|
|
8425
8658
|
const tmp = `${target}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
|
|
8426
|
-
const fd = await
|
|
8659
|
+
const fd = await import_node_fs30.promises.open(tmp, "w");
|
|
8427
8660
|
try {
|
|
8428
8661
|
await fd.writeFile(contents, "utf8");
|
|
8429
8662
|
await fd.sync();
|
|
8430
8663
|
} finally {
|
|
8431
8664
|
await fd.close();
|
|
8432
8665
|
}
|
|
8433
|
-
await
|
|
8666
|
+
await import_node_fs30.promises.rename(tmp, target);
|
|
8434
8667
|
}
|
|
8435
8668
|
async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaultLockHolderProbe) {
|
|
8436
8669
|
const deadline = Date.now() + timeoutMs;
|
|
8437
|
-
await
|
|
8670
|
+
await import_node_fs30.promises.mkdir(import_node_path47.default.dirname(lockPath), { recursive: true });
|
|
8438
8671
|
let probedHolder = false;
|
|
8439
8672
|
while (true) {
|
|
8440
8673
|
try {
|
|
8441
|
-
const fd = await
|
|
8674
|
+
const fd = await import_node_fs30.promises.open(lockPath, "wx");
|
|
8442
8675
|
try {
|
|
8443
8676
|
await fd.writeFile(`${process.pid}
|
|
8444
8677
|
`, "utf8");
|
|
@@ -8463,7 +8696,7 @@ async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaul
|
|
|
8463
8696
|
}
|
|
8464
8697
|
}
|
|
8465
8698
|
async function releaseLock(lockPath) {
|
|
8466
|
-
await
|
|
8699
|
+
await import_node_fs30.promises.unlink(lockPath).catch(() => {
|
|
8467
8700
|
});
|
|
8468
8701
|
}
|
|
8469
8702
|
async function withLock(fn) {
|
|
@@ -8479,7 +8712,7 @@ async function readRegistry() {
|
|
|
8479
8712
|
const file = registryPath();
|
|
8480
8713
|
let raw;
|
|
8481
8714
|
try {
|
|
8482
|
-
raw = await
|
|
8715
|
+
raw = await import_node_fs30.promises.readFile(file, "utf8");
|
|
8483
8716
|
} catch (err) {
|
|
8484
8717
|
if (err.code === "ENOENT") {
|
|
8485
8718
|
return { version: 1, projects: [] };
|
|
@@ -8487,10 +8720,10 @@ async function readRegistry() {
|
|
|
8487
8720
|
throw err;
|
|
8488
8721
|
}
|
|
8489
8722
|
const parsed = JSON.parse(raw);
|
|
8490
|
-
return
|
|
8723
|
+
return import_types33.RegistryFileSchema.parse(parsed);
|
|
8491
8724
|
}
|
|
8492
8725
|
async function writeRegistry(reg) {
|
|
8493
|
-
const validated =
|
|
8726
|
+
const validated = import_types33.RegistryFileSchema.parse(reg);
|
|
8494
8727
|
await writeAtomically(registryPath(), JSON.stringify(validated, null, 2) + "\n");
|
|
8495
8728
|
}
|
|
8496
8729
|
var ProjectNameCollisionError = class extends Error {
|
|
@@ -8575,7 +8808,7 @@ function pruneTtlMs() {
|
|
|
8575
8808
|
}
|
|
8576
8809
|
async function statPathStatus(p) {
|
|
8577
8810
|
try {
|
|
8578
|
-
const stat = await
|
|
8811
|
+
const stat = await import_node_fs30.promises.stat(p);
|
|
8579
8812
|
return stat.isDirectory() ? "present" : "unknown";
|
|
8580
8813
|
} catch (err) {
|
|
8581
8814
|
return err.code === "ENOENT" ? "gone" : "unknown";
|
|
@@ -8672,54 +8905,368 @@ data: ${JSON.stringify(envelope.payload)}
|
|
|
8672
8905
|
|
|
8673
8906
|
// src/api.ts
|
|
8674
8907
|
init_auth();
|
|
8675
|
-
|
|
8676
|
-
|
|
8677
|
-
|
|
8678
|
-
|
|
8679
|
-
|
|
8680
|
-
|
|
8681
|
-
|
|
8682
|
-
|
|
8683
|
-
|
|
8684
|
-
|
|
8685
|
-
|
|
8686
|
-
|
|
8687
|
-
|
|
8688
|
-
|
|
8689
|
-
|
|
8690
|
-
return named === void 0 || named === DEFAULT_PROJECT ? singleProject : named;
|
|
8908
|
+
|
|
8909
|
+
// src/connectors-config.ts
|
|
8910
|
+
init_cjs_shims();
|
|
8911
|
+
var import_node_os4 = __toESM(require("os"), 1);
|
|
8912
|
+
var import_node_path48 = __toESM(require("path"), 1);
|
|
8913
|
+
var import_node_fs31 = require("fs");
|
|
8914
|
+
var CONNECTORS_CONFIG_VERSION = 1;
|
|
8915
|
+
var EnvRefUnsetError = class extends Error {
|
|
8916
|
+
ref;
|
|
8917
|
+
varName;
|
|
8918
|
+
constructor(ref, varName) {
|
|
8919
|
+
super(`${ref} is unset`);
|
|
8920
|
+
this.name = "EnvRefUnsetError";
|
|
8921
|
+
this.ref = ref;
|
|
8922
|
+
this.varName = varName;
|
|
8691
8923
|
}
|
|
8692
|
-
|
|
8924
|
+
};
|
|
8925
|
+
function neatHome2() {
|
|
8926
|
+
const override = process.env.NEAT_HOME;
|
|
8927
|
+
if (override && override.length > 0) return import_node_path48.default.resolve(override);
|
|
8928
|
+
return import_node_path48.default.join(import_node_os4.default.homedir(), ".neat");
|
|
8693
8929
|
}
|
|
8694
|
-
function
|
|
8695
|
-
|
|
8696
|
-
|
|
8697
|
-
|
|
8698
|
-
|
|
8699
|
-
|
|
8700
|
-
|
|
8701
|
-
|
|
8930
|
+
function connectorsConfigPath(home = neatHome2()) {
|
|
8931
|
+
return import_node_path48.default.join(home, "connectors.json");
|
|
8932
|
+
}
|
|
8933
|
+
var MODE_MASK_LOOSER_THAN_0600 = 63;
|
|
8934
|
+
async function warnIfModeLooserThan0600(file) {
|
|
8935
|
+
if (process.platform === "win32") return;
|
|
8936
|
+
try {
|
|
8937
|
+
const stat = await import_node_fs31.promises.stat(file);
|
|
8938
|
+
if ((stat.mode & MODE_MASK_LOOSER_THAN_0600) !== 0) {
|
|
8939
|
+
const mode = (stat.mode & 511).toString(8).padStart(3, "0");
|
|
8940
|
+
console.warn(
|
|
8941
|
+
`[neat] ${file} is mode 0${mode}, looser than the 0600 this file's secrets call for \u2014 run \`chmod 600 ${file}\``
|
|
8942
|
+
);
|
|
8702
8943
|
}
|
|
8703
|
-
|
|
8704
|
-
|
|
8705
|
-
|
|
8944
|
+
} catch {
|
|
8945
|
+
}
|
|
8946
|
+
}
|
|
8947
|
+
async function readConnectorsConfig(home = neatHome2()) {
|
|
8948
|
+
const file = connectorsConfigPath(home);
|
|
8949
|
+
let raw;
|
|
8950
|
+
try {
|
|
8951
|
+
raw = await import_node_fs31.promises.readFile(file, "utf8");
|
|
8952
|
+
} catch (err) {
|
|
8953
|
+
if (err.code === "ENOENT") {
|
|
8954
|
+
return { version: CONNECTORS_CONFIG_VERSION, connectors: [] };
|
|
8706
8955
|
}
|
|
8707
|
-
|
|
8708
|
-
return null;
|
|
8956
|
+
throw err;
|
|
8709
8957
|
}
|
|
8710
|
-
|
|
8958
|
+
await warnIfModeLooserThan0600(file);
|
|
8959
|
+
let parsed;
|
|
8960
|
+
try {
|
|
8961
|
+
parsed = JSON.parse(raw);
|
|
8962
|
+
} catch (err) {
|
|
8963
|
+
throw new Error(`${file} is not valid JSON: ${err.message}`);
|
|
8964
|
+
}
|
|
8965
|
+
return validateConfig(parsed, file);
|
|
8711
8966
|
}
|
|
8712
|
-
function
|
|
8713
|
-
if (
|
|
8714
|
-
|
|
8715
|
-
throw new Error("buildApi: either `projects` or `graph` must be provided");
|
|
8967
|
+
function validateConfig(parsed, file) {
|
|
8968
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
8969
|
+
throw new Error(`${file} must be a JSON object with a "connectors" array`);
|
|
8716
8970
|
}
|
|
8717
|
-
const
|
|
8718
|
-
const
|
|
8719
|
-
|
|
8720
|
-
|
|
8721
|
-
|
|
8722
|
-
|
|
8971
|
+
const obj = parsed;
|
|
8972
|
+
const version = obj.version === void 0 ? CONNECTORS_CONFIG_VERSION : obj.version;
|
|
8973
|
+
if (typeof version !== "number" || !Number.isInteger(version)) {
|
|
8974
|
+
throw new Error(`${file}: "version" must be an integer`);
|
|
8975
|
+
}
|
|
8976
|
+
const rawConnectors = obj.connectors;
|
|
8977
|
+
if (!Array.isArray(rawConnectors)) {
|
|
8978
|
+
throw new Error(`${file}: "connectors" must be an array`);
|
|
8979
|
+
}
|
|
8980
|
+
const connectors = rawConnectors.map((entry2, i) => validateEntry(entry2, i, file));
|
|
8981
|
+
return { version, connectors };
|
|
8982
|
+
}
|
|
8983
|
+
function validateEntry(entry2, index, file) {
|
|
8984
|
+
const where = `${file}: connectors[${index}]`;
|
|
8985
|
+
if (typeof entry2 !== "object" || entry2 === null || Array.isArray(entry2)) {
|
|
8986
|
+
throw new Error(`${where} must be an object`);
|
|
8987
|
+
}
|
|
8988
|
+
const e = entry2;
|
|
8989
|
+
const id = e.id;
|
|
8990
|
+
if (typeof id !== "string" || id.length === 0) {
|
|
8991
|
+
throw new Error(`${where}.id must be a non-empty string`);
|
|
8992
|
+
}
|
|
8993
|
+
const provider = e.provider;
|
|
8994
|
+
if (typeof provider !== "string" || provider.length === 0) {
|
|
8995
|
+
throw new Error(`${where}.provider must be a non-empty string`);
|
|
8996
|
+
}
|
|
8997
|
+
if (e.project !== void 0 && typeof e.project !== "string") {
|
|
8998
|
+
throw new Error(`${where}.project must be a string when present`);
|
|
8999
|
+
}
|
|
9000
|
+
const credential = validateCredentialRef(e.credential, `${where}.credential`);
|
|
9001
|
+
let options;
|
|
9002
|
+
if (e.options !== void 0) {
|
|
9003
|
+
if (typeof e.options !== "object" || e.options === null || Array.isArray(e.options)) {
|
|
9004
|
+
throw new Error(`${where}.options must be an object when present`);
|
|
9005
|
+
}
|
|
9006
|
+
options = e.options;
|
|
9007
|
+
}
|
|
9008
|
+
return {
|
|
9009
|
+
id,
|
|
9010
|
+
provider,
|
|
9011
|
+
...typeof e.project === "string" ? { project: e.project } : {},
|
|
9012
|
+
credential,
|
|
9013
|
+
...options ? { options } : {}
|
|
9014
|
+
};
|
|
9015
|
+
}
|
|
9016
|
+
function validateCredentialRef(raw, where) {
|
|
9017
|
+
if (typeof raw === "string") {
|
|
9018
|
+
if (raw.length === 0) throw new Error(`${where} must be a non-empty string`);
|
|
9019
|
+
return raw;
|
|
9020
|
+
}
|
|
9021
|
+
if (typeof raw === "object" && raw !== null && !Array.isArray(raw)) {
|
|
9022
|
+
const fields = raw;
|
|
9023
|
+
const keys = Object.keys(fields);
|
|
9024
|
+
if (keys.length === 0) throw new Error(`${where} object must have at least one field`);
|
|
9025
|
+
for (const key of keys) {
|
|
9026
|
+
const v = fields[key];
|
|
9027
|
+
if (typeof v !== "string" || v.length === 0) {
|
|
9028
|
+
throw new Error(`${where}.${key} must be a non-empty string`);
|
|
9029
|
+
}
|
|
9030
|
+
}
|
|
9031
|
+
return fields;
|
|
9032
|
+
}
|
|
9033
|
+
throw new Error(`${where} must be a string or an object of strings`);
|
|
9034
|
+
}
|
|
9035
|
+
function resolveCredential(ref, env = process.env) {
|
|
9036
|
+
if (typeof ref === "string") {
|
|
9037
|
+
return { kind: "single", value: resolveRef(ref, env) };
|
|
9038
|
+
}
|
|
9039
|
+
const fields = {};
|
|
9040
|
+
for (const [key, value] of Object.entries(ref)) {
|
|
9041
|
+
fields[key] = resolveRef(value, env);
|
|
9042
|
+
}
|
|
9043
|
+
return { kind: "fields", fields };
|
|
9044
|
+
}
|
|
9045
|
+
function resolveRef(value, env) {
|
|
9046
|
+
if (value.length > 1 && value.startsWith("$")) {
|
|
9047
|
+
const varName = value.slice(1);
|
|
9048
|
+
const resolved = env[varName];
|
|
9049
|
+
if (resolved === void 0 || resolved.length === 0) {
|
|
9050
|
+
throw new EnvRefUnsetError(value, varName);
|
|
9051
|
+
}
|
|
9052
|
+
return resolved;
|
|
9053
|
+
}
|
|
9054
|
+
return value;
|
|
9055
|
+
}
|
|
9056
|
+
function connectorMatchesProject(entry2, project) {
|
|
9057
|
+
return entry2.project === void 0 || entry2.project === project;
|
|
9058
|
+
}
|
|
9059
|
+
var CONNECTORS_LOCK_TIMEOUT_MS = 5e3;
|
|
9060
|
+
var CONNECTORS_LOCK_RETRY_MS = 50;
|
|
9061
|
+
function connectorsConfigLockPath(home = neatHome2()) {
|
|
9062
|
+
return import_node_path48.default.join(home, "connectors.json.lock");
|
|
9063
|
+
}
|
|
9064
|
+
function isEnvRef(value) {
|
|
9065
|
+
return value.length > 1 && value.startsWith("$");
|
|
9066
|
+
}
|
|
9067
|
+
function redactCredentialRef(ref) {
|
|
9068
|
+
const one = (value) => isEnvRef(value) ? value : "****";
|
|
9069
|
+
if (typeof ref === "string") return one(ref);
|
|
9070
|
+
const out = {};
|
|
9071
|
+
for (const [key, value] of Object.entries(ref)) out[key] = one(value);
|
|
9072
|
+
return out;
|
|
9073
|
+
}
|
|
9074
|
+
async function writeConfigAtomically0600(file, contents) {
|
|
9075
|
+
await import_node_fs31.promises.mkdir(import_node_path48.default.dirname(file), { recursive: true });
|
|
9076
|
+
const tmp = `${file}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
|
|
9077
|
+
const fd = await import_node_fs31.promises.open(tmp, "w", 384);
|
|
9078
|
+
try {
|
|
9079
|
+
await fd.writeFile(contents, "utf8");
|
|
9080
|
+
await fd.chmod(384);
|
|
9081
|
+
await fd.sync();
|
|
9082
|
+
} finally {
|
|
9083
|
+
await fd.close();
|
|
9084
|
+
}
|
|
9085
|
+
await import_node_fs31.promises.rename(tmp, file);
|
|
9086
|
+
}
|
|
9087
|
+
async function acquireConnectorsLock(lockPath, timeoutMs = CONNECTORS_LOCK_TIMEOUT_MS) {
|
|
9088
|
+
const deadline = Date.now() + timeoutMs;
|
|
9089
|
+
await import_node_fs31.promises.mkdir(import_node_path48.default.dirname(lockPath), { recursive: true });
|
|
9090
|
+
for (; ; ) {
|
|
9091
|
+
try {
|
|
9092
|
+
const fd = await import_node_fs31.promises.open(lockPath, "wx");
|
|
9093
|
+
try {
|
|
9094
|
+
await fd.writeFile(`${process.pid}
|
|
9095
|
+
`, "utf8");
|
|
9096
|
+
} finally {
|
|
9097
|
+
await fd.close();
|
|
9098
|
+
}
|
|
9099
|
+
return;
|
|
9100
|
+
} catch (err) {
|
|
9101
|
+
if (err.code !== "EEXIST") throw err;
|
|
9102
|
+
if (Date.now() >= deadline) {
|
|
9103
|
+
throw new Error(
|
|
9104
|
+
`neat connector: timed out after ${timeoutMs}ms waiting for ${lockPath}. Another neat process may be writing the connector config; if none is, remove that file by hand.`
|
|
9105
|
+
);
|
|
9106
|
+
}
|
|
9107
|
+
await new Promise((r) => setTimeout(r, CONNECTORS_LOCK_RETRY_MS));
|
|
9108
|
+
}
|
|
9109
|
+
}
|
|
9110
|
+
}
|
|
9111
|
+
async function releaseConnectorsLock(lockPath) {
|
|
9112
|
+
await import_node_fs31.promises.unlink(lockPath).catch(() => {
|
|
9113
|
+
});
|
|
9114
|
+
}
|
|
9115
|
+
async function withConnectorsLock(home, fn) {
|
|
9116
|
+
const lock = connectorsConfigLockPath(home);
|
|
9117
|
+
await acquireConnectorsLock(lock);
|
|
9118
|
+
try {
|
|
9119
|
+
return await fn();
|
|
9120
|
+
} finally {
|
|
9121
|
+
await releaseConnectorsLock(lock);
|
|
9122
|
+
}
|
|
9123
|
+
}
|
|
9124
|
+
function serializeConfig(config) {
|
|
9125
|
+
return JSON.stringify(
|
|
9126
|
+
{ version: config.version ?? CONNECTORS_CONFIG_VERSION, connectors: config.connectors },
|
|
9127
|
+
null,
|
|
9128
|
+
2
|
|
9129
|
+
) + "\n";
|
|
9130
|
+
}
|
|
9131
|
+
async function upsertConnectorEntry(entry2, home = neatHome2()) {
|
|
9132
|
+
const file = connectorsConfigPath(home);
|
|
9133
|
+
return withConnectorsLock(home, async () => {
|
|
9134
|
+
const config = await readConnectorsConfig(home);
|
|
9135
|
+
validateEntry(entry2, config.connectors.length, file);
|
|
9136
|
+
const idx = config.connectors.findIndex((c) => c.id === entry2.id);
|
|
9137
|
+
const replaced = idx >= 0;
|
|
9138
|
+
if (replaced) config.connectors[idx] = entry2;
|
|
9139
|
+
else config.connectors.push(entry2);
|
|
9140
|
+
await writeConfigAtomically0600(file, serializeConfig(config));
|
|
9141
|
+
return { replaced };
|
|
9142
|
+
});
|
|
9143
|
+
}
|
|
9144
|
+
async function removeConnectorEntry(id, home = neatHome2()) {
|
|
9145
|
+
const file = connectorsConfigPath(home);
|
|
9146
|
+
return withConnectorsLock(home, async () => {
|
|
9147
|
+
const config = await readConnectorsConfig(home);
|
|
9148
|
+
const idx = config.connectors.findIndex((c) => c.id === id);
|
|
9149
|
+
if (idx < 0) return void 0;
|
|
9150
|
+
const [removed] = config.connectors.splice(idx, 1);
|
|
9151
|
+
await writeConfigAtomically0600(file, serializeConfig(config));
|
|
9152
|
+
return removed;
|
|
9153
|
+
});
|
|
9154
|
+
}
|
|
9155
|
+
function slugFragment(value) {
|
|
9156
|
+
return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
9157
|
+
}
|
|
9158
|
+
function autoSlugConnectorId(provider, project, existingIds) {
|
|
9159
|
+
const base = slugFragment(provider) || "connector";
|
|
9160
|
+
if (!existingIds.has(base)) return base;
|
|
9161
|
+
const projectFrag = project ? slugFragment(project) : "";
|
|
9162
|
+
if (projectFrag) {
|
|
9163
|
+
const withProject = `${base}-${projectFrag}`;
|
|
9164
|
+
if (!existingIds.has(withProject)) return withProject;
|
|
9165
|
+
}
|
|
9166
|
+
const stem = projectFrag ? `${base}-${projectFrag}` : base;
|
|
9167
|
+
for (let n = 2; ; n++) {
|
|
9168
|
+
const candidate = `${stem}-${n}`;
|
|
9169
|
+
if (!existingIds.has(candidate)) return candidate;
|
|
9170
|
+
}
|
|
9171
|
+
}
|
|
9172
|
+
function describeCredential(ref, env = process.env) {
|
|
9173
|
+
const one = (value, field) => {
|
|
9174
|
+
if (isEnvRef(value)) {
|
|
9175
|
+
const varName = value.slice(1);
|
|
9176
|
+
const resolved = env[varName];
|
|
9177
|
+
const set = typeof resolved === "string" && resolved.length > 0;
|
|
9178
|
+
return {
|
|
9179
|
+
...field ? { field } : {},
|
|
9180
|
+
kind: "env-ref",
|
|
9181
|
+
display: value,
|
|
9182
|
+
status: set ? "set" : "unset"
|
|
9183
|
+
};
|
|
9184
|
+
}
|
|
9185
|
+
return { ...field ? { field } : {}, kind: "plaintext", display: "****" };
|
|
9186
|
+
};
|
|
9187
|
+
if (typeof ref === "string") return [one(ref)];
|
|
9188
|
+
return Object.entries(ref).map(([k, v]) => one(v, k));
|
|
9189
|
+
}
|
|
9190
|
+
|
|
9191
|
+
// src/connectors/status.ts
|
|
9192
|
+
init_cjs_shims();
|
|
9193
|
+
var CONNECTOR_STALE_THRESHOLD_MS = 5 * 6e4;
|
|
9194
|
+
var records = /* @__PURE__ */ new Map();
|
|
9195
|
+
function deriveState(rec, now, thresholdMs) {
|
|
9196
|
+
if (rec.lastOutcome === "error") return "error";
|
|
9197
|
+
const okAt = rec.lastOkAt ? Date.parse(rec.lastOkAt) : NaN;
|
|
9198
|
+
if (!Number.isNaN(okAt) && now - okAt > thresholdMs) return "stale";
|
|
9199
|
+
return "healthy";
|
|
9200
|
+
}
|
|
9201
|
+
function getConnectorStatus(id, now = Date.now(), thresholdMs = CONNECTOR_STALE_THRESHOLD_MS) {
|
|
9202
|
+
const rec = records.get(id);
|
|
9203
|
+
if (!rec) {
|
|
9204
|
+
return {
|
|
9205
|
+
state: "idle",
|
|
9206
|
+
lastPollAt: null,
|
|
9207
|
+
lastOutcome: null,
|
|
9208
|
+
lastError: null,
|
|
9209
|
+
signalsLastPoll: 0
|
|
9210
|
+
};
|
|
9211
|
+
}
|
|
9212
|
+
return {
|
|
9213
|
+
state: deriveState(rec, now, thresholdMs),
|
|
9214
|
+
lastPollAt: rec.lastPollAt,
|
|
9215
|
+
lastOutcome: rec.lastOutcome,
|
|
9216
|
+
lastError: rec.lastError,
|
|
9217
|
+
signalsLastPoll: rec.signalsLastPoll
|
|
9218
|
+
};
|
|
9219
|
+
}
|
|
9220
|
+
|
|
9221
|
+
// src/api.ts
|
|
9222
|
+
function serializeGraph(graph) {
|
|
9223
|
+
const nodes = [];
|
|
9224
|
+
graph.forEachNode((_id, attrs) => {
|
|
9225
|
+
nodes.push(attrs);
|
|
9226
|
+
});
|
|
9227
|
+
const edges = [];
|
|
9228
|
+
graph.forEachEdge((_id, attrs) => {
|
|
9229
|
+
edges.push(attrs);
|
|
9230
|
+
});
|
|
9231
|
+
return { nodes, edges };
|
|
9232
|
+
}
|
|
9233
|
+
function projectFromReq(req, singleProject) {
|
|
9234
|
+
const params = req.params;
|
|
9235
|
+
const named = params.project;
|
|
9236
|
+
if (singleProject) {
|
|
9237
|
+
return named === void 0 || named === DEFAULT_PROJECT ? singleProject : named;
|
|
9238
|
+
}
|
|
9239
|
+
return named ?? DEFAULT_PROJECT;
|
|
9240
|
+
}
|
|
9241
|
+
function resolveProject(registry, req, reply, bootstrap, singleProject) {
|
|
9242
|
+
const name = projectFromReq(req, singleProject);
|
|
9243
|
+
const ctx = registry.get(name);
|
|
9244
|
+
if (!ctx) {
|
|
9245
|
+
const phase = bootstrap?.status(name);
|
|
9246
|
+
if (phase === "bootstrapping") {
|
|
9247
|
+
void reply.code(503).send({ ready: false, project: name, status: "bootstrapping" });
|
|
9248
|
+
return null;
|
|
9249
|
+
}
|
|
9250
|
+
if (phase === "broken") {
|
|
9251
|
+
void reply.code(503).send({ ready: false, project: name, status: "broken" });
|
|
9252
|
+
return null;
|
|
9253
|
+
}
|
|
9254
|
+
void reply.code(404).send({ error: "project not found", project: name });
|
|
9255
|
+
return null;
|
|
9256
|
+
}
|
|
9257
|
+
return ctx;
|
|
9258
|
+
}
|
|
9259
|
+
function buildLegacyRegistry(opts) {
|
|
9260
|
+
if (opts.projects) return opts.projects;
|
|
9261
|
+
if (!opts.graph) {
|
|
9262
|
+
throw new Error("buildApi: either `projects` or `graph` must be provided");
|
|
9263
|
+
}
|
|
9264
|
+
const registry = new Projects();
|
|
9265
|
+
const paths = pathsForProject(DEFAULT_PROJECT, "");
|
|
9266
|
+
registry.set(DEFAULT_PROJECT, {
|
|
9267
|
+
graph: opts.graph,
|
|
9268
|
+
scanPath: opts.scanPath,
|
|
9269
|
+
paths: {
|
|
8723
9270
|
snapshotPath: paths.snapshotPath,
|
|
8724
9271
|
errorsPath: opts.errorsPath ?? paths.errorsPath,
|
|
8725
9272
|
staleEventsPath: opts.staleEventsPath ?? paths.staleEventsPath,
|
|
@@ -8822,11 +9369,11 @@ function registerRoutes(scope, ctx) {
|
|
|
8822
9369
|
const candidates = req.query.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
8823
9370
|
const parsed = [];
|
|
8824
9371
|
for (const c of candidates) {
|
|
8825
|
-
const r =
|
|
9372
|
+
const r = import_types34.DivergenceTypeSchema.safeParse(c);
|
|
8826
9373
|
if (!r.success) {
|
|
8827
9374
|
return reply.code(400).send({
|
|
8828
9375
|
error: `unknown divergence type "${c}"`,
|
|
8829
|
-
allowed:
|
|
9376
|
+
allowed: import_types34.DivergenceTypeSchema.options
|
|
8830
9377
|
});
|
|
8831
9378
|
}
|
|
8832
9379
|
parsed.push(r.data);
|
|
@@ -8891,6 +9438,26 @@ function registerRoutes(scope, ctx) {
|
|
|
8891
9438
|
const sliced = filtered.slice(0, safeLimit);
|
|
8892
9439
|
return { count: sliced.length, total, logs: sliced };
|
|
8893
9440
|
});
|
|
9441
|
+
scope.get("/connectors", async (req, reply) => {
|
|
9442
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
|
|
9443
|
+
if (!proj) return;
|
|
9444
|
+
let entries;
|
|
9445
|
+
try {
|
|
9446
|
+
entries = (await readConnectorsConfig(ctx.connectorsHome)).connectors;
|
|
9447
|
+
} catch (err) {
|
|
9448
|
+
return reply.code(500).send({
|
|
9449
|
+
error: "connectors.json failed to read",
|
|
9450
|
+
details: err.message
|
|
9451
|
+
});
|
|
9452
|
+
}
|
|
9453
|
+
const connectors = entries.filter((entry2) => connectorMatchesProject(entry2, proj.name)).map((entry2) => ({
|
|
9454
|
+
id: entry2.id,
|
|
9455
|
+
provider: entry2.provider,
|
|
9456
|
+
credentialRef: redactCredentialRef(entry2.credential),
|
|
9457
|
+
status: getConnectorStatus(entry2.id)
|
|
9458
|
+
}));
|
|
9459
|
+
return { connectors };
|
|
9460
|
+
});
|
|
8894
9461
|
const incidentHistoryHandler = async (req, reply) => {
|
|
8895
9462
|
const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
|
|
8896
9463
|
if (!proj) return;
|
|
@@ -9077,7 +9644,7 @@ function registerRoutes(scope, ctx) {
|
|
|
9077
9644
|
const log = new PolicyViolationsLog(proj.paths.policyViolationsPath);
|
|
9078
9645
|
let violations = await log.readAll();
|
|
9079
9646
|
if (req.query.severity) {
|
|
9080
|
-
const sev =
|
|
9647
|
+
const sev = import_types34.PolicySeveritySchema.safeParse(req.query.severity);
|
|
9081
9648
|
if (!sev.success) {
|
|
9082
9649
|
return reply.code(400).send({
|
|
9083
9650
|
error: "invalid severity",
|
|
@@ -9116,7 +9683,7 @@ function registerRoutes(scope, ctx) {
|
|
|
9116
9683
|
scope.post("/policies/check", async (req, reply) => {
|
|
9117
9684
|
const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
|
|
9118
9685
|
if (!proj) return;
|
|
9119
|
-
const parsed =
|
|
9686
|
+
const parsed = import_types34.PoliciesCheckBodySchema.safeParse(req.body ?? {});
|
|
9120
9687
|
if (!parsed.success) {
|
|
9121
9688
|
return reply.code(400).send({
|
|
9122
9689
|
error: "invalid /policies/check body",
|
|
@@ -9293,7 +9860,8 @@ async function buildApi(opts) {
|
|
|
9293
9860
|
staleEventsPathFor,
|
|
9294
9861
|
policyFilePathFor,
|
|
9295
9862
|
bootstrap: opts.bootstrap,
|
|
9296
|
-
singleProject: opts.singleProject?.name
|
|
9863
|
+
singleProject: opts.singleProject?.name,
|
|
9864
|
+
connectorsHome: opts.connectorsHome
|
|
9297
9865
|
};
|
|
9298
9866
|
app.get("/health", async () => {
|
|
9299
9867
|
const uptimeMs = Date.now() - startedAt;
|
|
@@ -9378,8 +9946,8 @@ init_otel();
|
|
|
9378
9946
|
|
|
9379
9947
|
// src/daemon.ts
|
|
9380
9948
|
init_cjs_shims();
|
|
9381
|
-
var
|
|
9382
|
-
var
|
|
9949
|
+
var import_node_fs33 = require("fs");
|
|
9950
|
+
var import_node_path52 = __toESM(require("path"), 1);
|
|
9383
9951
|
var import_node_module = require("module");
|
|
9384
9952
|
init_otel();
|
|
9385
9953
|
|
|
@@ -9673,6 +10241,30 @@ function buildEdgeLogsQuery(limit) {
|
|
|
9673
10241
|
`limit ${safeLimit}`
|
|
9674
10242
|
].join("\n");
|
|
9675
10243
|
}
|
|
10244
|
+
function logsAllHttpFailureMessage(status2) {
|
|
10245
|
+
if (status2 === 401 || status2 === 403) {
|
|
10246
|
+
return `supabase connector: logs.all request rejected (HTTP ${status2}). Check the Management API token, its analytics read scope, and --api-project-ref.`;
|
|
10247
|
+
}
|
|
10248
|
+
if (status2 === 404) {
|
|
10249
|
+
return "supabase connector: logs.all project not found (HTTP 404). Check --api-project-ref.";
|
|
10250
|
+
}
|
|
10251
|
+
if (status2 === 429) {
|
|
10252
|
+
return "supabase connector: logs.all rate-limited (HTTP 429). The connector will retry on the next poll; reduce poll cadence if this persists.";
|
|
10253
|
+
}
|
|
10254
|
+
if (status2 === 400) {
|
|
10255
|
+
return "supabase connector: logs.all query rejected (HTTP 400). Provider details redacted; confirm the live log-query dialect before shipping this connector.";
|
|
10256
|
+
}
|
|
10257
|
+
return `supabase connector: logs.all request failed (HTTP ${status2}); provider details redacted.`;
|
|
10258
|
+
}
|
|
10259
|
+
function providerErrorDetails(error) {
|
|
10260
|
+
if (!error || typeof error === "string") return "";
|
|
10261
|
+
const parts = [];
|
|
10262
|
+
if (typeof error.code === "number") parts.push(`code ${error.code}`);
|
|
10263
|
+
if (typeof error.status === "string" && /^[A-Z0-9_.-]+$/.test(error.status)) {
|
|
10264
|
+
parts.push(`status ${error.status}`);
|
|
10265
|
+
}
|
|
10266
|
+
return parts.length > 0 ? ` (${parts.join(", ")})` : "";
|
|
10267
|
+
}
|
|
9676
10268
|
async function fetchSupabaseEdgeLogs(config, token, startIso, endIso, fetchImpl = fetch) {
|
|
9677
10269
|
const baseUrl = config.managementApiUrl ?? DEFAULT_SUPABASE_MANAGEMENT_API_URL;
|
|
9678
10270
|
const url = new URL(`${baseUrl}/v1/projects/${config.apiProjectRef}/analytics/endpoints/logs.all`);
|
|
@@ -9687,12 +10279,18 @@ async function fetchSupabaseEdgeLogs(config, token, startIso, endIso, fetchImpl
|
|
|
9687
10279
|
{ provider: "supabase", accountKey: config.apiProjectRef, fetchImpl }
|
|
9688
10280
|
);
|
|
9689
10281
|
if (!res.ok) {
|
|
9690
|
-
throw new Error(
|
|
10282
|
+
throw new Error(logsAllHttpFailureMessage(res.status));
|
|
10283
|
+
}
|
|
10284
|
+
let body;
|
|
10285
|
+
try {
|
|
10286
|
+
body = await res.json();
|
|
10287
|
+
} catch {
|
|
10288
|
+
throw new Error("supabase connector: logs.all returned invalid JSON; provider details redacted.");
|
|
9691
10289
|
}
|
|
9692
|
-
const body = await res.json();
|
|
9693
10290
|
if (body.error) {
|
|
9694
|
-
|
|
9695
|
-
|
|
10291
|
+
throw new Error(
|
|
10292
|
+
`supabase connector: logs.all returned a provider error${providerErrorDetails(body.error)}; provider message redacted.`
|
|
10293
|
+
);
|
|
9696
10294
|
}
|
|
9697
10295
|
return body.result ?? [];
|
|
9698
10296
|
}
|
|
@@ -9724,10 +10322,10 @@ var SUPABASE_RPC_TARGET_KIND = "supabase-rpc";
|
|
|
9724
10322
|
// src/connectors/supabase/map.ts
|
|
9725
10323
|
var REST_RPC_PATH_RE = /^\/rest\/v1\/rpc\/([^/?]+)/;
|
|
9726
10324
|
var REST_TABLE_PATH_RE = /^\/rest\/v1\/([^/?]+)/;
|
|
9727
|
-
function targetFromRestPath(
|
|
9728
|
-
const rpcMatch = REST_RPC_PATH_RE.exec(
|
|
10325
|
+
function targetFromRestPath(path61) {
|
|
10326
|
+
const rpcMatch = REST_RPC_PATH_RE.exec(path61);
|
|
9729
10327
|
if (rpcMatch) return { targetKind: SUPABASE_RPC_TARGET_KIND, name: rpcMatch[1] };
|
|
9730
|
-
const tableMatch = REST_TABLE_PATH_RE.exec(
|
|
10328
|
+
const tableMatch = REST_TABLE_PATH_RE.exec(path61);
|
|
9731
10329
|
if (tableMatch) return { targetKind: SUPABASE_TABLE_TARGET_KIND, name: tableMatch[1] };
|
|
9732
10330
|
return null;
|
|
9733
10331
|
}
|
|
@@ -9830,19 +10428,19 @@ async function fetchPgStatStatements(connectionString, limit = DEFAULT_STATEMENT
|
|
|
9830
10428
|
|
|
9831
10429
|
// src/connectors/supabase/resolve.ts
|
|
9832
10430
|
init_cjs_shims();
|
|
9833
|
-
var
|
|
10431
|
+
var import_types36 = require("@neat.is/types");
|
|
9834
10432
|
function createSupabaseResolveTarget(graph, config) {
|
|
9835
10433
|
return (signal, _ctx) => {
|
|
9836
10434
|
if (signal.targetKind !== SUPABASE_TABLE_TARGET_KIND && signal.targetKind !== SUPABASE_RPC_TARGET_KIND) {
|
|
9837
10435
|
return null;
|
|
9838
10436
|
}
|
|
9839
|
-
const subResourceId = (0,
|
|
10437
|
+
const subResourceId = (0, import_types36.infraId)(signal.targetKind, `${config.nodeRef}/${signal.targetName}`);
|
|
9840
10438
|
if (graph.hasNode(subResourceId)) {
|
|
9841
|
-
return { targetNodeId: subResourceId, serviceName: config.serviceName, edgeType:
|
|
10439
|
+
return { targetNodeId: subResourceId, serviceName: config.serviceName, edgeType: import_types36.EdgeType.CALLS };
|
|
9842
10440
|
}
|
|
9843
|
-
const projectLevelId = (0,
|
|
10441
|
+
const projectLevelId = (0, import_types36.infraId)("supabase", config.nodeRef);
|
|
9844
10442
|
if (graph.hasNode(projectLevelId)) {
|
|
9845
|
-
return { targetNodeId: projectLevelId, serviceName: config.serviceName, edgeType:
|
|
10443
|
+
return { targetNodeId: projectLevelId, serviceName: config.serviceName, edgeType: import_types36.EdgeType.CALLS };
|
|
9846
10444
|
}
|
|
9847
10445
|
return null;
|
|
9848
10446
|
};
|
|
@@ -9850,6 +10448,35 @@ function createSupabaseResolveTarget(graph, config) {
|
|
|
9850
10448
|
|
|
9851
10449
|
// src/connectors/supabase/index.ts
|
|
9852
10450
|
var DEFAULT_MAX_LOOKBACK_MS = 24 * 60 * 60 * 1e3;
|
|
10451
|
+
function errorCode(err) {
|
|
10452
|
+
const code = err?.code;
|
|
10453
|
+
return typeof code === "string" && code.length > 0 ? code : void 0;
|
|
10454
|
+
}
|
|
10455
|
+
function describeSupabasePostgresSurfaceFailure(projectRef, err) {
|
|
10456
|
+
const code = errorCode(err);
|
|
10457
|
+
let reason;
|
|
10458
|
+
switch (code) {
|
|
10459
|
+
case "42501":
|
|
10460
|
+
reason = "permission denied; grant pg_read_all_stats to the configured Postgres role";
|
|
10461
|
+
break;
|
|
10462
|
+
case "42P01":
|
|
10463
|
+
case "42704":
|
|
10464
|
+
reason = "pg_stat_statements is not enabled or visible to the configured Postgres role";
|
|
10465
|
+
break;
|
|
10466
|
+
case "28P01":
|
|
10467
|
+
case "28000":
|
|
10468
|
+
reason = "Postgres credential rejected";
|
|
10469
|
+
break;
|
|
10470
|
+
case "3D000":
|
|
10471
|
+
reason = "database not found";
|
|
10472
|
+
break;
|
|
10473
|
+
default: {
|
|
10474
|
+
const name = err instanceof Error && err.name ? err.name : "Error";
|
|
10475
|
+
reason = code ? `${name} ${code}` : name;
|
|
10476
|
+
}
|
|
10477
|
+
}
|
|
10478
|
+
return `supabase connector: pg_stat_statements surface unavailable for project ${projectRef} (${reason}); continuing with Management API log surface.`;
|
|
10479
|
+
}
|
|
9853
10480
|
var SupabaseConnector = class {
|
|
9854
10481
|
// `deps.fetchPgStatStatements` defaults to the real Postgres-backed
|
|
9855
10482
|
// implementation; tests override it to exercise the "both surfaces
|
|
@@ -9881,12 +10508,18 @@ var SupabaseConnector = class {
|
|
|
9881
10508
|
const signals = mapEdgeLogRowsToSignals(logRows);
|
|
9882
10509
|
if (creds.postgresConnectionString) {
|
|
9883
10510
|
const fetchStatements = this.deps.fetchPgStatStatements ?? fetchPgStatStatements;
|
|
9884
|
-
|
|
9885
|
-
|
|
9886
|
-
|
|
9887
|
-
|
|
9888
|
-
|
|
9889
|
-
|
|
10511
|
+
try {
|
|
10512
|
+
const statementRows = await fetchStatements(
|
|
10513
|
+
creds.postgresConnectionString,
|
|
10514
|
+
this.config.statementLimit ?? DEFAULT_STATEMENT_LIMIT,
|
|
10515
|
+
this.config.apiProjectRef
|
|
10516
|
+
);
|
|
10517
|
+
signals.push(...diffPgStatStatementsToSignals(statementRows, this.statementBaselines, now.toISOString()));
|
|
10518
|
+
} catch (err) {
|
|
10519
|
+
const summary = describeSupabasePostgresSurfaceFailure(this.config.apiProjectRef, err);
|
|
10520
|
+
if (this.deps.onPostgresSurfaceError) this.deps.onPostgresSurfaceError(err, summary);
|
|
10521
|
+
else console.warn(summary);
|
|
10522
|
+
}
|
|
9890
10523
|
}
|
|
9891
10524
|
return signals;
|
|
9892
10525
|
}
|
|
@@ -9900,7 +10533,7 @@ function createSupabaseConnector(graph, config, deps = {}) {
|
|
|
9900
10533
|
|
|
9901
10534
|
// src/connectors/railway/index.ts
|
|
9902
10535
|
init_cjs_shims();
|
|
9903
|
-
var
|
|
10536
|
+
var import_types40 = require("@neat.is/types");
|
|
9904
10537
|
|
|
9905
10538
|
// src/connectors/railway/client.ts
|
|
9906
10539
|
init_cjs_shims();
|
|
@@ -10045,7 +10678,7 @@ function buildRailwayRouteIndex(graph, serviceName) {
|
|
|
10045
10678
|
const out = [];
|
|
10046
10679
|
graph.forEachNode((_id, attrs) => {
|
|
10047
10680
|
const node = attrs;
|
|
10048
|
-
if (node.type !==
|
|
10681
|
+
if (node.type !== import_types40.NodeType.RouteNode) return;
|
|
10049
10682
|
const route = attrs;
|
|
10050
10683
|
if (route.service !== serviceName) return;
|
|
10051
10684
|
out.push({
|
|
@@ -10141,12 +10774,12 @@ function createRailwayResolveTarget(config) {
|
|
|
10141
10774
|
const serviceName = config.serviceNameById[config.serviceId];
|
|
10142
10775
|
if (!serviceName) return null;
|
|
10143
10776
|
if (signal.targetKind === ROUTE_TARGET_KIND) {
|
|
10144
|
-
return { targetNodeId: signal.targetName, serviceName, edgeType:
|
|
10777
|
+
return { targetNodeId: signal.targetName, serviceName, edgeType: import_types40.EdgeType.CALLS };
|
|
10145
10778
|
}
|
|
10146
10779
|
if (signal.targetKind === PEER_SERVICE_TARGET_KIND) {
|
|
10147
10780
|
const peerName = config.serviceNameById[signal.targetName];
|
|
10148
10781
|
if (!peerName) return null;
|
|
10149
|
-
return { targetNodeId: (0,
|
|
10782
|
+
return { targetNodeId: (0, import_types40.serviceId)(peerName), serviceName, edgeType: import_types40.EdgeType.CONNECTS_TO };
|
|
10150
10783
|
}
|
|
10151
10784
|
return null;
|
|
10152
10785
|
};
|
|
@@ -10261,9 +10894,9 @@ function parseFirebaseTargetName(targetName) {
|
|
|
10261
10894
|
const secondSep = rest.indexOf(FIELD_SEP);
|
|
10262
10895
|
if (secondSep === -1) return null;
|
|
10263
10896
|
const method = rest.slice(0, secondSep);
|
|
10264
|
-
const
|
|
10265
|
-
if (!resourceName || !method || !
|
|
10266
|
-
return { resourceName, method, path:
|
|
10897
|
+
const path61 = rest.slice(secondSep + 1);
|
|
10898
|
+
if (!resourceName || !method || !path61) return null;
|
|
10899
|
+
return { resourceName, method, path: path61 };
|
|
10267
10900
|
}
|
|
10268
10901
|
function resourceNameFor(type, labels) {
|
|
10269
10902
|
if (!labels) return null;
|
|
@@ -10300,14 +10933,14 @@ function mapLogEntryToSignal(entry2) {
|
|
|
10300
10933
|
if (!req) return null;
|
|
10301
10934
|
if (!req.requestMethod) return null;
|
|
10302
10935
|
const method = req.requestMethod.toUpperCase();
|
|
10303
|
-
const
|
|
10304
|
-
if (
|
|
10936
|
+
const path61 = pathFromRequestUrl(req.requestUrl);
|
|
10937
|
+
if (path61 === null) return null;
|
|
10305
10938
|
const timestamp = entry2.timestamp;
|
|
10306
10939
|
if (!timestamp) return null;
|
|
10307
10940
|
const isError = typeof req.status === "number" && req.status >= ERROR_STATUS_THRESHOLD2;
|
|
10308
10941
|
return {
|
|
10309
10942
|
targetKind: resourceType,
|
|
10310
|
-
targetName: packFirebaseTargetName({ resourceName, method, path:
|
|
10943
|
+
targetName: packFirebaseTargetName({ resourceName, method, path: path61 }),
|
|
10311
10944
|
callCount: 1,
|
|
10312
10945
|
errorCount: isError ? 1 : 0,
|
|
10313
10946
|
lastObservedIso: timestamp
|
|
@@ -10324,7 +10957,7 @@ function mapLogEntriesToSignals(entries) {
|
|
|
10324
10957
|
|
|
10325
10958
|
// src/connectors/firebase/resolve.ts
|
|
10326
10959
|
init_cjs_shims();
|
|
10327
|
-
var
|
|
10960
|
+
var import_types41 = require("@neat.is/types");
|
|
10328
10961
|
function neatServiceNameFor(resourceType, resourceName, serviceMap) {
|
|
10329
10962
|
switch (resourceType) {
|
|
10330
10963
|
case "cloud_function":
|
|
@@ -10339,7 +10972,7 @@ function routeEntriesFor(graph, serviceName) {
|
|
|
10339
10972
|
const entries = [];
|
|
10340
10973
|
graph.forEachNode((_id, attrs) => {
|
|
10341
10974
|
const node = attrs;
|
|
10342
|
-
if (node.type !==
|
|
10975
|
+
if (node.type !== import_types41.NodeType.RouteNode) return;
|
|
10343
10976
|
const route = attrs;
|
|
10344
10977
|
if (route.service !== serviceName) return;
|
|
10345
10978
|
entries.push({
|
|
@@ -10371,7 +11004,7 @@ function createFirebaseResolveTarget(graph, serviceMap) {
|
|
|
10371
11004
|
return {
|
|
10372
11005
|
targetNodeId: match.routeNodeId,
|
|
10373
11006
|
serviceName,
|
|
10374
|
-
edgeType:
|
|
11007
|
+
edgeType: import_types41.EdgeType.CALLS
|
|
10375
11008
|
};
|
|
10376
11009
|
};
|
|
10377
11010
|
}
|
|
@@ -10398,7 +11031,7 @@ init_cjs_shims();
|
|
|
10398
11031
|
|
|
10399
11032
|
// src/connectors/cloudflare/connector.ts
|
|
10400
11033
|
init_cjs_shims();
|
|
10401
|
-
var
|
|
11034
|
+
var import_types43 = require("@neat.is/types");
|
|
10402
11035
|
|
|
10403
11036
|
// src/connectors/cloudflare/client.ts
|
|
10404
11037
|
init_cjs_shims();
|
|
@@ -10489,396 +11122,124 @@ function parseHttpMethodFromTrigger(trigger) {
|
|
|
10489
11122
|
function parsePathFromTrigger(trigger) {
|
|
10490
11123
|
const trimmed = trigger.trim();
|
|
10491
11124
|
const spaceIdx = trimmed.indexOf(" ");
|
|
10492
|
-
if (spaceIdx === -1) return void 0;
|
|
10493
|
-
const rest = trimmed.slice(spaceIdx + 1).trim();
|
|
10494
|
-
return rest.length > 0 ? rest : void 0;
|
|
10495
|
-
}
|
|
10496
|
-
var ERROR_STATUS_THRESHOLD3 = 500;
|
|
10497
|
-
function mapEventToSignal(event) {
|
|
10498
|
-
const metadata = event.$metadata;
|
|
10499
|
-
const workers = event.$workers;
|
|
10500
|
-
const method = parseHttpMethodFromTrigger(metadata?.trigger);
|
|
10501
|
-
if (!method) return null;
|
|
10502
|
-
const scriptName = workers?.scriptName ?? metadata?.service;
|
|
10503
|
-
if (!scriptName) return null;
|
|
10504
|
-
const timestampMs = event.timestamp ?? metadata?.startTime;
|
|
10505
|
-
if (typeof timestampMs !== "number" || !Number.isFinite(timestampMs)) return null;
|
|
10506
|
-
const statusCode = metadata?.statusCode;
|
|
10507
|
-
const isError = typeof statusCode === "number" && statusCode >= ERROR_STATUS_THRESHOLD3;
|
|
10508
|
-
const
|
|
10509
|
-
return {
|
|
10510
|
-
targetKind: CLOUDFLARE_TARGET_KIND,
|
|
10511
|
-
targetName: scriptName,
|
|
10512
|
-
callCount: 1,
|
|
10513
|
-
errorCount: isError ? 1 : 0,
|
|
10514
|
-
lastObservedIso: new Date(timestampMs).toISOString(),
|
|
10515
|
-
method,
|
|
10516
|
-
...path58 ? { path: path58 } : {},
|
|
10517
|
-
...typeof statusCode === "number" ? { statusCode } : {},
|
|
10518
|
-
...typeof metadata?.duration === "number" ? { duration: metadata.duration } : {}
|
|
10519
|
-
};
|
|
10520
|
-
}
|
|
10521
|
-
|
|
10522
|
-
// src/connectors/cloudflare/connector.ts
|
|
10523
|
-
var DEFAULT_MAX_LOOKBACK_MS3 = 60 * 60 * 1e3;
|
|
10524
|
-
function resolveFromMs(since, maxLookbackMs) {
|
|
10525
|
-
const cap = maxLookbackMs ?? DEFAULT_MAX_LOOKBACK_MS3;
|
|
10526
|
-
const now = Date.now();
|
|
10527
|
-
const floor = now - cap;
|
|
10528
|
-
if (!since) return floor;
|
|
10529
|
-
const parsed = Date.parse(since);
|
|
10530
|
-
if (Number.isNaN(parsed)) return floor;
|
|
10531
|
-
return Math.max(parsed, floor);
|
|
10532
|
-
}
|
|
10533
|
-
var CloudflareConnector = class {
|
|
10534
|
-
constructor(config) {
|
|
10535
|
-
this.config = config;
|
|
10536
|
-
}
|
|
10537
|
-
config;
|
|
10538
|
-
provider = "cloudflare";
|
|
10539
|
-
async poll(ctx) {
|
|
10540
|
-
const toMs = Date.now();
|
|
10541
|
-
const fromMs = resolveFromMs(ctx.since, this.config.maxLookbackMs);
|
|
10542
|
-
const events = await queryWorkerInvocations(ctx, this.config, { fromMs, toMs });
|
|
10543
|
-
const signals = [];
|
|
10544
|
-
for (const event of events) {
|
|
10545
|
-
const signal = mapEventToSignal(event);
|
|
10546
|
-
if (signal) signals.push(signal);
|
|
10547
|
-
}
|
|
10548
|
-
return signals;
|
|
10549
|
-
}
|
|
10550
|
-
};
|
|
10551
|
-
function findTaggedWorkerFileNode(graph, workerName) {
|
|
10552
|
-
let found = null;
|
|
10553
|
-
graph.forEachNode((id, attrs) => {
|
|
10554
|
-
if (found) return;
|
|
10555
|
-
const a = attrs;
|
|
10556
|
-
if (a.type === import_types40.NodeType.FileNode && a.platform === "cloudflare" && a.platformName === workerName) {
|
|
10557
|
-
found = id;
|
|
10558
|
-
}
|
|
10559
|
-
});
|
|
10560
|
-
return found;
|
|
10561
|
-
}
|
|
10562
|
-
function findMatchingRouteNode(graph, serviceName, method, path58) {
|
|
10563
|
-
const normalizedPath = normalizePathTemplate(path58);
|
|
10564
|
-
let found = null;
|
|
10565
|
-
graph.forEachNode((id, attrs) => {
|
|
10566
|
-
if (found) return;
|
|
10567
|
-
const a = attrs;
|
|
10568
|
-
if (a.type !== import_types40.NodeType.RouteNode || a.service !== serviceName) return;
|
|
10569
|
-
if (!a.pathTemplate || normalizePathTemplate(a.pathTemplate) !== normalizedPath) return;
|
|
10570
|
-
const routeMethod = (a.method ?? "").toUpperCase();
|
|
10571
|
-
if (routeMethod !== "ALL" && routeMethod !== method) return;
|
|
10572
|
-
found = id;
|
|
10573
|
-
});
|
|
10574
|
-
return found;
|
|
10575
|
-
}
|
|
10576
|
-
function createCloudflareResolveTarget(config, graph) {
|
|
10577
|
-
return (signal) => {
|
|
10578
|
-
if (signal.targetKind !== CLOUDFLARE_TARGET_KIND) return null;
|
|
10579
|
-
const scriptName = signal.targetName;
|
|
10580
|
-
const { method, path: path58 } = signal;
|
|
10581
|
-
const resolveRouteGrain = (serviceName, wholeFileId) => {
|
|
10582
|
-
if (!method || !path58) return wholeFileId;
|
|
10583
|
-
return findMatchingRouteNode(graph, serviceName, method, path58) ?? wholeFileId;
|
|
10584
|
-
};
|
|
10585
|
-
const mapping = config.workers?.[scriptName];
|
|
10586
|
-
if (mapping) {
|
|
10587
|
-
const wholeFileId = (0, import_types40.fileId)(mapping.service, mapping.entryFile);
|
|
10588
|
-
return {
|
|
10589
|
-
targetNodeId: resolveRouteGrain(mapping.service, wholeFileId),
|
|
10590
|
-
serviceName: mapping.service,
|
|
10591
|
-
edgeType: import_types40.EdgeType.CALLS
|
|
10592
|
-
};
|
|
10593
|
-
}
|
|
10594
|
-
const taggedFileId = findTaggedWorkerFileNode(graph, scriptName);
|
|
10595
|
-
if (taggedFileId) {
|
|
10596
|
-
const fileNode = graph.getNodeAttributes(taggedFileId);
|
|
10597
|
-
return {
|
|
10598
|
-
targetNodeId: resolveRouteGrain(fileNode.service, taggedFileId),
|
|
10599
|
-
serviceName: fileNode.service,
|
|
10600
|
-
edgeType: import_types40.EdgeType.CALLS
|
|
10601
|
-
};
|
|
10602
|
-
}
|
|
10603
|
-
return {
|
|
10604
|
-
targetNodeId: (0, import_types40.infraId)("cloudflare-worker", scriptName),
|
|
10605
|
-
serviceName: scriptName,
|
|
10606
|
-
edgeType: import_types40.EdgeType.CALLS,
|
|
10607
|
-
ensureInfraNode: { kind: "cloudflare-worker", name: scriptName, provider: "cloudflare" }
|
|
10608
|
-
};
|
|
10609
|
-
};
|
|
10610
|
-
}
|
|
10611
|
-
|
|
10612
|
-
// src/connectors-config.ts
|
|
10613
|
-
init_cjs_shims();
|
|
10614
|
-
var import_node_os4 = __toESM(require("os"), 1);
|
|
10615
|
-
var import_node_path47 = __toESM(require("path"), 1);
|
|
10616
|
-
var import_node_fs28 = require("fs");
|
|
10617
|
-
var CONNECTORS_CONFIG_VERSION = 1;
|
|
10618
|
-
var EnvRefUnsetError = class extends Error {
|
|
10619
|
-
ref;
|
|
10620
|
-
varName;
|
|
10621
|
-
constructor(ref, varName) {
|
|
10622
|
-
super(`${ref} is unset`);
|
|
10623
|
-
this.name = "EnvRefUnsetError";
|
|
10624
|
-
this.ref = ref;
|
|
10625
|
-
this.varName = varName;
|
|
10626
|
-
}
|
|
10627
|
-
};
|
|
10628
|
-
function neatHome2() {
|
|
10629
|
-
const override = process.env.NEAT_HOME;
|
|
10630
|
-
if (override && override.length > 0) return import_node_path47.default.resolve(override);
|
|
10631
|
-
return import_node_path47.default.join(import_node_os4.default.homedir(), ".neat");
|
|
10632
|
-
}
|
|
10633
|
-
function connectorsConfigPath(home = neatHome2()) {
|
|
10634
|
-
return import_node_path47.default.join(home, "connectors.json");
|
|
10635
|
-
}
|
|
10636
|
-
var MODE_MASK_LOOSER_THAN_0600 = 63;
|
|
10637
|
-
async function warnIfModeLooserThan0600(file) {
|
|
10638
|
-
if (process.platform === "win32") return;
|
|
10639
|
-
try {
|
|
10640
|
-
const stat = await import_node_fs28.promises.stat(file);
|
|
10641
|
-
if ((stat.mode & MODE_MASK_LOOSER_THAN_0600) !== 0) {
|
|
10642
|
-
const mode = (stat.mode & 511).toString(8).padStart(3, "0");
|
|
10643
|
-
console.warn(
|
|
10644
|
-
`[neat] ${file} is mode 0${mode}, looser than the 0600 this file's secrets call for \u2014 run \`chmod 600 ${file}\``
|
|
10645
|
-
);
|
|
10646
|
-
}
|
|
10647
|
-
} catch {
|
|
10648
|
-
}
|
|
10649
|
-
}
|
|
10650
|
-
async function readConnectorsConfig(home = neatHome2()) {
|
|
10651
|
-
const file = connectorsConfigPath(home);
|
|
10652
|
-
let raw;
|
|
10653
|
-
try {
|
|
10654
|
-
raw = await import_node_fs28.promises.readFile(file, "utf8");
|
|
10655
|
-
} catch (err) {
|
|
10656
|
-
if (err.code === "ENOENT") {
|
|
10657
|
-
return { version: CONNECTORS_CONFIG_VERSION, connectors: [] };
|
|
10658
|
-
}
|
|
10659
|
-
throw err;
|
|
10660
|
-
}
|
|
10661
|
-
await warnIfModeLooserThan0600(file);
|
|
10662
|
-
let parsed;
|
|
10663
|
-
try {
|
|
10664
|
-
parsed = JSON.parse(raw);
|
|
10665
|
-
} catch (err) {
|
|
10666
|
-
throw new Error(`${file} is not valid JSON: ${err.message}`);
|
|
10667
|
-
}
|
|
10668
|
-
return validateConfig(parsed, file);
|
|
10669
|
-
}
|
|
10670
|
-
function validateConfig(parsed, file) {
|
|
10671
|
-
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
10672
|
-
throw new Error(`${file} must be a JSON object with a "connectors" array`);
|
|
10673
|
-
}
|
|
10674
|
-
const obj = parsed;
|
|
10675
|
-
const version = obj.version === void 0 ? CONNECTORS_CONFIG_VERSION : obj.version;
|
|
10676
|
-
if (typeof version !== "number" || !Number.isInteger(version)) {
|
|
10677
|
-
throw new Error(`${file}: "version" must be an integer`);
|
|
10678
|
-
}
|
|
10679
|
-
const rawConnectors = obj.connectors;
|
|
10680
|
-
if (!Array.isArray(rawConnectors)) {
|
|
10681
|
-
throw new Error(`${file}: "connectors" must be an array`);
|
|
10682
|
-
}
|
|
10683
|
-
const connectors = rawConnectors.map((entry2, i) => validateEntry(entry2, i, file));
|
|
10684
|
-
return { version, connectors };
|
|
10685
|
-
}
|
|
10686
|
-
function validateEntry(entry2, index, file) {
|
|
10687
|
-
const where = `${file}: connectors[${index}]`;
|
|
10688
|
-
if (typeof entry2 !== "object" || entry2 === null || Array.isArray(entry2)) {
|
|
10689
|
-
throw new Error(`${where} must be an object`);
|
|
10690
|
-
}
|
|
10691
|
-
const e = entry2;
|
|
10692
|
-
const id = e.id;
|
|
10693
|
-
if (typeof id !== "string" || id.length === 0) {
|
|
10694
|
-
throw new Error(`${where}.id must be a non-empty string`);
|
|
10695
|
-
}
|
|
10696
|
-
const provider = e.provider;
|
|
10697
|
-
if (typeof provider !== "string" || provider.length === 0) {
|
|
10698
|
-
throw new Error(`${where}.provider must be a non-empty string`);
|
|
10699
|
-
}
|
|
10700
|
-
if (e.project !== void 0 && typeof e.project !== "string") {
|
|
10701
|
-
throw new Error(`${where}.project must be a string when present`);
|
|
10702
|
-
}
|
|
10703
|
-
const credential = validateCredentialRef(e.credential, `${where}.credential`);
|
|
10704
|
-
let options;
|
|
10705
|
-
if (e.options !== void 0) {
|
|
10706
|
-
if (typeof e.options !== "object" || e.options === null || Array.isArray(e.options)) {
|
|
10707
|
-
throw new Error(`${where}.options must be an object when present`);
|
|
10708
|
-
}
|
|
10709
|
-
options = e.options;
|
|
10710
|
-
}
|
|
11125
|
+
if (spaceIdx === -1) return void 0;
|
|
11126
|
+
const rest = trimmed.slice(spaceIdx + 1).trim();
|
|
11127
|
+
return rest.length > 0 ? rest : void 0;
|
|
11128
|
+
}
|
|
11129
|
+
var ERROR_STATUS_THRESHOLD3 = 500;
|
|
11130
|
+
function mapEventToSignal(event) {
|
|
11131
|
+
const metadata = event.$metadata;
|
|
11132
|
+
const workers = event.$workers;
|
|
11133
|
+
const method = parseHttpMethodFromTrigger(metadata?.trigger);
|
|
11134
|
+
if (!method) return null;
|
|
11135
|
+
const scriptName = workers?.scriptName ?? metadata?.service;
|
|
11136
|
+
if (!scriptName) return null;
|
|
11137
|
+
const timestampMs = event.timestamp ?? metadata?.startTime;
|
|
11138
|
+
if (typeof timestampMs !== "number" || !Number.isFinite(timestampMs)) return null;
|
|
11139
|
+
const statusCode = metadata?.statusCode;
|
|
11140
|
+
const isError = typeof statusCode === "number" && statusCode >= ERROR_STATUS_THRESHOLD3;
|
|
11141
|
+
const path61 = metadata?.trigger ? parsePathFromTrigger(metadata.trigger) : void 0;
|
|
10711
11142
|
return {
|
|
10712
|
-
|
|
10713
|
-
|
|
10714
|
-
|
|
10715
|
-
|
|
10716
|
-
|
|
11143
|
+
targetKind: CLOUDFLARE_TARGET_KIND,
|
|
11144
|
+
targetName: scriptName,
|
|
11145
|
+
callCount: 1,
|
|
11146
|
+
errorCount: isError ? 1 : 0,
|
|
11147
|
+
lastObservedIso: new Date(timestampMs).toISOString(),
|
|
11148
|
+
method,
|
|
11149
|
+
...path61 ? { path: path61 } : {},
|
|
11150
|
+
...typeof statusCode === "number" ? { statusCode } : {},
|
|
11151
|
+
...typeof metadata?.duration === "number" ? { duration: metadata.duration } : {}
|
|
10717
11152
|
};
|
|
10718
11153
|
}
|
|
10719
|
-
|
|
10720
|
-
|
|
10721
|
-
|
|
10722
|
-
|
|
10723
|
-
|
|
10724
|
-
|
|
10725
|
-
|
|
10726
|
-
|
|
10727
|
-
|
|
10728
|
-
|
|
10729
|
-
|
|
10730
|
-
if (typeof v !== "string" || v.length === 0) {
|
|
10731
|
-
throw new Error(`${where}.${key} must be a non-empty string`);
|
|
10732
|
-
}
|
|
10733
|
-
}
|
|
10734
|
-
return fields;
|
|
10735
|
-
}
|
|
10736
|
-
throw new Error(`${where} must be a string or an object of strings`);
|
|
11154
|
+
|
|
11155
|
+
// src/connectors/cloudflare/connector.ts
|
|
11156
|
+
var DEFAULT_MAX_LOOKBACK_MS3 = 60 * 60 * 1e3;
|
|
11157
|
+
function resolveFromMs(since, maxLookbackMs) {
|
|
11158
|
+
const cap = maxLookbackMs ?? DEFAULT_MAX_LOOKBACK_MS3;
|
|
11159
|
+
const now = Date.now();
|
|
11160
|
+
const floor = now - cap;
|
|
11161
|
+
if (!since) return floor;
|
|
11162
|
+
const parsed = Date.parse(since);
|
|
11163
|
+
if (Number.isNaN(parsed)) return floor;
|
|
11164
|
+
return Math.max(parsed, floor);
|
|
10737
11165
|
}
|
|
10738
|
-
|
|
10739
|
-
|
|
10740
|
-
|
|
10741
|
-
}
|
|
10742
|
-
const fields = {};
|
|
10743
|
-
for (const [key, value] of Object.entries(ref)) {
|
|
10744
|
-
fields[key] = resolveRef(value, env);
|
|
11166
|
+
var CloudflareConnector = class {
|
|
11167
|
+
constructor(config) {
|
|
11168
|
+
this.config = config;
|
|
10745
11169
|
}
|
|
10746
|
-
|
|
10747
|
-
|
|
10748
|
-
|
|
10749
|
-
|
|
10750
|
-
const
|
|
10751
|
-
const
|
|
10752
|
-
|
|
10753
|
-
|
|
11170
|
+
config;
|
|
11171
|
+
provider = "cloudflare";
|
|
11172
|
+
async poll(ctx) {
|
|
11173
|
+
const toMs = Date.now();
|
|
11174
|
+
const fromMs = resolveFromMs(ctx.since, this.config.maxLookbackMs);
|
|
11175
|
+
const events = await queryWorkerInvocations(ctx, this.config, { fromMs, toMs });
|
|
11176
|
+
const signals = [];
|
|
11177
|
+
for (const event of events) {
|
|
11178
|
+
const signal = mapEventToSignal(event);
|
|
11179
|
+
if (signal) signals.push(signal);
|
|
10754
11180
|
}
|
|
10755
|
-
return
|
|
10756
|
-
}
|
|
10757
|
-
return value;
|
|
10758
|
-
}
|
|
10759
|
-
var CONNECTORS_LOCK_TIMEOUT_MS = 5e3;
|
|
10760
|
-
var CONNECTORS_LOCK_RETRY_MS = 50;
|
|
10761
|
-
function connectorsConfigLockPath(home = neatHome2()) {
|
|
10762
|
-
return import_node_path47.default.join(home, "connectors.json.lock");
|
|
10763
|
-
}
|
|
10764
|
-
function isEnvRef(value) {
|
|
10765
|
-
return value.length > 1 && value.startsWith("$");
|
|
10766
|
-
}
|
|
10767
|
-
async function writeConfigAtomically0600(file, contents) {
|
|
10768
|
-
await import_node_fs28.promises.mkdir(import_node_path47.default.dirname(file), { recursive: true });
|
|
10769
|
-
const tmp = `${file}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
|
|
10770
|
-
const fd = await import_node_fs28.promises.open(tmp, "w", 384);
|
|
10771
|
-
try {
|
|
10772
|
-
await fd.writeFile(contents, "utf8");
|
|
10773
|
-
await fd.chmod(384);
|
|
10774
|
-
await fd.sync();
|
|
10775
|
-
} finally {
|
|
10776
|
-
await fd.close();
|
|
11181
|
+
return signals;
|
|
10777
11182
|
}
|
|
10778
|
-
|
|
10779
|
-
|
|
10780
|
-
|
|
10781
|
-
|
|
10782
|
-
|
|
10783
|
-
|
|
10784
|
-
|
|
10785
|
-
|
|
10786
|
-
try {
|
|
10787
|
-
await fd.writeFile(`${process.pid}
|
|
10788
|
-
`, "utf8");
|
|
10789
|
-
} finally {
|
|
10790
|
-
await fd.close();
|
|
10791
|
-
}
|
|
10792
|
-
return;
|
|
10793
|
-
} catch (err) {
|
|
10794
|
-
if (err.code !== "EEXIST") throw err;
|
|
10795
|
-
if (Date.now() >= deadline) {
|
|
10796
|
-
throw new Error(
|
|
10797
|
-
`neat connector: timed out after ${timeoutMs}ms waiting for ${lockPath}. Another neat process may be writing the connector config; if none is, remove that file by hand.`
|
|
10798
|
-
);
|
|
10799
|
-
}
|
|
10800
|
-
await new Promise((r) => setTimeout(r, CONNECTORS_LOCK_RETRY_MS));
|
|
11183
|
+
};
|
|
11184
|
+
function findTaggedWorkerFileNode(graph, workerName) {
|
|
11185
|
+
let found = null;
|
|
11186
|
+
graph.forEachNode((id, attrs) => {
|
|
11187
|
+
if (found) return;
|
|
11188
|
+
const a = attrs;
|
|
11189
|
+
if (a.type === import_types43.NodeType.FileNode && a.platform === "cloudflare" && a.platformName === workerName) {
|
|
11190
|
+
found = id;
|
|
10801
11191
|
}
|
|
10802
|
-
}
|
|
10803
|
-
}
|
|
10804
|
-
async function releaseConnectorsLock(lockPath) {
|
|
10805
|
-
await import_node_fs28.promises.unlink(lockPath).catch(() => {
|
|
10806
|
-
});
|
|
10807
|
-
}
|
|
10808
|
-
async function withConnectorsLock(home, fn) {
|
|
10809
|
-
const lock = connectorsConfigLockPath(home);
|
|
10810
|
-
await acquireConnectorsLock(lock);
|
|
10811
|
-
try {
|
|
10812
|
-
return await fn();
|
|
10813
|
-
} finally {
|
|
10814
|
-
await releaseConnectorsLock(lock);
|
|
10815
|
-
}
|
|
10816
|
-
}
|
|
10817
|
-
function serializeConfig(config) {
|
|
10818
|
-
return JSON.stringify(
|
|
10819
|
-
{ version: config.version ?? CONNECTORS_CONFIG_VERSION, connectors: config.connectors },
|
|
10820
|
-
null,
|
|
10821
|
-
2
|
|
10822
|
-
) + "\n";
|
|
10823
|
-
}
|
|
10824
|
-
async function upsertConnectorEntry(entry2, home = neatHome2()) {
|
|
10825
|
-
const file = connectorsConfigPath(home);
|
|
10826
|
-
return withConnectorsLock(home, async () => {
|
|
10827
|
-
const config = await readConnectorsConfig(home);
|
|
10828
|
-
validateEntry(entry2, config.connectors.length, file);
|
|
10829
|
-
const idx = config.connectors.findIndex((c) => c.id === entry2.id);
|
|
10830
|
-
const replaced = idx >= 0;
|
|
10831
|
-
if (replaced) config.connectors[idx] = entry2;
|
|
10832
|
-
else config.connectors.push(entry2);
|
|
10833
|
-
await writeConfigAtomically0600(file, serializeConfig(config));
|
|
10834
|
-
return { replaced };
|
|
10835
11192
|
});
|
|
11193
|
+
return found;
|
|
10836
11194
|
}
|
|
10837
|
-
|
|
10838
|
-
const
|
|
10839
|
-
|
|
10840
|
-
|
|
10841
|
-
|
|
10842
|
-
|
|
10843
|
-
|
|
10844
|
-
|
|
10845
|
-
|
|
11195
|
+
function findMatchingRouteNode(graph, serviceName, method, path61) {
|
|
11196
|
+
const normalizedPath = normalizePathTemplate(path61);
|
|
11197
|
+
let found = null;
|
|
11198
|
+
graph.forEachNode((id, attrs) => {
|
|
11199
|
+
if (found) return;
|
|
11200
|
+
const a = attrs;
|
|
11201
|
+
if (a.type !== import_types43.NodeType.RouteNode || a.service !== serviceName) return;
|
|
11202
|
+
if (!a.pathTemplate || normalizePathTemplate(a.pathTemplate) !== normalizedPath) return;
|
|
11203
|
+
const routeMethod = (a.method ?? "").toUpperCase();
|
|
11204
|
+
if (routeMethod !== "ALL" && routeMethod !== method) return;
|
|
11205
|
+
found = id;
|
|
10846
11206
|
});
|
|
11207
|
+
return found;
|
|
10847
11208
|
}
|
|
10848
|
-
function
|
|
10849
|
-
return
|
|
10850
|
-
|
|
10851
|
-
|
|
10852
|
-
|
|
10853
|
-
|
|
10854
|
-
|
|
10855
|
-
|
|
10856
|
-
|
|
10857
|
-
|
|
10858
|
-
|
|
10859
|
-
|
|
10860
|
-
for (let n = 2; ; n++) {
|
|
10861
|
-
const candidate = `${stem}-${n}`;
|
|
10862
|
-
if (!existingIds.has(candidate)) return candidate;
|
|
10863
|
-
}
|
|
10864
|
-
}
|
|
10865
|
-
function describeCredential(ref, env = process.env) {
|
|
10866
|
-
const one = (value, field) => {
|
|
10867
|
-
if (isEnvRef(value)) {
|
|
10868
|
-
const varName = value.slice(1);
|
|
10869
|
-
const resolved = env[varName];
|
|
10870
|
-
const set = typeof resolved === "string" && resolved.length > 0;
|
|
11209
|
+
function createCloudflareResolveTarget(config, graph) {
|
|
11210
|
+
return (signal) => {
|
|
11211
|
+
if (signal.targetKind !== CLOUDFLARE_TARGET_KIND) return null;
|
|
11212
|
+
const scriptName = signal.targetName;
|
|
11213
|
+
const { method, path: path61 } = signal;
|
|
11214
|
+
const resolveRouteGrain = (serviceName, wholeFileId) => {
|
|
11215
|
+
if (!method || !path61) return wholeFileId;
|
|
11216
|
+
return findMatchingRouteNode(graph, serviceName, method, path61) ?? wholeFileId;
|
|
11217
|
+
};
|
|
11218
|
+
const mapping = config.workers?.[scriptName];
|
|
11219
|
+
if (mapping) {
|
|
11220
|
+
const wholeFileId = (0, import_types43.fileId)(mapping.service, mapping.entryFile);
|
|
10871
11221
|
return {
|
|
10872
|
-
|
|
10873
|
-
|
|
10874
|
-
|
|
10875
|
-
status: set ? "set" : "unset"
|
|
11222
|
+
targetNodeId: resolveRouteGrain(mapping.service, wholeFileId),
|
|
11223
|
+
serviceName: mapping.service,
|
|
11224
|
+
edgeType: import_types43.EdgeType.CALLS
|
|
10876
11225
|
};
|
|
10877
11226
|
}
|
|
10878
|
-
|
|
11227
|
+
const taggedFileId = findTaggedWorkerFileNode(graph, scriptName);
|
|
11228
|
+
if (taggedFileId) {
|
|
11229
|
+
const fileNode = graph.getNodeAttributes(taggedFileId);
|
|
11230
|
+
return {
|
|
11231
|
+
targetNodeId: resolveRouteGrain(fileNode.service, taggedFileId),
|
|
11232
|
+
serviceName: fileNode.service,
|
|
11233
|
+
edgeType: import_types43.EdgeType.CALLS
|
|
11234
|
+
};
|
|
11235
|
+
}
|
|
11236
|
+
return {
|
|
11237
|
+
targetNodeId: (0, import_types43.infraId)("cloudflare-worker", scriptName),
|
|
11238
|
+
serviceName: scriptName,
|
|
11239
|
+
edgeType: import_types43.EdgeType.CALLS,
|
|
11240
|
+
ensureInfraNode: { kind: "cloudflare-worker", name: scriptName, provider: "cloudflare" }
|
|
11241
|
+
};
|
|
10879
11242
|
};
|
|
10880
|
-
if (typeof ref === "string") return [one(ref)];
|
|
10881
|
-
return Object.entries(ref).map(([k, v]) => one(v, k));
|
|
10882
11243
|
}
|
|
10883
11244
|
|
|
10884
11245
|
// src/connectors/registry.ts
|
|
@@ -11074,33 +11435,33 @@ init_auth();
|
|
|
11074
11435
|
|
|
11075
11436
|
// src/unrouted.ts
|
|
11076
11437
|
init_cjs_shims();
|
|
11077
|
-
var
|
|
11078
|
-
var
|
|
11438
|
+
var import_node_fs32 = require("fs");
|
|
11439
|
+
var import_node_path51 = __toESM(require("path"), 1);
|
|
11079
11440
|
|
|
11080
11441
|
// src/daemon.ts
|
|
11081
|
-
var
|
|
11442
|
+
var import_types46 = require("@neat.is/types");
|
|
11082
11443
|
function daemonJsonPath(scanPath) {
|
|
11083
|
-
return
|
|
11444
|
+
return import_node_path52.default.join(scanPath, "neat-out", "daemon.json");
|
|
11084
11445
|
}
|
|
11085
11446
|
function daemonsDiscoveryDir(home) {
|
|
11086
11447
|
const base = home && home.length > 0 ? home : neatHomeFromEnv();
|
|
11087
|
-
return
|
|
11448
|
+
return import_node_path52.default.join(base, "daemons");
|
|
11088
11449
|
}
|
|
11089
11450
|
function daemonDiscoveryPath(project, home) {
|
|
11090
|
-
return
|
|
11451
|
+
return import_node_path52.default.join(daemonsDiscoveryDir(home), `${sanitizeDiscoveryName(project)}.json`);
|
|
11091
11452
|
}
|
|
11092
11453
|
function sanitizeDiscoveryName(project) {
|
|
11093
11454
|
return project.replace(/[^A-Za-z0-9._-]/g, "_");
|
|
11094
11455
|
}
|
|
11095
11456
|
function neatHomeFromEnv() {
|
|
11096
11457
|
const env = process.env.NEAT_HOME;
|
|
11097
|
-
if (env && env.length > 0) return
|
|
11458
|
+
if (env && env.length > 0) return import_node_path52.default.resolve(env);
|
|
11098
11459
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
11099
|
-
return
|
|
11460
|
+
return import_node_path52.default.join(home, ".neat");
|
|
11100
11461
|
}
|
|
11101
11462
|
async function readDaemonRecord(scanPath) {
|
|
11102
11463
|
try {
|
|
11103
|
-
const raw = await
|
|
11464
|
+
const raw = await import_node_fs33.promises.readFile(daemonJsonPath(scanPath), "utf8");
|
|
11104
11465
|
const parsed = JSON.parse(raw);
|
|
11105
11466
|
if (typeof parsed.project === "string" && parsed.ports && typeof parsed.ports.rest === "number" && typeof parsed.ports.otlp === "number" && typeof parsed.ports.web === "number") {
|
|
11106
11467
|
return parsed;
|
|
@@ -11140,7 +11501,7 @@ async function clearDaemonRecord(record, home) {
|
|
|
11140
11501
|
} catch {
|
|
11141
11502
|
}
|
|
11142
11503
|
try {
|
|
11143
|
-
await
|
|
11504
|
+
await import_node_fs33.promises.unlink(daemonDiscoveryPath(record.project, home));
|
|
11144
11505
|
} catch {
|
|
11145
11506
|
}
|
|
11146
11507
|
}
|
|
@@ -11166,8 +11527,8 @@ init_otel_grpc();
|
|
|
11166
11527
|
|
|
11167
11528
|
// src/search.ts
|
|
11168
11529
|
init_cjs_shims();
|
|
11169
|
-
var
|
|
11170
|
-
var
|
|
11530
|
+
var import_node_fs34 = require("fs");
|
|
11531
|
+
var import_node_path53 = __toESM(require("path"), 1);
|
|
11171
11532
|
var import_node_crypto4 = require("crypto");
|
|
11172
11533
|
var DEFAULT_LIMIT = 10;
|
|
11173
11534
|
var NOMIC_DIM = 768;
|
|
@@ -11321,7 +11682,7 @@ async function pickEmbedder() {
|
|
|
11321
11682
|
}
|
|
11322
11683
|
async function readCache(cachePath) {
|
|
11323
11684
|
try {
|
|
11324
|
-
const raw = await
|
|
11685
|
+
const raw = await import_node_fs34.promises.readFile(cachePath, "utf8");
|
|
11325
11686
|
const parsed = JSON.parse(raw);
|
|
11326
11687
|
if (parsed.version !== 1) return null;
|
|
11327
11688
|
return parsed;
|
|
@@ -11330,8 +11691,8 @@ async function readCache(cachePath) {
|
|
|
11330
11691
|
}
|
|
11331
11692
|
}
|
|
11332
11693
|
async function writeCache(cachePath, cache) {
|
|
11333
|
-
await
|
|
11334
|
-
await
|
|
11694
|
+
await import_node_fs34.promises.mkdir(import_node_path53.default.dirname(cachePath), { recursive: true });
|
|
11695
|
+
await import_node_fs34.promises.writeFile(cachePath, JSON.stringify(cache));
|
|
11335
11696
|
}
|
|
11336
11697
|
var VectorIndex = class {
|
|
11337
11698
|
constructor(embedder, cachePath) {
|
|
@@ -11488,8 +11849,8 @@ var ALL_PHASES = [
|
|
|
11488
11849
|
];
|
|
11489
11850
|
function classifyChange(relPath) {
|
|
11490
11851
|
const phases = /* @__PURE__ */ new Set();
|
|
11491
|
-
const base =
|
|
11492
|
-
const segments = relPath.split(
|
|
11852
|
+
const base = import_node_path54.default.basename(relPath).toLowerCase();
|
|
11853
|
+
const segments = relPath.split(import_node_path54.default.sep).map((s) => s.toLowerCase());
|
|
11493
11854
|
if (base === "package.json" || base === "requirements.txt" || base === "pyproject.toml" || base === "setup.py") {
|
|
11494
11855
|
phases.add("services");
|
|
11495
11856
|
phases.add("aliases");
|
|
@@ -11610,16 +11971,16 @@ function countWatchableDirs(scanPath, limit) {
|
|
|
11610
11971
|
if (count >= limit) return;
|
|
11611
11972
|
let entries;
|
|
11612
11973
|
try {
|
|
11613
|
-
entries =
|
|
11974
|
+
entries = import_node_fs35.default.readdirSync(dir, { withFileTypes: true });
|
|
11614
11975
|
} catch {
|
|
11615
11976
|
return;
|
|
11616
11977
|
}
|
|
11617
11978
|
for (const e of entries) {
|
|
11618
11979
|
if (count >= limit) return;
|
|
11619
11980
|
if (!e.isDirectory()) continue;
|
|
11620
|
-
if (IGNORED_WATCH_PATHS.some((re) => re.test(
|
|
11981
|
+
if (IGNORED_WATCH_PATHS.some((re) => re.test(import_node_path54.default.join(dir, e.name) + import_node_path54.default.sep))) continue;
|
|
11621
11982
|
count++;
|
|
11622
|
-
if (depth < 2) visit(
|
|
11983
|
+
if (depth < 2) visit(import_node_path54.default.join(dir, e.name), depth + 1);
|
|
11623
11984
|
}
|
|
11624
11985
|
};
|
|
11625
11986
|
visit(scanPath, 0);
|
|
@@ -11637,8 +11998,8 @@ async function startWatch(graph, opts) {
|
|
|
11637
11998
|
const projectName = opts.project ?? DEFAULT_PROJECT;
|
|
11638
11999
|
await loadGraphFromDisk(graph, opts.outPath);
|
|
11639
12000
|
const detachEventBus = attachGraphToEventBus(graph, { project: projectName });
|
|
11640
|
-
const policyFilePath =
|
|
11641
|
-
const policyViolationsPath =
|
|
12001
|
+
const policyFilePath = import_node_path54.default.join(opts.scanPath, "policy.json");
|
|
12002
|
+
const policyViolationsPath = import_node_path54.default.join(import_node_path54.default.dirname(opts.outPath), "policy-violations.ndjson");
|
|
11642
12003
|
let policies = [];
|
|
11643
12004
|
try {
|
|
11644
12005
|
policies = await loadPolicyFile(policyFilePath);
|
|
@@ -11689,7 +12050,7 @@ async function startWatch(graph, opts) {
|
|
|
11689
12050
|
assertBindAuthority(host, auth.authToken);
|
|
11690
12051
|
const port = opts.port ?? 8080;
|
|
11691
12052
|
const otelPort = opts.otelPort ?? 4318;
|
|
11692
|
-
const cachePath = opts.embeddingsCachePath ??
|
|
12053
|
+
const cachePath = opts.embeddingsCachePath ?? import_node_path54.default.join(import_node_path54.default.dirname(opts.outPath), "embeddings.json");
|
|
11693
12054
|
let searchIndex;
|
|
11694
12055
|
try {
|
|
11695
12056
|
searchIndex = await buildSearchIndex(graph, { cachePath });
|
|
@@ -11707,7 +12068,7 @@ async function startWatch(graph, opts) {
|
|
|
11707
12068
|
// Paths are derived from the explicit options the watch caller passes
|
|
11708
12069
|
// — pathsForProject is only used to fill in the embeddings/snapshot
|
|
11709
12070
|
// fields so the registry shape is complete.
|
|
11710
|
-
...pathsForProject(projectName,
|
|
12071
|
+
...pathsForProject(projectName, import_node_path54.default.dirname(opts.outPath)),
|
|
11711
12072
|
snapshotPath: opts.outPath,
|
|
11712
12073
|
errorsPath: opts.errorsPath,
|
|
11713
12074
|
staleEventsPath: opts.staleEventsPath
|
|
@@ -11825,9 +12186,9 @@ async function startWatch(graph, opts) {
|
|
|
11825
12186
|
};
|
|
11826
12187
|
const onPath = (absPath) => {
|
|
11827
12188
|
if (shouldIgnore(absPath)) return;
|
|
11828
|
-
const rel =
|
|
12189
|
+
const rel = import_node_path54.default.relative(opts.scanPath, absPath);
|
|
11829
12190
|
if (!rel || rel.startsWith("..")) return;
|
|
11830
|
-
pendingPaths.add(rel.split(
|
|
12191
|
+
pendingPaths.add(rel.split(import_node_path54.default.sep).join("/"));
|
|
11831
12192
|
const phases = classifyChange(rel);
|
|
11832
12193
|
if (phases.size === 0) {
|
|
11833
12194
|
for (const p of ALL_PHASES) pending.add(p);
|
|
@@ -11883,8 +12244,8 @@ async function startWatch(graph, opts) {
|
|
|
11883
12244
|
|
|
11884
12245
|
// src/deploy/detect.ts
|
|
11885
12246
|
init_cjs_shims();
|
|
11886
|
-
var
|
|
11887
|
-
var
|
|
12247
|
+
var import_node_fs36 = require("fs");
|
|
12248
|
+
var import_node_path55 = __toESM(require("path"), 1);
|
|
11888
12249
|
var import_node_child_process2 = require("child_process");
|
|
11889
12250
|
var import_node_crypto5 = require("crypto");
|
|
11890
12251
|
function generateToken() {
|
|
@@ -11984,21 +12345,21 @@ async function runDeploy(opts = {}) {
|
|
|
11984
12345
|
const token = generateToken();
|
|
11985
12346
|
switch (substrate) {
|
|
11986
12347
|
case "docker-compose": {
|
|
11987
|
-
const artifactPath =
|
|
12348
|
+
const artifactPath = import_node_path55.default.join(cwd, "docker-compose.neat.yml");
|
|
11988
12349
|
const contents = emitDockerCompose(cwd);
|
|
11989
|
-
await
|
|
12350
|
+
await import_node_fs36.promises.writeFile(artifactPath, contents, "utf8");
|
|
11990
12351
|
return {
|
|
11991
12352
|
substrate,
|
|
11992
12353
|
artifactPath,
|
|
11993
12354
|
token,
|
|
11994
12355
|
contents,
|
|
11995
|
-
startCommand: `NEAT_AUTH_TOKEN=${token} docker compose -f ${
|
|
12356
|
+
startCommand: `NEAT_AUTH_TOKEN=${token} docker compose -f ${import_node_path55.default.basename(artifactPath)} up -d`
|
|
11996
12357
|
};
|
|
11997
12358
|
}
|
|
11998
12359
|
case "systemd": {
|
|
11999
|
-
const artifactPath =
|
|
12360
|
+
const artifactPath = import_node_path55.default.join(cwd, "neat.service");
|
|
12000
12361
|
const contents = emitSystemdUnit(cwd);
|
|
12001
|
-
await
|
|
12362
|
+
await import_node_fs36.promises.writeFile(artifactPath, contents, "utf8");
|
|
12002
12363
|
return {
|
|
12003
12364
|
substrate,
|
|
12004
12365
|
artifactPath,
|
|
@@ -12031,8 +12392,8 @@ init_cjs_shims();
|
|
|
12031
12392
|
|
|
12032
12393
|
// src/installers/javascript.ts
|
|
12033
12394
|
init_cjs_shims();
|
|
12034
|
-
var
|
|
12035
|
-
var
|
|
12395
|
+
var import_node_fs37 = require("fs");
|
|
12396
|
+
var import_node_path56 = __toESM(require("path"), 1);
|
|
12036
12397
|
var import_semver2 = __toESM(require("semver"), 1);
|
|
12037
12398
|
|
|
12038
12399
|
// src/installers/templates.ts
|
|
@@ -12647,15 +13008,15 @@ var OTEL_ENV = {
|
|
|
12647
13008
|
value: "http://localhost:4318/projects/<project>/v1/traces"
|
|
12648
13009
|
};
|
|
12649
13010
|
function serviceNodeName(pkg, serviceDir) {
|
|
12650
|
-
return pkg.name ??
|
|
13011
|
+
return pkg.name ?? import_node_path56.default.basename(serviceDir);
|
|
12651
13012
|
}
|
|
12652
13013
|
function projectToken(pkg, serviceDir, project) {
|
|
12653
13014
|
if (project && project.length > 0) return project;
|
|
12654
|
-
return pkg.name ??
|
|
13015
|
+
return pkg.name ?? import_node_path56.default.basename(serviceDir);
|
|
12655
13016
|
}
|
|
12656
13017
|
async function readJsonFile(p) {
|
|
12657
13018
|
try {
|
|
12658
|
-
const raw = await
|
|
13019
|
+
const raw = await import_node_fs37.promises.readFile(p, "utf8");
|
|
12659
13020
|
return JSON.parse(raw);
|
|
12660
13021
|
} catch {
|
|
12661
13022
|
return null;
|
|
@@ -12664,16 +13025,16 @@ async function readJsonFile(p) {
|
|
|
12664
13025
|
async function detectRuntimeKind(pkgRoot, pkg) {
|
|
12665
13026
|
const deps = allDeps(pkg);
|
|
12666
13027
|
if ("react-native" in deps || "expo" in deps) return "react-native";
|
|
12667
|
-
const appJson = await readJsonFile(
|
|
13028
|
+
const appJson = await readJsonFile(import_node_path56.default.join(pkgRoot, "app.json"));
|
|
12668
13029
|
if (appJson && typeof appJson === "object" && "expo" in appJson) {
|
|
12669
13030
|
return "react-native";
|
|
12670
13031
|
}
|
|
12671
|
-
if (await exists3(
|
|
13032
|
+
if (await exists3(import_node_path56.default.join(pkgRoot, "vite.config.js")) || await exists3(import_node_path56.default.join(pkgRoot, "vite.config.ts")) || await exists3(import_node_path56.default.join(pkgRoot, "vite.config.mjs")) || "vite" in deps) {
|
|
12672
13033
|
return "browser-bundle";
|
|
12673
13034
|
}
|
|
12674
|
-
if (await exists3(
|
|
12675
|
-
if (await exists3(
|
|
12676
|
-
if (await exists3(
|
|
13035
|
+
if (await exists3(import_node_path56.default.join(pkgRoot, "wrangler.toml"))) return "cloudflare-workers";
|
|
13036
|
+
if (await exists3(import_node_path56.default.join(pkgRoot, "bun.lockb"))) return "bun";
|
|
13037
|
+
if (await exists3(import_node_path56.default.join(pkgRoot, "deno.json")) || await exists3(import_node_path56.default.join(pkgRoot, "deno.lock"))) {
|
|
12677
13038
|
return "deno";
|
|
12678
13039
|
}
|
|
12679
13040
|
const engines = pkg.engines ?? {};
|
|
@@ -12682,7 +13043,7 @@ async function detectRuntimeKind(pkgRoot, pkg) {
|
|
|
12682
13043
|
}
|
|
12683
13044
|
async function readPackageJson2(serviceDir) {
|
|
12684
13045
|
try {
|
|
12685
|
-
const raw = await
|
|
13046
|
+
const raw = await import_node_fs37.promises.readFile(import_node_path56.default.join(serviceDir, "package.json"), "utf8");
|
|
12686
13047
|
return JSON.parse(raw);
|
|
12687
13048
|
} catch {
|
|
12688
13049
|
return null;
|
|
@@ -12690,7 +13051,7 @@ async function readPackageJson2(serviceDir) {
|
|
|
12690
13051
|
}
|
|
12691
13052
|
async function exists3(p) {
|
|
12692
13053
|
try {
|
|
12693
|
-
await
|
|
13054
|
+
await import_node_fs37.promises.stat(p);
|
|
12694
13055
|
return true;
|
|
12695
13056
|
} catch {
|
|
12696
13057
|
return false;
|
|
@@ -12698,7 +13059,7 @@ async function exists3(p) {
|
|
|
12698
13059
|
}
|
|
12699
13060
|
async function readFileMaybe(p) {
|
|
12700
13061
|
try {
|
|
12701
|
-
return await
|
|
13062
|
+
return await import_node_fs37.promises.readFile(p, "utf8");
|
|
12702
13063
|
} catch {
|
|
12703
13064
|
return null;
|
|
12704
13065
|
}
|
|
@@ -12726,7 +13087,7 @@ function needsVersionUpgrade(installed, expected) {
|
|
|
12726
13087
|
var NEXT_CONFIG_CANDIDATES = ["next.config.js", "next.config.ts", "next.config.mjs"];
|
|
12727
13088
|
async function findNextConfig(serviceDir) {
|
|
12728
13089
|
for (const name of NEXT_CONFIG_CANDIDATES) {
|
|
12729
|
-
const candidate =
|
|
13090
|
+
const candidate = import_node_path56.default.join(serviceDir, name);
|
|
12730
13091
|
if (await exists3(candidate)) return candidate;
|
|
12731
13092
|
}
|
|
12732
13093
|
return null;
|
|
@@ -12795,7 +13156,7 @@ function hasRemixDependency(pkg) {
|
|
|
12795
13156
|
}
|
|
12796
13157
|
async function findRemixEntry(serviceDir) {
|
|
12797
13158
|
for (const rel of REMIX_ENTRY_CANDIDATES) {
|
|
12798
|
-
const candidate =
|
|
13159
|
+
const candidate = import_node_path56.default.join(serviceDir, rel);
|
|
12799
13160
|
if (await exists3(candidate)) return candidate;
|
|
12800
13161
|
}
|
|
12801
13162
|
return null;
|
|
@@ -12807,14 +13168,14 @@ function hasSvelteKitDependency(pkg) {
|
|
|
12807
13168
|
}
|
|
12808
13169
|
async function findSvelteKitHooks(serviceDir) {
|
|
12809
13170
|
for (const rel of SVELTEKIT_HOOKS_CANDIDATES) {
|
|
12810
|
-
const candidate =
|
|
13171
|
+
const candidate = import_node_path56.default.join(serviceDir, rel);
|
|
12811
13172
|
if (await exists3(candidate)) return candidate;
|
|
12812
13173
|
}
|
|
12813
13174
|
return null;
|
|
12814
13175
|
}
|
|
12815
13176
|
async function findSvelteKitConfig(serviceDir) {
|
|
12816
13177
|
for (const rel of SVELTEKIT_CONFIG_CANDIDATES) {
|
|
12817
|
-
const candidate =
|
|
13178
|
+
const candidate = import_node_path56.default.join(serviceDir, rel);
|
|
12818
13179
|
if (await exists3(candidate)) return candidate;
|
|
12819
13180
|
}
|
|
12820
13181
|
return null;
|
|
@@ -12825,7 +13186,7 @@ function hasNuxtDependency(pkg) {
|
|
|
12825
13186
|
}
|
|
12826
13187
|
async function findNuxtConfig(serviceDir) {
|
|
12827
13188
|
for (const name of NUXT_CONFIG_CANDIDATES) {
|
|
12828
|
-
const candidate =
|
|
13189
|
+
const candidate = import_node_path56.default.join(serviceDir, name);
|
|
12829
13190
|
if (await exists3(candidate)) return candidate;
|
|
12830
13191
|
}
|
|
12831
13192
|
return null;
|
|
@@ -12836,7 +13197,7 @@ function hasAstroDependency(pkg) {
|
|
|
12836
13197
|
}
|
|
12837
13198
|
async function findAstroConfig(serviceDir) {
|
|
12838
13199
|
for (const name of ASTRO_CONFIG_CANDIDATES) {
|
|
12839
|
-
const candidate =
|
|
13200
|
+
const candidate = import_node_path56.default.join(serviceDir, name);
|
|
12840
13201
|
if (await exists3(candidate)) return candidate;
|
|
12841
13202
|
}
|
|
12842
13203
|
return null;
|
|
@@ -12850,7 +13211,7 @@ function parseNextMajor(range) {
|
|
|
12850
13211
|
return Number.isFinite(n) ? n : null;
|
|
12851
13212
|
}
|
|
12852
13213
|
async function isTypeScriptProject(serviceDir) {
|
|
12853
|
-
return exists3(
|
|
13214
|
+
return exists3(import_node_path56.default.join(serviceDir, "tsconfig.json"));
|
|
12854
13215
|
}
|
|
12855
13216
|
var INDEX_EXTENSIONS = [".ts", ".tsx", ".js", ".mjs", ".cjs"];
|
|
12856
13217
|
var INDEX_CANDIDATES = INDEX_EXTENSIONS.map((ext) => `index${ext}`);
|
|
@@ -12899,7 +13260,7 @@ function entryFromScript(script) {
|
|
|
12899
13260
|
}
|
|
12900
13261
|
async function resolveEntry(serviceDir, pkg) {
|
|
12901
13262
|
if (typeof pkg.main === "string" && pkg.main.length > 0) {
|
|
12902
|
-
const candidate =
|
|
13263
|
+
const candidate = import_node_path56.default.resolve(serviceDir, pkg.main);
|
|
12903
13264
|
if (await exists3(candidate)) return candidate;
|
|
12904
13265
|
}
|
|
12905
13266
|
if (pkg.bin) {
|
|
@@ -12913,40 +13274,40 @@ async function resolveEntry(serviceDir, pkg) {
|
|
|
12913
13274
|
if (typeof first === "string") binEntry = first;
|
|
12914
13275
|
}
|
|
12915
13276
|
if (binEntry) {
|
|
12916
|
-
const candidate =
|
|
13277
|
+
const candidate = import_node_path56.default.resolve(serviceDir, binEntry);
|
|
12917
13278
|
if (await exists3(candidate)) return candidate;
|
|
12918
13279
|
}
|
|
12919
13280
|
}
|
|
12920
13281
|
const startEntry = entryFromScript(pkg.scripts?.start);
|
|
12921
13282
|
if (startEntry) {
|
|
12922
|
-
const candidate =
|
|
13283
|
+
const candidate = import_node_path56.default.resolve(serviceDir, startEntry);
|
|
12923
13284
|
if (await exists3(candidate)) return candidate;
|
|
12924
13285
|
}
|
|
12925
13286
|
const devEntry = entryFromScript(pkg.scripts?.dev);
|
|
12926
13287
|
if (devEntry) {
|
|
12927
|
-
const candidate =
|
|
13288
|
+
const candidate = import_node_path56.default.resolve(serviceDir, devEntry);
|
|
12928
13289
|
if (await exists3(candidate)) return candidate;
|
|
12929
13290
|
}
|
|
12930
13291
|
for (const rel of SRC_INDEX_CANDIDATES) {
|
|
12931
|
-
const candidate =
|
|
13292
|
+
const candidate = import_node_path56.default.join(serviceDir, rel);
|
|
12932
13293
|
if (await exists3(candidate)) return candidate;
|
|
12933
13294
|
}
|
|
12934
13295
|
for (const rel of SRC_NAMED_CANDIDATES) {
|
|
12935
|
-
const candidate =
|
|
13296
|
+
const candidate = import_node_path56.default.join(serviceDir, rel);
|
|
12936
13297
|
if (await exists3(candidate)) return candidate;
|
|
12937
13298
|
}
|
|
12938
13299
|
for (const rel of ROOT_NAMED_CANDIDATES) {
|
|
12939
|
-
const candidate =
|
|
13300
|
+
const candidate = import_node_path56.default.join(serviceDir, rel);
|
|
12940
13301
|
if (await exists3(candidate)) return candidate;
|
|
12941
13302
|
}
|
|
12942
13303
|
for (const name of INDEX_CANDIDATES) {
|
|
12943
|
-
const candidate =
|
|
13304
|
+
const candidate = import_node_path56.default.join(serviceDir, name);
|
|
12944
13305
|
if (await exists3(candidate)) return candidate;
|
|
12945
13306
|
}
|
|
12946
13307
|
return null;
|
|
12947
13308
|
}
|
|
12948
13309
|
function dispatchEntry(entryFile, pkg) {
|
|
12949
|
-
const ext =
|
|
13310
|
+
const ext = import_node_path56.default.extname(entryFile).toLowerCase();
|
|
12950
13311
|
if (ext === ".ts" || ext === ".tsx") return "ts";
|
|
12951
13312
|
if (ext === ".mjs") return "esm";
|
|
12952
13313
|
if (ext === ".cjs") return "cjs";
|
|
@@ -12963,9 +13324,9 @@ function otelInitContents(flavor) {
|
|
|
12963
13324
|
return OTEL_INIT_CJS;
|
|
12964
13325
|
}
|
|
12965
13326
|
function injectionLine(flavor, entryFile, otelInitFile) {
|
|
12966
|
-
let rel =
|
|
13327
|
+
let rel = import_node_path56.default.relative(import_node_path56.default.dirname(entryFile), otelInitFile);
|
|
12967
13328
|
if (!rel.startsWith(".")) rel = `./${rel}`;
|
|
12968
|
-
rel = rel.split(
|
|
13329
|
+
rel = rel.split(import_node_path56.default.sep).join("/");
|
|
12969
13330
|
if (flavor === "cjs") return `require('${rel}')`;
|
|
12970
13331
|
if (flavor === "esm") return `import '${rel}'`;
|
|
12971
13332
|
const tsRel = rel.replace(/\.ts$/, "");
|
|
@@ -12978,27 +13339,27 @@ function lineIsOtelInjection(line) {
|
|
|
12978
13339
|
}
|
|
12979
13340
|
async function detectsSrcLayout(serviceDir) {
|
|
12980
13341
|
const [hasSrcApp, hasSrcPages, hasRootApp, hasRootPages] = await Promise.all([
|
|
12981
|
-
exists3(
|
|
12982
|
-
exists3(
|
|
12983
|
-
exists3(
|
|
12984
|
-
exists3(
|
|
13342
|
+
exists3(import_node_path56.default.join(serviceDir, "src", "app")),
|
|
13343
|
+
exists3(import_node_path56.default.join(serviceDir, "src", "pages")),
|
|
13344
|
+
exists3(import_node_path56.default.join(serviceDir, "app")),
|
|
13345
|
+
exists3(import_node_path56.default.join(serviceDir, "pages"))
|
|
12985
13346
|
]);
|
|
12986
13347
|
return (hasSrcApp || hasSrcPages) && !hasRootApp && !hasRootPages;
|
|
12987
13348
|
}
|
|
12988
13349
|
async function planNext(serviceDir, pkg, manifestPath, nextConfigPath, project) {
|
|
12989
13350
|
const useTs = await isTypeScriptProject(serviceDir);
|
|
12990
13351
|
const srcLayout = await detectsSrcLayout(serviceDir);
|
|
12991
|
-
const baseDir = srcLayout ?
|
|
12992
|
-
const instrumentationFile =
|
|
12993
|
-
const instrumentationNodeFile =
|
|
13352
|
+
const baseDir = srcLayout ? import_node_path56.default.join(serviceDir, "src") : serviceDir;
|
|
13353
|
+
const instrumentationFile = import_node_path56.default.join(baseDir, useTs ? "instrumentation.ts" : "instrumentation.js");
|
|
13354
|
+
const instrumentationNodeFile = import_node_path56.default.join(
|
|
12994
13355
|
baseDir,
|
|
12995
13356
|
useTs ? "instrumentation.node.ts" : "instrumentation.node.js"
|
|
12996
13357
|
);
|
|
12997
|
-
const instrumentationEdgeFile =
|
|
13358
|
+
const instrumentationEdgeFile = import_node_path56.default.join(
|
|
12998
13359
|
baseDir,
|
|
12999
13360
|
useTs ? "instrumentation.edge.ts" : "instrumentation.edge.js"
|
|
13000
13361
|
);
|
|
13001
|
-
const envNeatFile =
|
|
13362
|
+
const envNeatFile = import_node_path56.default.join(baseDir, ".env.neat");
|
|
13002
13363
|
const existingDeps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
13003
13364
|
const dependencyEdits = [];
|
|
13004
13365
|
for (const sdk of SDK_PACKAGES) {
|
|
@@ -13075,7 +13436,7 @@ async function planNext(serviceDir, pkg, manifestPath, nextConfigPath, project)
|
|
|
13075
13436
|
const nextMajor = parseNextMajor(nextRange);
|
|
13076
13437
|
if (nextMajor !== null && nextMajor < 15) {
|
|
13077
13438
|
try {
|
|
13078
|
-
const raw = await
|
|
13439
|
+
const raw = await import_node_fs37.promises.readFile(nextConfigPath, "utf8");
|
|
13079
13440
|
if (!raw.includes("instrumentationHook")) {
|
|
13080
13441
|
nextConfigEdit = {
|
|
13081
13442
|
file: nextConfigPath,
|
|
@@ -13123,7 +13484,7 @@ function buildDependencyEdits(pkg, manifestPath) {
|
|
|
13123
13484
|
return edits;
|
|
13124
13485
|
}
|
|
13125
13486
|
async function queueEnvNeat(serviceDir, pkg, project, generatedFiles) {
|
|
13126
|
-
const envNeatFile =
|
|
13487
|
+
const envNeatFile = import_node_path56.default.join(serviceDir, ".env.neat");
|
|
13127
13488
|
if (!await exists3(envNeatFile)) {
|
|
13128
13489
|
generatedFiles.push({
|
|
13129
13490
|
file: envNeatFile,
|
|
@@ -13158,7 +13519,7 @@ function fileImportsOtelHook(raw, specifiers) {
|
|
|
13158
13519
|
}
|
|
13159
13520
|
async function planRemix(serviceDir, pkg, manifestPath, entryFile, project) {
|
|
13160
13521
|
const useTs = await isTypeScriptProject(serviceDir);
|
|
13161
|
-
const otelServerFile =
|
|
13522
|
+
const otelServerFile = import_node_path56.default.join(
|
|
13162
13523
|
serviceDir,
|
|
13163
13524
|
useTs ? "app/otel.server.ts" : "app/otel.server.js"
|
|
13164
13525
|
);
|
|
@@ -13179,7 +13540,7 @@ async function planRemix(serviceDir, pkg, manifestPath, entryFile, project) {
|
|
|
13179
13540
|
await queueEnvNeat(serviceDir, pkg, project, generatedFiles);
|
|
13180
13541
|
const entrypointEdits = [];
|
|
13181
13542
|
try {
|
|
13182
|
-
const raw = await
|
|
13543
|
+
const raw = await import_node_fs37.promises.readFile(entryFile, "utf8");
|
|
13183
13544
|
if (!fileImportsOtelHook(raw, ["./otel.server"])) {
|
|
13184
13545
|
const lines = raw.split(/\r?\n/);
|
|
13185
13546
|
const firstReal = lines[0]?.startsWith("#!") ? lines[1] ?? "" : lines[0] ?? "";
|
|
@@ -13215,11 +13576,11 @@ async function planRemix(serviceDir, pkg, manifestPath, entryFile, project) {
|
|
|
13215
13576
|
}
|
|
13216
13577
|
async function planSvelteKit(serviceDir, pkg, manifestPath, hooksFile, project) {
|
|
13217
13578
|
const useTs = await isTypeScriptProject(serviceDir);
|
|
13218
|
-
const otelInitFile =
|
|
13579
|
+
const otelInitFile = import_node_path56.default.join(
|
|
13219
13580
|
serviceDir,
|
|
13220
13581
|
useTs ? "src/otel-init.ts" : "src/otel-init.js"
|
|
13221
13582
|
);
|
|
13222
|
-
const resolvedHooksFile = hooksFile ??
|
|
13583
|
+
const resolvedHooksFile = hooksFile ?? import_node_path56.default.join(serviceDir, useTs ? "src/hooks.server.ts" : "src/hooks.server.js");
|
|
13223
13584
|
const dependencyEdits = buildDependencyEdits(pkg, manifestPath);
|
|
13224
13585
|
const generatedFiles = [];
|
|
13225
13586
|
const entrypointEdits = [];
|
|
@@ -13244,7 +13605,7 @@ async function planSvelteKit(serviceDir, pkg, manifestPath, hooksFile, project)
|
|
|
13244
13605
|
});
|
|
13245
13606
|
} else {
|
|
13246
13607
|
try {
|
|
13247
|
-
const raw = await
|
|
13608
|
+
const raw = await import_node_fs37.promises.readFile(hooksFile, "utf8");
|
|
13248
13609
|
if (!fileImportsOtelHook(raw, ["./otel-init"])) {
|
|
13249
13610
|
const lines = raw.split(/\r?\n/);
|
|
13250
13611
|
const firstReal = lines[0]?.startsWith("#!") ? lines[1] ?? "" : lines[0] ?? "";
|
|
@@ -13281,11 +13642,11 @@ async function planSvelteKit(serviceDir, pkg, manifestPath, hooksFile, project)
|
|
|
13281
13642
|
}
|
|
13282
13643
|
async function planNuxt(serviceDir, pkg, manifestPath, project) {
|
|
13283
13644
|
const useTs = await isTypeScriptProject(serviceDir);
|
|
13284
|
-
const otelPluginFile =
|
|
13645
|
+
const otelPluginFile = import_node_path56.default.join(
|
|
13285
13646
|
serviceDir,
|
|
13286
13647
|
useTs ? "server/plugins/otel.ts" : "server/plugins/otel.js"
|
|
13287
13648
|
);
|
|
13288
|
-
const otelInitFile =
|
|
13649
|
+
const otelInitFile = import_node_path56.default.join(
|
|
13289
13650
|
serviceDir,
|
|
13290
13651
|
useTs ? "server/plugins/otel-init.ts" : "server/plugins/otel-init.js"
|
|
13291
13652
|
);
|
|
@@ -13336,19 +13697,19 @@ async function planNuxt(serviceDir, pkg, manifestPath, project) {
|
|
|
13336
13697
|
var ASTRO_MIDDLEWARE_CANDIDATES = ["src/middleware.ts", "src/middleware.js"];
|
|
13337
13698
|
async function findAstroMiddleware(serviceDir) {
|
|
13338
13699
|
for (const rel of ASTRO_MIDDLEWARE_CANDIDATES) {
|
|
13339
|
-
const candidate =
|
|
13700
|
+
const candidate = import_node_path56.default.join(serviceDir, rel);
|
|
13340
13701
|
if (await exists3(candidate)) return candidate;
|
|
13341
13702
|
}
|
|
13342
13703
|
return null;
|
|
13343
13704
|
}
|
|
13344
13705
|
async function planAstro(serviceDir, pkg, manifestPath, project) {
|
|
13345
13706
|
const useTs = await isTypeScriptProject(serviceDir);
|
|
13346
|
-
const otelInitFile =
|
|
13707
|
+
const otelInitFile = import_node_path56.default.join(
|
|
13347
13708
|
serviceDir,
|
|
13348
13709
|
useTs ? "src/otel-init.ts" : "src/otel-init.js"
|
|
13349
13710
|
);
|
|
13350
13711
|
const existingMiddleware = await findAstroMiddleware(serviceDir);
|
|
13351
|
-
const middlewareFile = existingMiddleware ??
|
|
13712
|
+
const middlewareFile = existingMiddleware ?? import_node_path56.default.join(serviceDir, useTs ? "src/middleware.ts" : "src/middleware.js");
|
|
13352
13713
|
const dependencyEdits = buildDependencyEdits(pkg, manifestPath);
|
|
13353
13714
|
const generatedFiles = [];
|
|
13354
13715
|
const entrypointEdits = [];
|
|
@@ -13373,7 +13734,7 @@ async function planAstro(serviceDir, pkg, manifestPath, project) {
|
|
|
13373
13734
|
});
|
|
13374
13735
|
} else {
|
|
13375
13736
|
try {
|
|
13376
|
-
const raw = await
|
|
13737
|
+
const raw = await import_node_fs37.promises.readFile(existingMiddleware, "utf8");
|
|
13377
13738
|
if (!fileImportsOtelHook(raw, ["./otel-init"])) {
|
|
13378
13739
|
const lines = raw.split(/\r?\n/);
|
|
13379
13740
|
const firstReal = lines[0]?.startsWith("#!") ? lines[1] ?? "" : lines[0] ?? "";
|
|
@@ -13444,7 +13805,7 @@ async function findFrameworkDispatch(serviceDir, pkg, manifestPath, project) {
|
|
|
13444
13805
|
}
|
|
13445
13806
|
async function plan(serviceDir, opts) {
|
|
13446
13807
|
const pkg = await readPackageJson2(serviceDir);
|
|
13447
|
-
const manifestPath =
|
|
13808
|
+
const manifestPath = import_node_path56.default.join(serviceDir, "package.json");
|
|
13448
13809
|
const project = opts?.project;
|
|
13449
13810
|
const empty = {
|
|
13450
13811
|
language: "javascript",
|
|
@@ -13479,8 +13840,8 @@ async function plan(serviceDir, opts) {
|
|
|
13479
13840
|
return { ...empty, libOnly: true };
|
|
13480
13841
|
}
|
|
13481
13842
|
const flavor = dispatchEntry(entryFile, pkg);
|
|
13482
|
-
const otelInitFile =
|
|
13483
|
-
const envNeatFile =
|
|
13843
|
+
const otelInitFile = import_node_path56.default.join(import_node_path56.default.dirname(entryFile), otelInitFilename(flavor));
|
|
13844
|
+
const envNeatFile = import_node_path56.default.join(serviceDir, ".env.neat");
|
|
13484
13845
|
const existingDeps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
13485
13846
|
const dependencyEdits = [];
|
|
13486
13847
|
for (const sdk of SDK_PACKAGES) {
|
|
@@ -13504,7 +13865,7 @@ async function plan(serviceDir, opts) {
|
|
|
13504
13865
|
}
|
|
13505
13866
|
const entrypointEdits = [];
|
|
13506
13867
|
try {
|
|
13507
|
-
const raw = await
|
|
13868
|
+
const raw = await import_node_fs37.promises.readFile(entryFile, "utf8");
|
|
13508
13869
|
const lines = raw.split(/\r?\n/);
|
|
13509
13870
|
const firstReal = lines[0]?.startsWith("#!") ? lines[1] ?? "" : lines[0] ?? "";
|
|
13510
13871
|
if (!lineIsOtelInjection(firstReal)) {
|
|
@@ -13549,13 +13910,13 @@ async function plan(serviceDir, opts) {
|
|
|
13549
13910
|
};
|
|
13550
13911
|
}
|
|
13551
13912
|
function isAllowedWritePath(serviceDir, target) {
|
|
13552
|
-
const rel =
|
|
13913
|
+
const rel = import_node_path56.default.relative(serviceDir, target);
|
|
13553
13914
|
if (rel.startsWith("..")) return false;
|
|
13554
|
-
const base =
|
|
13915
|
+
const base = import_node_path56.default.basename(target);
|
|
13555
13916
|
if (base === "package.json") return true;
|
|
13556
13917
|
if (base === ".env.neat") return true;
|
|
13557
13918
|
if (/^otel-init\.(?:js|cjs|mjs|ts)$/.test(base)) return true;
|
|
13558
|
-
const relPosix = rel.split(
|
|
13919
|
+
const relPosix = rel.split(import_node_path56.default.sep).join("/");
|
|
13559
13920
|
if (/^instrumentation(?:\.(?:node|edge))?\.(?:js|cjs|mjs|ts)$/.test(base)) {
|
|
13560
13921
|
if (relPosix === base) return true;
|
|
13561
13922
|
if (relPosix === `src/${base}`) return true;
|
|
@@ -13572,10 +13933,10 @@ function isAllowedWritePath(serviceDir, target) {
|
|
|
13572
13933
|
return false;
|
|
13573
13934
|
}
|
|
13574
13935
|
async function writeAtomic(file, contents) {
|
|
13575
|
-
await
|
|
13936
|
+
await import_node_fs37.promises.mkdir(import_node_path56.default.dirname(file), { recursive: true });
|
|
13576
13937
|
const tmp = `${file}.${process.pid}.${Date.now()}.tmp`;
|
|
13577
|
-
await
|
|
13578
|
-
await
|
|
13938
|
+
await import_node_fs37.promises.writeFile(tmp, contents, "utf8");
|
|
13939
|
+
await import_node_fs37.promises.rename(tmp, file);
|
|
13579
13940
|
}
|
|
13580
13941
|
async function apply(installPlan) {
|
|
13581
13942
|
const { serviceDir } = installPlan;
|
|
@@ -13651,7 +14012,7 @@ async function apply(installPlan) {
|
|
|
13651
14012
|
for (const target of allTargets) {
|
|
13652
14013
|
if (await exists3(target)) {
|
|
13653
14014
|
try {
|
|
13654
|
-
originals.set(target, await
|
|
14015
|
+
originals.set(target, await import_node_fs37.promises.readFile(target, "utf8"));
|
|
13655
14016
|
} catch {
|
|
13656
14017
|
}
|
|
13657
14018
|
}
|
|
@@ -13734,14 +14095,14 @@ async function rollback(installPlan, originals, createdFiles) {
|
|
|
13734
14095
|
const removed = [];
|
|
13735
14096
|
for (const [file, raw] of originals.entries()) {
|
|
13736
14097
|
try {
|
|
13737
|
-
await
|
|
14098
|
+
await import_node_fs37.promises.writeFile(file, raw, "utf8");
|
|
13738
14099
|
restored.push(file);
|
|
13739
14100
|
} catch {
|
|
13740
14101
|
}
|
|
13741
14102
|
}
|
|
13742
14103
|
for (const file of createdFiles) {
|
|
13743
14104
|
try {
|
|
13744
|
-
await
|
|
14105
|
+
await import_node_fs37.promises.unlink(file);
|
|
13745
14106
|
removed.push(file);
|
|
13746
14107
|
} catch {
|
|
13747
14108
|
}
|
|
@@ -13756,8 +14117,8 @@ async function rollback(installPlan, originals, createdFiles) {
|
|
|
13756
14117
|
...removed.map((f) => `removed: ${f}`),
|
|
13757
14118
|
""
|
|
13758
14119
|
];
|
|
13759
|
-
const rollbackPath =
|
|
13760
|
-
await
|
|
14120
|
+
const rollbackPath = import_node_path56.default.join(installPlan.serviceDir, "neat-rollback.patch");
|
|
14121
|
+
await import_node_fs37.promises.writeFile(rollbackPath, lines.join("\n"), "utf8");
|
|
13761
14122
|
}
|
|
13762
14123
|
function injectInstrumentationHook(raw) {
|
|
13763
14124
|
if (raw.includes("instrumentationHook")) return raw;
|
|
@@ -13786,8 +14147,8 @@ var javascriptInstaller = {
|
|
|
13786
14147
|
|
|
13787
14148
|
// src/installers/python.ts
|
|
13788
14149
|
init_cjs_shims();
|
|
13789
|
-
var
|
|
13790
|
-
var
|
|
14150
|
+
var import_node_fs38 = require("fs");
|
|
14151
|
+
var import_node_path57 = __toESM(require("path"), 1);
|
|
13791
14152
|
var SDK_PACKAGES2 = [
|
|
13792
14153
|
{ name: "opentelemetry-distro", version: ">=0.49b0" },
|
|
13793
14154
|
{ name: "opentelemetry-exporter-otlp", version: ">=1.28.0" }
|
|
@@ -13799,7 +14160,7 @@ var OTEL_ENV2 = {
|
|
|
13799
14160
|
};
|
|
13800
14161
|
async function exists4(p) {
|
|
13801
14162
|
try {
|
|
13802
|
-
await
|
|
14163
|
+
await import_node_fs38.promises.stat(p);
|
|
13803
14164
|
return true;
|
|
13804
14165
|
} catch {
|
|
13805
14166
|
return false;
|
|
@@ -13808,7 +14169,7 @@ async function exists4(p) {
|
|
|
13808
14169
|
async function detect2(serviceDir) {
|
|
13809
14170
|
const markers = ["requirements.txt", "pyproject.toml", "setup.py"];
|
|
13810
14171
|
for (const m of markers) {
|
|
13811
|
-
if (await exists4(
|
|
14172
|
+
if (await exists4(import_node_path57.default.join(serviceDir, m))) return true;
|
|
13812
14173
|
}
|
|
13813
14174
|
return false;
|
|
13814
14175
|
}
|
|
@@ -13818,9 +14179,9 @@ function reqPackageName(line) {
|
|
|
13818
14179
|
return head.replace(/[<>=!~].*$/, "").toLowerCase();
|
|
13819
14180
|
}
|
|
13820
14181
|
async function planRequirementsTxtEdits(serviceDir) {
|
|
13821
|
-
const file =
|
|
14182
|
+
const file = import_node_path57.default.join(serviceDir, "requirements.txt");
|
|
13822
14183
|
if (!await exists4(file)) return null;
|
|
13823
|
-
const raw = await
|
|
14184
|
+
const raw = await import_node_fs38.promises.readFile(file, "utf8");
|
|
13824
14185
|
const presentNames = new Set(
|
|
13825
14186
|
raw.split(/\r?\n/).map(reqPackageName).filter((n) => n.length > 0)
|
|
13826
14187
|
);
|
|
@@ -13828,9 +14189,9 @@ async function planRequirementsTxtEdits(serviceDir) {
|
|
|
13828
14189
|
return { manifest: file, missing: [...missing] };
|
|
13829
14190
|
}
|
|
13830
14191
|
async function planProcfileEdits(serviceDir) {
|
|
13831
|
-
const procfile =
|
|
14192
|
+
const procfile = import_node_path57.default.join(serviceDir, "Procfile");
|
|
13832
14193
|
if (!await exists4(procfile)) return [];
|
|
13833
|
-
const raw = await
|
|
14194
|
+
const raw = await import_node_fs38.promises.readFile(procfile, "utf8");
|
|
13834
14195
|
const edits = [];
|
|
13835
14196
|
for (const line of raw.split(/\r?\n/)) {
|
|
13836
14197
|
if (line.length === 0) continue;
|
|
@@ -13882,8 +14243,8 @@ async function applyRequirementsTxt(manifest, edits, original) {
|
|
|
13882
14243
|
const next = `${original}${trailing}${newlines.join("\n")}
|
|
13883
14244
|
`;
|
|
13884
14245
|
const tmp = `${manifest}.${process.pid}.${Date.now()}.tmp`;
|
|
13885
|
-
await
|
|
13886
|
-
await
|
|
14246
|
+
await import_node_fs38.promises.writeFile(tmp, next, "utf8");
|
|
14247
|
+
await import_node_fs38.promises.rename(tmp, manifest);
|
|
13887
14248
|
}
|
|
13888
14249
|
async function applyProcfile(procfile, edits, original) {
|
|
13889
14250
|
let next = original;
|
|
@@ -13892,8 +14253,8 @@ async function applyProcfile(procfile, edits, original) {
|
|
|
13892
14253
|
next = next.replace(e.before, e.after);
|
|
13893
14254
|
}
|
|
13894
14255
|
const tmp = `${procfile}.${process.pid}.${Date.now()}.tmp`;
|
|
13895
|
-
await
|
|
13896
|
-
await
|
|
14256
|
+
await import_node_fs38.promises.writeFile(tmp, next, "utf8");
|
|
14257
|
+
await import_node_fs38.promises.rename(tmp, procfile);
|
|
13897
14258
|
}
|
|
13898
14259
|
async function apply2(installPlan) {
|
|
13899
14260
|
const { serviceDir } = installPlan;
|
|
@@ -13906,7 +14267,7 @@ async function apply2(installPlan) {
|
|
|
13906
14267
|
const originals = /* @__PURE__ */ new Map();
|
|
13907
14268
|
for (const file of touched) {
|
|
13908
14269
|
try {
|
|
13909
|
-
originals.set(file, await
|
|
14270
|
+
originals.set(file, await import_node_fs38.promises.readFile(file, "utf8"));
|
|
13910
14271
|
} catch {
|
|
13911
14272
|
}
|
|
13912
14273
|
}
|
|
@@ -13917,7 +14278,7 @@ async function apply2(installPlan) {
|
|
|
13917
14278
|
if (raw === void 0) {
|
|
13918
14279
|
throw new Error(`python installer: cannot read ${file} during apply`);
|
|
13919
14280
|
}
|
|
13920
|
-
const base =
|
|
14281
|
+
const base = import_node_path57.default.basename(file);
|
|
13921
14282
|
if (base === "requirements.txt") {
|
|
13922
14283
|
const edits = installPlan.dependencyEdits.filter((e) => e.file === file);
|
|
13923
14284
|
if (edits.length > 0) {
|
|
@@ -13942,7 +14303,7 @@ async function rollback2(installPlan, originals) {
|
|
|
13942
14303
|
const restored = [];
|
|
13943
14304
|
for (const [file, raw] of originals.entries()) {
|
|
13944
14305
|
try {
|
|
13945
|
-
await
|
|
14306
|
+
await import_node_fs38.promises.writeFile(file, raw, "utf8");
|
|
13946
14307
|
restored.push(file);
|
|
13947
14308
|
} catch {
|
|
13948
14309
|
}
|
|
@@ -13956,8 +14317,8 @@ async function rollback2(installPlan, originals) {
|
|
|
13956
14317
|
...restored.map((f) => `restored: ${f}`),
|
|
13957
14318
|
""
|
|
13958
14319
|
];
|
|
13959
|
-
const rollbackPath =
|
|
13960
|
-
await
|
|
14320
|
+
const rollbackPath = import_node_path57.default.join(installPlan.serviceDir, "neat-rollback.patch");
|
|
14321
|
+
await import_node_fs38.promises.writeFile(rollbackPath, lines.join("\n"), "utf8");
|
|
13961
14322
|
}
|
|
13962
14323
|
var pythonInstaller = {
|
|
13963
14324
|
name: "python",
|
|
@@ -14080,10 +14441,10 @@ function renderPatch(sections) {
|
|
|
14080
14441
|
|
|
14081
14442
|
// src/orchestrator.ts
|
|
14082
14443
|
init_cjs_shims();
|
|
14083
|
-
var
|
|
14444
|
+
var import_node_fs39 = require("fs");
|
|
14084
14445
|
var import_node_http = __toESM(require("http"), 1);
|
|
14085
14446
|
var import_node_net = __toESM(require("net"), 1);
|
|
14086
|
-
var
|
|
14447
|
+
var import_node_path58 = __toESM(require("path"), 1);
|
|
14087
14448
|
var import_node_child_process3 = require("child_process");
|
|
14088
14449
|
var import_node_readline = __toESM(require("readline"), 1);
|
|
14089
14450
|
async function extractAndPersist(opts) {
|
|
@@ -14092,7 +14453,7 @@ async function extractAndPersist(opts) {
|
|
|
14092
14453
|
const graphKey = opts.projectExplicit ? opts.project : DEFAULT_PROJECT;
|
|
14093
14454
|
resetGraph(graphKey);
|
|
14094
14455
|
const graph = getGraph(graphKey);
|
|
14095
|
-
const projectPaths = pathsForProject(graphKey,
|
|
14456
|
+
const projectPaths = pathsForProject(graphKey, import_node_path58.default.join(opts.scanPath, "neat-out"));
|
|
14096
14457
|
const extraction = await extractFromDirectory(graph, opts.scanPath, {
|
|
14097
14458
|
errorsPath: projectPaths.errorsPath
|
|
14098
14459
|
});
|
|
@@ -14144,7 +14505,7 @@ async function applyInstallersOver(services, project, options = {}) {
|
|
|
14144
14505
|
libOnly++;
|
|
14145
14506
|
const appDeps = svc.pkg ? appFrameworkDependencies(svc.pkg) : [];
|
|
14146
14507
|
if (appDeps.length > 0) {
|
|
14147
|
-
const svcName =
|
|
14508
|
+
const svcName = import_node_path58.default.basename(svc.dir);
|
|
14148
14509
|
const list = appDeps.join(", ");
|
|
14149
14510
|
console.warn(
|
|
14150
14511
|
`neat: runtime layer won't engage for ${svcName}: no entry point found.
|
|
@@ -14157,7 +14518,7 @@ async function applyInstallersOver(services, project, options = {}) {
|
|
|
14157
14518
|
console.log(`skipping ${svc.dir}: browser bundle; browser-OTel support lands in a future release.`);
|
|
14158
14519
|
} else if (outcome.outcome === "react-native") {
|
|
14159
14520
|
reactNative++;
|
|
14160
|
-
const svcName =
|
|
14521
|
+
const svcName = import_node_path58.default.basename(svc.dir);
|
|
14161
14522
|
console.log(
|
|
14162
14523
|
`neat: ${svc.dir} detected as React Native / Expo
|
|
14163
14524
|
The installer doesn't cover this runtime deterministically.
|
|
@@ -14168,7 +14529,7 @@ async function applyInstallersOver(services, project, options = {}) {
|
|
|
14168
14529
|
);
|
|
14169
14530
|
} else if (outcome.outcome === "bun") {
|
|
14170
14531
|
bun++;
|
|
14171
|
-
const svcName =
|
|
14532
|
+
const svcName = import_node_path58.default.basename(svc.dir);
|
|
14172
14533
|
console.log(
|
|
14173
14534
|
`neat: ${svc.dir} detected as Bun
|
|
14174
14535
|
The installer doesn't cover this runtime deterministically.
|
|
@@ -14179,7 +14540,7 @@ async function applyInstallersOver(services, project, options = {}) {
|
|
|
14179
14540
|
);
|
|
14180
14541
|
} else if (outcome.outcome === "deno") {
|
|
14181
14542
|
deno++;
|
|
14182
|
-
const svcName =
|
|
14543
|
+
const svcName = import_node_path58.default.basename(svc.dir);
|
|
14183
14544
|
console.log(
|
|
14184
14545
|
`neat: ${svc.dir} detected as Deno
|
|
14185
14546
|
The installer doesn't cover this runtime deterministically.
|
|
@@ -14190,7 +14551,7 @@ async function applyInstallersOver(services, project, options = {}) {
|
|
|
14190
14551
|
);
|
|
14191
14552
|
} else if (outcome.outcome === "cloudflare-workers") {
|
|
14192
14553
|
cloudflareWorkers++;
|
|
14193
|
-
const svcName =
|
|
14554
|
+
const svcName = import_node_path58.default.basename(svc.dir);
|
|
14194
14555
|
console.log(
|
|
14195
14556
|
`neat: ${svc.dir} detected as Cloudflare Workers
|
|
14196
14557
|
The installer doesn't cover this runtime deterministically.
|
|
@@ -14201,7 +14562,7 @@ async function applyInstallersOver(services, project, options = {}) {
|
|
|
14201
14562
|
);
|
|
14202
14563
|
} else if (outcome.outcome === "electron") {
|
|
14203
14564
|
electron++;
|
|
14204
|
-
const svcName =
|
|
14565
|
+
const svcName = import_node_path58.default.basename(svc.dir);
|
|
14205
14566
|
console.log(
|
|
14206
14567
|
`neat: ${svc.dir} detected as Electron
|
|
14207
14568
|
The installer doesn't cover this runtime deterministically.
|
|
@@ -14214,7 +14575,7 @@ async function applyInstallersOver(services, project, options = {}) {
|
|
|
14214
14575
|
if (svc.pkg && (outcome.outcome === "instrumented" || outcome.outcome === "already-instrumented")) {
|
|
14215
14576
|
const gaps = uninstrumentedLibraries(svc.pkg);
|
|
14216
14577
|
if (gaps.length > 0) {
|
|
14217
|
-
const svcName =
|
|
14578
|
+
const svcName = import_node_path58.default.basename(svc.dir);
|
|
14218
14579
|
const list = gaps.join(", ");
|
|
14219
14580
|
const subject = gaps.length === 1 ? "this library" : "these libraries";
|
|
14220
14581
|
const aux = gaps.length === 1 ? "isn't" : "aren't";
|
|
@@ -14420,24 +14781,24 @@ async function persistedPortsFor(scanPath) {
|
|
|
14420
14781
|
return { rest: record.ports.rest, otlp: record.ports.otlp, web: record.ports.web };
|
|
14421
14782
|
}
|
|
14422
14783
|
async function acquireSpawnLock(scanPath) {
|
|
14423
|
-
const lockPath =
|
|
14424
|
-
await
|
|
14784
|
+
const lockPath = import_node_path58.default.join(scanPath, "neat-out", "daemon.spawn.lock");
|
|
14785
|
+
await import_node_fs39.promises.mkdir(import_node_path58.default.dirname(lockPath), { recursive: true });
|
|
14425
14786
|
const STALE_LOCK_MS = 6e4;
|
|
14426
14787
|
try {
|
|
14427
|
-
const fd = await
|
|
14788
|
+
const fd = await import_node_fs39.promises.open(lockPath, "wx");
|
|
14428
14789
|
await fd.writeFile(`${process.pid}
|
|
14429
14790
|
`, "utf8");
|
|
14430
14791
|
await fd.close();
|
|
14431
14792
|
return async () => {
|
|
14432
|
-
await
|
|
14793
|
+
await import_node_fs39.promises.unlink(lockPath).catch(() => {
|
|
14433
14794
|
});
|
|
14434
14795
|
};
|
|
14435
14796
|
} catch (err) {
|
|
14436
14797
|
if (err.code !== "EEXIST") return null;
|
|
14437
14798
|
try {
|
|
14438
|
-
const stat = await
|
|
14799
|
+
const stat = await import_node_fs39.promises.stat(lockPath);
|
|
14439
14800
|
if (Date.now() - stat.mtimeMs > STALE_LOCK_MS) {
|
|
14440
|
-
await
|
|
14801
|
+
await import_node_fs39.promises.unlink(lockPath).catch(() => {
|
|
14441
14802
|
});
|
|
14442
14803
|
return acquireSpawnLock(scanPath);
|
|
14443
14804
|
}
|
|
@@ -14466,13 +14827,13 @@ async function healthIsForProject(restPort, project) {
|
|
|
14466
14827
|
return false;
|
|
14467
14828
|
}
|
|
14468
14829
|
function daemonLogPath(projectPath2) {
|
|
14469
|
-
return
|
|
14830
|
+
return import_node_path58.default.join(projectPath2, "neat-out", "daemon.log");
|
|
14470
14831
|
}
|
|
14471
14832
|
function spawnDaemonDetached(spec) {
|
|
14472
|
-
const here =
|
|
14833
|
+
const here = import_node_path58.default.dirname(new URL(importMetaUrl).pathname);
|
|
14473
14834
|
const candidates = [
|
|
14474
|
-
|
|
14475
|
-
|
|
14835
|
+
import_node_path58.default.join(here, "neatd.cjs"),
|
|
14836
|
+
import_node_path58.default.join(here, "neatd.js")
|
|
14476
14837
|
];
|
|
14477
14838
|
let entry2 = null;
|
|
14478
14839
|
const fsSync = require("fs");
|
|
@@ -14502,7 +14863,7 @@ function spawnDaemonDetached(spec) {
|
|
|
14502
14863
|
let logFd = null;
|
|
14503
14864
|
if (spec) {
|
|
14504
14865
|
const logPath = daemonLogPath(spec.projectPath);
|
|
14505
|
-
fsSync.mkdirSync(
|
|
14866
|
+
fsSync.mkdirSync(import_node_path58.default.dirname(logPath), { recursive: true });
|
|
14506
14867
|
logFd = fsSync.openSync(logPath, "a");
|
|
14507
14868
|
}
|
|
14508
14869
|
const child = (0, import_node_child_process3.spawn)(process.execPath, [entry2, "start"], {
|
|
@@ -14541,7 +14902,7 @@ async function runOrchestrator(opts) {
|
|
|
14541
14902
|
browser: "skipped"
|
|
14542
14903
|
}
|
|
14543
14904
|
};
|
|
14544
|
-
const stat = await
|
|
14905
|
+
const stat = await import_node_fs39.promises.stat(opts.scanPath).catch(() => null);
|
|
14545
14906
|
if (!stat || !stat.isDirectory()) {
|
|
14546
14907
|
console.error(`neat: ${opts.scanPath} is not a directory`);
|
|
14547
14908
|
result.exitCode = 2;
|
|
@@ -14701,7 +15062,7 @@ async function runOrchestrator(opts) {
|
|
|
14701
15062
|
result.steps.browser = openBrowser(dashboardUrl);
|
|
14702
15063
|
}
|
|
14703
15064
|
const daemonRunning = result.steps.daemon === "spawned" || result.steps.daemon === "already-running";
|
|
14704
|
-
const daemonLog = daemonRunning ?
|
|
15065
|
+
const daemonLog = daemonRunning ? import_node_path58.default.relative(opts.scanPath, daemonLogPath(opts.scanPath)) : null;
|
|
14705
15066
|
printSummary(result, graph, dashboardUrl, daemonLog);
|
|
14706
15067
|
return result;
|
|
14707
15068
|
}
|
|
@@ -15069,11 +15430,11 @@ async function runConnectorCommand(rawArgs, deps = {}) {
|
|
|
15069
15430
|
|
|
15070
15431
|
// src/cli-verbs.ts
|
|
15071
15432
|
init_cjs_shims();
|
|
15072
|
-
var
|
|
15433
|
+
var import_node_path59 = __toESM(require("path"), 1);
|
|
15073
15434
|
|
|
15074
15435
|
// src/cli-client.ts
|
|
15075
15436
|
init_cjs_shims();
|
|
15076
|
-
var
|
|
15437
|
+
var import_types47 = require("@neat.is/types");
|
|
15077
15438
|
var HttpError = class extends Error {
|
|
15078
15439
|
constructor(status2, message, responseBody = "") {
|
|
15079
15440
|
super(message);
|
|
@@ -15098,10 +15459,10 @@ function createHttpClient(baseUrl, bearerToken) {
|
|
|
15098
15459
|
const root = baseUrl.replace(/\/$/, "");
|
|
15099
15460
|
const authHeader = bearerToken && bearerToken.length > 0 ? { authorization: `Bearer ${bearerToken}` } : {};
|
|
15100
15461
|
return {
|
|
15101
|
-
async get(
|
|
15462
|
+
async get(path61) {
|
|
15102
15463
|
let res;
|
|
15103
15464
|
try {
|
|
15104
|
-
res = await fetch(`${root}${
|
|
15465
|
+
res = await fetch(`${root}${path61}`, {
|
|
15105
15466
|
headers: { ...authHeader }
|
|
15106
15467
|
});
|
|
15107
15468
|
} catch (err) {
|
|
@@ -15113,16 +15474,16 @@ function createHttpClient(baseUrl, bearerToken) {
|
|
|
15113
15474
|
const body = await res.text().catch(() => "");
|
|
15114
15475
|
throw new HttpError(
|
|
15115
15476
|
res.status,
|
|
15116
|
-
`${res.status} ${res.statusText} on GET ${
|
|
15477
|
+
`${res.status} ${res.statusText} on GET ${path61}: ${body}`,
|
|
15117
15478
|
body
|
|
15118
15479
|
);
|
|
15119
15480
|
}
|
|
15120
15481
|
return await res.json();
|
|
15121
15482
|
},
|
|
15122
|
-
async post(
|
|
15483
|
+
async post(path61, body) {
|
|
15123
15484
|
let res;
|
|
15124
15485
|
try {
|
|
15125
|
-
res = await fetch(`${root}${
|
|
15486
|
+
res = await fetch(`${root}${path61}`, {
|
|
15126
15487
|
method: "POST",
|
|
15127
15488
|
headers: { "content-type": "application/json", ...authHeader },
|
|
15128
15489
|
body: JSON.stringify(body)
|
|
@@ -15136,7 +15497,7 @@ function createHttpClient(baseUrl, bearerToken) {
|
|
|
15136
15497
|
const text = await res.text().catch(() => "");
|
|
15137
15498
|
throw new HttpError(
|
|
15138
15499
|
res.status,
|
|
15139
|
-
`${res.status} ${res.statusText} on POST ${
|
|
15500
|
+
`${res.status} ${res.statusText} on POST ${path61}: ${text}`,
|
|
15140
15501
|
text
|
|
15141
15502
|
);
|
|
15142
15503
|
}
|
|
@@ -15150,12 +15511,12 @@ function projectPath(project, suffix) {
|
|
|
15150
15511
|
}
|
|
15151
15512
|
async function runRootCause(client, input) {
|
|
15152
15513
|
const qs = input.errorId ? `?errorId=${encodeURIComponent(input.errorId)}` : "";
|
|
15153
|
-
const
|
|
15514
|
+
const path61 = projectPath(
|
|
15154
15515
|
input.project,
|
|
15155
15516
|
`/graph/root-cause/${encodeURIComponent(input.errorNode)}${qs}`
|
|
15156
15517
|
);
|
|
15157
15518
|
try {
|
|
15158
|
-
const result = await client.get(
|
|
15519
|
+
const result = await client.get(path61);
|
|
15159
15520
|
const arrowPath = result.traversalPath.join(" \u2190 ");
|
|
15160
15521
|
const provenances = result.edgeProvenances.length ? result.edgeProvenances.join(", ") : "(direct, no edges traversed)";
|
|
15161
15522
|
const summary = `Root cause for ${input.errorNode} is ${result.rootCauseNode}. ` + result.rootCauseReason + (result.fixRecommendation ? ` Recommended fix: ${result.fixRecommendation}.` : "");
|
|
@@ -15181,12 +15542,12 @@ async function runRootCause(client, input) {
|
|
|
15181
15542
|
}
|
|
15182
15543
|
async function runBlastRadius(client, input) {
|
|
15183
15544
|
const qs = input.depth !== void 0 ? `?depth=${input.depth}` : "";
|
|
15184
|
-
const
|
|
15545
|
+
const path61 = projectPath(
|
|
15185
15546
|
input.project,
|
|
15186
15547
|
`/graph/blast-radius/${encodeURIComponent(input.nodeId)}${qs}`
|
|
15187
15548
|
);
|
|
15188
15549
|
try {
|
|
15189
|
-
const result = await client.get(
|
|
15550
|
+
const result = await client.get(path61);
|
|
15190
15551
|
if (result.totalAffected === 0) {
|
|
15191
15552
|
return {
|
|
15192
15553
|
summary: `${result.origin} has no dependents. Nothing else would break if it failed.`
|
|
@@ -15215,17 +15576,17 @@ async function runBlastRadius(client, input) {
|
|
|
15215
15576
|
}
|
|
15216
15577
|
}
|
|
15217
15578
|
function formatBlastEntry(n) {
|
|
15218
|
-
const tag = n.edgeProvenance ===
|
|
15579
|
+
const tag = n.edgeProvenance === import_types47.Provenance.STALE ? " [STALE \u2014 last seen too long ago]" : "";
|
|
15219
15580
|
return ` \u2022 ${n.nodeId} (distance ${n.distance}, ${n.edgeProvenance})${tag}`;
|
|
15220
15581
|
}
|
|
15221
15582
|
async function runDependencies(client, input) {
|
|
15222
15583
|
const depth = input.depth ?? 3;
|
|
15223
|
-
const
|
|
15584
|
+
const path61 = projectPath(
|
|
15224
15585
|
input.project,
|
|
15225
15586
|
`/graph/dependencies/${encodeURIComponent(input.nodeId)}?depth=${depth}`
|
|
15226
15587
|
);
|
|
15227
15588
|
try {
|
|
15228
|
-
const result = await client.get(
|
|
15589
|
+
const result = await client.get(path61);
|
|
15229
15590
|
if (result.total === 0) {
|
|
15230
15591
|
return {
|
|
15231
15592
|
summary: depth === 1 ? `${input.nodeId} has no direct dependencies in the graph.` : `${input.nodeId} has no dependencies (BFS to depth ${depth}).`
|
|
@@ -15272,7 +15633,7 @@ async function runObservedDependencies(client, input) {
|
|
|
15272
15633
|
if (result.observed) {
|
|
15273
15634
|
return {
|
|
15274
15635
|
summary: `${input.nodeId} makes no outbound runtime calls, but OTel has observed it receiving traffic on ${result.inboundObservedCount} inbound call path${result.inboundObservedCount === 1 ? "" : "s"} \u2014 it's a pure receiver.`,
|
|
15275
|
-
provenance:
|
|
15636
|
+
provenance: import_types47.Provenance.OBSERVED
|
|
15276
15637
|
};
|
|
15277
15638
|
}
|
|
15278
15639
|
const note = result.hasExtractedOutbound ? " Static (EXTRACTED) dependencies exist but no runtime traffic has been seen \u2014 is OTel running?" : "";
|
|
@@ -15282,7 +15643,7 @@ async function runObservedDependencies(client, input) {
|
|
|
15282
15643
|
return {
|
|
15283
15644
|
summary: `${input.nodeId} has ${result.dependencies.length} runtime dependenc${result.dependencies.length === 1 ? "y" : "ies"} confirmed by OTel.`,
|
|
15284
15645
|
block: blockLines.join("\n"),
|
|
15285
|
-
provenance:
|
|
15646
|
+
provenance: import_types47.Provenance.OBSERVED
|
|
15286
15647
|
};
|
|
15287
15648
|
} catch (err) {
|
|
15288
15649
|
if (err instanceof HttpError && err.status === 404) {
|
|
@@ -15317,9 +15678,9 @@ function formatDuration(ms) {
|
|
|
15317
15678
|
return `${Math.round(h / 24)}d`;
|
|
15318
15679
|
}
|
|
15319
15680
|
async function runIncidents(client, input) {
|
|
15320
|
-
const
|
|
15681
|
+
const path61 = input.nodeId ? projectPath(input.project, `/incidents/${encodeURIComponent(input.nodeId)}`) : projectPath(input.project, "/incidents");
|
|
15321
15682
|
try {
|
|
15322
|
-
const body = await client.get(
|
|
15683
|
+
const body = await client.get(path61);
|
|
15323
15684
|
const events = body.events;
|
|
15324
15685
|
if (events.length === 0) {
|
|
15325
15686
|
return {
|
|
@@ -15336,7 +15697,7 @@ async function runIncidents(client, input) {
|
|
|
15336
15697
|
return {
|
|
15337
15698
|
summary: `${target} has ${body.total} recorded incident${body.total === 1 ? "" : "s"}; showing the ${ordered.length} most recent.`,
|
|
15338
15699
|
block: blockLines.join("\n"),
|
|
15339
|
-
provenance:
|
|
15700
|
+
provenance: import_types47.Provenance.OBSERVED
|
|
15340
15701
|
};
|
|
15341
15702
|
} catch (err) {
|
|
15342
15703
|
if (err instanceof HttpError && err.status === 404) {
|
|
@@ -15445,7 +15806,7 @@ async function runStaleEdges(client, input) {
|
|
|
15445
15806
|
return {
|
|
15446
15807
|
summary: `${events.length} stale-edge transition${events.length === 1 ? "" : "s"} recorded${input.edgeType ? ` for ${input.edgeType}` : ""}.`,
|
|
15447
15808
|
block: blockLines.join("\n"),
|
|
15448
|
-
provenance:
|
|
15809
|
+
provenance: import_types47.Provenance.STALE
|
|
15449
15810
|
};
|
|
15450
15811
|
}
|
|
15451
15812
|
async function runPolicies(client, input) {
|
|
@@ -15609,7 +15970,7 @@ async function resolveProjectEntry(opts) {
|
|
|
15609
15970
|
const cwd = opts.cwd ?? process.cwd();
|
|
15610
15971
|
const resolvedCwd = await normalizeProjectPath(cwd);
|
|
15611
15972
|
for (const entry2 of entries) {
|
|
15612
|
-
if (resolvedCwd === entry2.path || resolvedCwd.startsWith(`${entry2.path}${
|
|
15973
|
+
if (resolvedCwd === entry2.path || resolvedCwd.startsWith(`${entry2.path}${import_node_path59.default.sep}`)) {
|
|
15613
15974
|
return entry2;
|
|
15614
15975
|
}
|
|
15615
15976
|
}
|
|
@@ -15762,7 +16123,7 @@ async function runSync(opts) {
|
|
|
15762
16123
|
}
|
|
15763
16124
|
|
|
15764
16125
|
// src/cli.ts
|
|
15765
|
-
var
|
|
16126
|
+
var import_types48 = require("@neat.is/types");
|
|
15766
16127
|
function isNpxInvocation() {
|
|
15767
16128
|
if (process.env.npm_command === "exec") return true;
|
|
15768
16129
|
const execpath = process.env.npm_execpath ?? "";
|
|
@@ -16055,7 +16416,7 @@ async function buildPatchSections(services, project) {
|
|
|
16055
16416
|
}
|
|
16056
16417
|
async function runInit(opts) {
|
|
16057
16418
|
const written = [];
|
|
16058
|
-
const stat = await
|
|
16419
|
+
const stat = await import_node_fs40.promises.stat(opts.scanPath).catch(() => null);
|
|
16059
16420
|
if (!stat || !stat.isDirectory()) {
|
|
16060
16421
|
console.error(`neat init: ${opts.scanPath} is not a directory`);
|
|
16061
16422
|
return { exitCode: 2, writtenFiles: written };
|
|
@@ -16064,13 +16425,13 @@ async function runInit(opts) {
|
|
|
16064
16425
|
printDiscoveryReport(opts, services);
|
|
16065
16426
|
const sections = opts.noInstall ? [] : await buildPatchSections(services, opts.project);
|
|
16066
16427
|
const patch = renderPatch(sections);
|
|
16067
|
-
const patchPath =
|
|
16428
|
+
const patchPath = import_node_path60.default.join(opts.scanPath, "neat.patch");
|
|
16068
16429
|
if (opts.dryRun) {
|
|
16069
|
-
await
|
|
16430
|
+
await import_node_fs40.promises.writeFile(patchPath, patch, "utf8");
|
|
16070
16431
|
written.push(patchPath);
|
|
16071
16432
|
console.log(`dry-run: patch written to ${patchPath}`);
|
|
16072
|
-
const gitignorePath =
|
|
16073
|
-
const gitignoreExists = await
|
|
16433
|
+
const gitignorePath = import_node_path60.default.join(opts.scanPath, ".gitignore");
|
|
16434
|
+
const gitignoreExists = await import_node_fs40.promises.stat(gitignorePath).then(() => true).catch(() => false);
|
|
16074
16435
|
const verb = gitignoreExists ? "append" : "create";
|
|
16075
16436
|
console.log(`dry-run: would ${verb} ${gitignorePath} (add neat-out/)`);
|
|
16076
16437
|
console.log("rerun without --dry-run to register and snapshot.");
|
|
@@ -16081,9 +16442,9 @@ async function runInit(opts) {
|
|
|
16081
16442
|
const graph = getGraph(graphKey);
|
|
16082
16443
|
const projectPaths = pathsForProject(
|
|
16083
16444
|
graphKey,
|
|
16084
|
-
|
|
16445
|
+
import_node_path60.default.join(opts.scanPath, "neat-out")
|
|
16085
16446
|
);
|
|
16086
|
-
const errorsPath =
|
|
16447
|
+
const errorsPath = import_node_path60.default.join(import_node_path60.default.dirname(opts.outPath), import_node_path60.default.basename(projectPaths.errorsPath));
|
|
16087
16448
|
const result = await extractFromDirectory(graph, opts.scanPath, { errorsPath });
|
|
16088
16449
|
await saveGraphToDisk(graph, opts.outPath);
|
|
16089
16450
|
written.push(opts.outPath);
|
|
@@ -16162,7 +16523,7 @@ async function runInit(opts) {
|
|
|
16162
16523
|
console.log("Run `npm install` (or your language equivalent) to refresh lockfiles.");
|
|
16163
16524
|
}
|
|
16164
16525
|
} else {
|
|
16165
|
-
await
|
|
16526
|
+
await import_node_fs40.promises.writeFile(patchPath, patch, "utf8");
|
|
16166
16527
|
written.push(patchPath);
|
|
16167
16528
|
}
|
|
16168
16529
|
}
|
|
@@ -16202,9 +16563,9 @@ var CLAUDE_SKILL_CONFIG = {
|
|
|
16202
16563
|
};
|
|
16203
16564
|
function claudeConfigPath() {
|
|
16204
16565
|
const override = process.env.NEAT_CLAUDE_CONFIG;
|
|
16205
|
-
if (override && override.length > 0) return
|
|
16566
|
+
if (override && override.length > 0) return import_node_path60.default.resolve(override);
|
|
16206
16567
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
16207
|
-
return
|
|
16568
|
+
return import_node_path60.default.join(home, ".claude.json");
|
|
16208
16569
|
}
|
|
16209
16570
|
async function runSkill(opts) {
|
|
16210
16571
|
const snippet2 = JSON.stringify(CLAUDE_SKILL_CONFIG, null, 2) + "\n";
|
|
@@ -16216,7 +16577,7 @@ async function runSkill(opts) {
|
|
|
16216
16577
|
const target = claudeConfigPath();
|
|
16217
16578
|
let existing = {};
|
|
16218
16579
|
try {
|
|
16219
|
-
existing = JSON.parse(await
|
|
16580
|
+
existing = JSON.parse(await import_node_fs40.promises.readFile(target, "utf8"));
|
|
16220
16581
|
} catch (err) {
|
|
16221
16582
|
if (err.code !== "ENOENT") {
|
|
16222
16583
|
console.error(`neat skill: failed to read ${target} \u2014 ${err.message}`);
|
|
@@ -16228,8 +16589,8 @@ async function runSkill(opts) {
|
|
|
16228
16589
|
...existing,
|
|
16229
16590
|
mcpServers: { ...mcp, neat: CLAUDE_SKILL_CONFIG.mcpServers.neat }
|
|
16230
16591
|
};
|
|
16231
|
-
await
|
|
16232
|
-
await
|
|
16592
|
+
await import_node_fs40.promises.mkdir(import_node_path60.default.dirname(target), { recursive: true });
|
|
16593
|
+
await import_node_fs40.promises.writeFile(target, JSON.stringify(merged, null, 2) + "\n", "utf8");
|
|
16233
16594
|
console.log(`neat skill: wrote mcpServers.neat to ${target}`);
|
|
16234
16595
|
console.log("restart Claude Code to pick up the new MCP server.");
|
|
16235
16596
|
return { exitCode: 0 };
|
|
@@ -16283,12 +16644,12 @@ async function main() {
|
|
|
16283
16644
|
console.error("neat init: --apply and --dry-run are mutually exclusive");
|
|
16284
16645
|
process.exit(2);
|
|
16285
16646
|
}
|
|
16286
|
-
const scanPath =
|
|
16647
|
+
const scanPath = import_node_path60.default.resolve(target);
|
|
16287
16648
|
const projectExplicit = parsed.project !== null;
|
|
16288
|
-
const projectName = projectExplicit ? project :
|
|
16649
|
+
const projectName = projectExplicit ? project : import_node_path60.default.basename(scanPath);
|
|
16289
16650
|
const projectKey = projectExplicit ? project : DEFAULT_PROJECT;
|
|
16290
|
-
const fallback = pathsForProject(projectKey,
|
|
16291
|
-
const outPath =
|
|
16651
|
+
const fallback = pathsForProject(projectKey, import_node_path60.default.join(scanPath, "neat-out")).snapshotPath;
|
|
16652
|
+
const outPath = import_node_path60.default.resolve(process.env.NEAT_OUT_PATH ?? fallback);
|
|
16292
16653
|
const result = await runInit({
|
|
16293
16654
|
scanPath,
|
|
16294
16655
|
outPath,
|
|
@@ -16309,21 +16670,21 @@ async function main() {
|
|
|
16309
16670
|
usage();
|
|
16310
16671
|
process.exit(2);
|
|
16311
16672
|
}
|
|
16312
|
-
const scanPath =
|
|
16313
|
-
const stat = await
|
|
16673
|
+
const scanPath = import_node_path60.default.resolve(target);
|
|
16674
|
+
const stat = await import_node_fs40.promises.stat(scanPath).catch(() => null);
|
|
16314
16675
|
if (!stat || !stat.isDirectory()) {
|
|
16315
16676
|
console.error(`neat watch: ${scanPath} is not a directory`);
|
|
16316
16677
|
process.exit(2);
|
|
16317
16678
|
}
|
|
16318
|
-
const projectPaths = pathsForProject(project,
|
|
16319
|
-
const outPath =
|
|
16320
|
-
const errorsPath =
|
|
16321
|
-
process.env.NEAT_ERRORS_PATH ??
|
|
16679
|
+
const projectPaths = pathsForProject(project, import_node_path60.default.join(scanPath, "neat-out"));
|
|
16680
|
+
const outPath = import_node_path60.default.resolve(process.env.NEAT_OUT_PATH ?? projectPaths.snapshotPath);
|
|
16681
|
+
const errorsPath = import_node_path60.default.resolve(
|
|
16682
|
+
process.env.NEAT_ERRORS_PATH ?? import_node_path60.default.join(import_node_path60.default.dirname(outPath), import_node_path60.default.basename(projectPaths.errorsPath))
|
|
16322
16683
|
);
|
|
16323
|
-
const staleEventsPath =
|
|
16324
|
-
process.env.NEAT_STALE_EVENTS_PATH ??
|
|
16684
|
+
const staleEventsPath = import_node_path60.default.resolve(
|
|
16685
|
+
process.env.NEAT_STALE_EVENTS_PATH ?? import_node_path60.default.join(import_node_path60.default.dirname(outPath), import_node_path60.default.basename(projectPaths.staleEventsPath))
|
|
16325
16686
|
);
|
|
16326
|
-
const embeddingsCachePath = process.env.NEAT_EMBEDDINGS_CACHE_PATH ?
|
|
16687
|
+
const embeddingsCachePath = process.env.NEAT_EMBEDDINGS_CACHE_PATH ? import_node_path60.default.resolve(process.env.NEAT_EMBEDDINGS_CACHE_PATH) : void 0;
|
|
16327
16688
|
const handle = await startWatch(getGraph(project), {
|
|
16328
16689
|
scanPath,
|
|
16329
16690
|
outPath,
|
|
@@ -16506,11 +16867,11 @@ async function main() {
|
|
|
16506
16867
|
process.exit(1);
|
|
16507
16868
|
}
|
|
16508
16869
|
async function tryOrchestrator(cmd, parsed) {
|
|
16509
|
-
const scanPath =
|
|
16510
|
-
const stat = await
|
|
16870
|
+
const scanPath = import_node_path60.default.resolve(cmd);
|
|
16871
|
+
const stat = await import_node_fs40.promises.stat(scanPath).catch(() => null);
|
|
16511
16872
|
if (!stat || !stat.isDirectory()) return null;
|
|
16512
16873
|
const projectExplicit = parsed.project !== null;
|
|
16513
|
-
const projectName = projectExplicit ? parsed.project :
|
|
16874
|
+
const projectName = projectExplicit ? parsed.project : import_node_path60.default.basename(scanPath);
|
|
16514
16875
|
const result = await runOrchestrator({
|
|
16515
16876
|
scanPath,
|
|
16516
16877
|
project: projectName,
|
|
@@ -16699,10 +17060,10 @@ async function runQueryVerb(cmd, parsed) {
|
|
|
16699
17060
|
const parts = parsed.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
16700
17061
|
const out = [];
|
|
16701
17062
|
for (const p of parts) {
|
|
16702
|
-
const r =
|
|
17063
|
+
const r = import_types48.DivergenceTypeSchema.safeParse(p);
|
|
16703
17064
|
if (!r.success) {
|
|
16704
17065
|
console.error(
|
|
16705
|
-
`neat divergences: unknown --type "${p}". allowed: ${
|
|
17066
|
+
`neat divergences: unknown --type "${p}". allowed: ${import_types48.DivergenceTypeSchema.options.join(", ")}`
|
|
16706
17067
|
);
|
|
16707
17068
|
return 2;
|
|
16708
17069
|
}
|