@neat.is/core 0.4.28-dev.20260712 → 0.4.28-dev.20260714
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-5T6J3WKC.js → chunk-6HPRD53A.js} +734 -144
- package/dist/chunk-6HPRD53A.js.map +1 -0
- package/dist/{chunk-UJ6WBLIE.js → chunk-AOLXZWRU.js} +163 -345
- package/dist/chunk-AOLXZWRU.js.map +1 -0
- package/dist/cli.cjs +1177 -814
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +9 -9
- package/dist/index.cjs +787 -381
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -2
- package/dist/neatd.cjs +852 -446
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +2 -2
- package/dist/server.cjs +510 -92
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +1 -1
- package/package.json +2 -2
- package/dist/chunk-5T6J3WKC.js.map +0 -1
- package/dist/chunk-UJ6WBLIE.js.map +0 -1
package/dist/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",
|
|
@@ -9261,9 +9828,11 @@ async function buildApi(opts) {
|
|
|
9261
9828
|
trustProxy,
|
|
9262
9829
|
publicRead
|
|
9263
9830
|
});
|
|
9831
|
+
const requiresAuth = authToken !== void 0 && authToken.length > 0 && trustProxy !== true;
|
|
9264
9832
|
app.get("/api/config", async () => ({
|
|
9265
9833
|
publicRead: publicRead === true,
|
|
9266
|
-
authProxy: trustProxy === true
|
|
9834
|
+
authProxy: trustProxy === true,
|
|
9835
|
+
requiresAuth
|
|
9267
9836
|
}));
|
|
9268
9837
|
const startedAt = opts.startedAt ?? Date.now();
|
|
9269
9838
|
const registry = buildLegacyRegistry(opts);
|
|
@@ -9293,7 +9862,8 @@ async function buildApi(opts) {
|
|
|
9293
9862
|
staleEventsPathFor,
|
|
9294
9863
|
policyFilePathFor,
|
|
9295
9864
|
bootstrap: opts.bootstrap,
|
|
9296
|
-
singleProject: opts.singleProject?.name
|
|
9865
|
+
singleProject: opts.singleProject?.name,
|
|
9866
|
+
connectorsHome: opts.connectorsHome
|
|
9297
9867
|
};
|
|
9298
9868
|
app.get("/health", async () => {
|
|
9299
9869
|
const uptimeMs = Date.now() - startedAt;
|
|
@@ -9378,8 +9948,8 @@ init_otel();
|
|
|
9378
9948
|
|
|
9379
9949
|
// src/daemon.ts
|
|
9380
9950
|
init_cjs_shims();
|
|
9381
|
-
var
|
|
9382
|
-
var
|
|
9951
|
+
var import_node_fs33 = require("fs");
|
|
9952
|
+
var import_node_path52 = __toESM(require("path"), 1);
|
|
9383
9953
|
var import_node_module = require("module");
|
|
9384
9954
|
init_otel();
|
|
9385
9955
|
|
|
@@ -9673,6 +10243,30 @@ function buildEdgeLogsQuery(limit) {
|
|
|
9673
10243
|
`limit ${safeLimit}`
|
|
9674
10244
|
].join("\n");
|
|
9675
10245
|
}
|
|
10246
|
+
function logsAllHttpFailureMessage(status2) {
|
|
10247
|
+
if (status2 === 401 || status2 === 403) {
|
|
10248
|
+
return `supabase connector: logs.all request rejected (HTTP ${status2}). Check the Management API token, its analytics read scope, and --api-project-ref.`;
|
|
10249
|
+
}
|
|
10250
|
+
if (status2 === 404) {
|
|
10251
|
+
return "supabase connector: logs.all project not found (HTTP 404). Check --api-project-ref.";
|
|
10252
|
+
}
|
|
10253
|
+
if (status2 === 429) {
|
|
10254
|
+
return "supabase connector: logs.all rate-limited (HTTP 429). The connector will retry on the next poll; reduce poll cadence if this persists.";
|
|
10255
|
+
}
|
|
10256
|
+
if (status2 === 400) {
|
|
10257
|
+
return "supabase connector: logs.all query rejected (HTTP 400). Provider details redacted; confirm the live log-query dialect before shipping this connector.";
|
|
10258
|
+
}
|
|
10259
|
+
return `supabase connector: logs.all request failed (HTTP ${status2}); provider details redacted.`;
|
|
10260
|
+
}
|
|
10261
|
+
function providerErrorDetails(error) {
|
|
10262
|
+
if (!error || typeof error === "string") return "";
|
|
10263
|
+
const parts = [];
|
|
10264
|
+
if (typeof error.code === "number") parts.push(`code ${error.code}`);
|
|
10265
|
+
if (typeof error.status === "string" && /^[A-Z0-9_.-]+$/.test(error.status)) {
|
|
10266
|
+
parts.push(`status ${error.status}`);
|
|
10267
|
+
}
|
|
10268
|
+
return parts.length > 0 ? ` (${parts.join(", ")})` : "";
|
|
10269
|
+
}
|
|
9676
10270
|
async function fetchSupabaseEdgeLogs(config, token, startIso, endIso, fetchImpl = fetch) {
|
|
9677
10271
|
const baseUrl = config.managementApiUrl ?? DEFAULT_SUPABASE_MANAGEMENT_API_URL;
|
|
9678
10272
|
const url = new URL(`${baseUrl}/v1/projects/${config.apiProjectRef}/analytics/endpoints/logs.all`);
|
|
@@ -9687,12 +10281,18 @@ async function fetchSupabaseEdgeLogs(config, token, startIso, endIso, fetchImpl
|
|
|
9687
10281
|
{ provider: "supabase", accountKey: config.apiProjectRef, fetchImpl }
|
|
9688
10282
|
);
|
|
9689
10283
|
if (!res.ok) {
|
|
9690
|
-
throw new Error(
|
|
10284
|
+
throw new Error(logsAllHttpFailureMessage(res.status));
|
|
10285
|
+
}
|
|
10286
|
+
let body;
|
|
10287
|
+
try {
|
|
10288
|
+
body = await res.json();
|
|
10289
|
+
} catch {
|
|
10290
|
+
throw new Error("supabase connector: logs.all returned invalid JSON; provider details redacted.");
|
|
9691
10291
|
}
|
|
9692
|
-
const body = await res.json();
|
|
9693
10292
|
if (body.error) {
|
|
9694
|
-
|
|
9695
|
-
|
|
10293
|
+
throw new Error(
|
|
10294
|
+
`supabase connector: logs.all returned a provider error${providerErrorDetails(body.error)}; provider message redacted.`
|
|
10295
|
+
);
|
|
9696
10296
|
}
|
|
9697
10297
|
return body.result ?? [];
|
|
9698
10298
|
}
|
|
@@ -9724,10 +10324,10 @@ var SUPABASE_RPC_TARGET_KIND = "supabase-rpc";
|
|
|
9724
10324
|
// src/connectors/supabase/map.ts
|
|
9725
10325
|
var REST_RPC_PATH_RE = /^\/rest\/v1\/rpc\/([^/?]+)/;
|
|
9726
10326
|
var REST_TABLE_PATH_RE = /^\/rest\/v1\/([^/?]+)/;
|
|
9727
|
-
function targetFromRestPath(
|
|
9728
|
-
const rpcMatch = REST_RPC_PATH_RE.exec(
|
|
10327
|
+
function targetFromRestPath(path61) {
|
|
10328
|
+
const rpcMatch = REST_RPC_PATH_RE.exec(path61);
|
|
9729
10329
|
if (rpcMatch) return { targetKind: SUPABASE_RPC_TARGET_KIND, name: rpcMatch[1] };
|
|
9730
|
-
const tableMatch = REST_TABLE_PATH_RE.exec(
|
|
10330
|
+
const tableMatch = REST_TABLE_PATH_RE.exec(path61);
|
|
9731
10331
|
if (tableMatch) return { targetKind: SUPABASE_TABLE_TARGET_KIND, name: tableMatch[1] };
|
|
9732
10332
|
return null;
|
|
9733
10333
|
}
|
|
@@ -9830,19 +10430,19 @@ async function fetchPgStatStatements(connectionString, limit = DEFAULT_STATEMENT
|
|
|
9830
10430
|
|
|
9831
10431
|
// src/connectors/supabase/resolve.ts
|
|
9832
10432
|
init_cjs_shims();
|
|
9833
|
-
var
|
|
10433
|
+
var import_types36 = require("@neat.is/types");
|
|
9834
10434
|
function createSupabaseResolveTarget(graph, config) {
|
|
9835
10435
|
return (signal, _ctx) => {
|
|
9836
10436
|
if (signal.targetKind !== SUPABASE_TABLE_TARGET_KIND && signal.targetKind !== SUPABASE_RPC_TARGET_KIND) {
|
|
9837
10437
|
return null;
|
|
9838
10438
|
}
|
|
9839
|
-
const subResourceId = (0,
|
|
10439
|
+
const subResourceId = (0, import_types36.infraId)(signal.targetKind, `${config.nodeRef}/${signal.targetName}`);
|
|
9840
10440
|
if (graph.hasNode(subResourceId)) {
|
|
9841
|
-
return { targetNodeId: subResourceId, serviceName: config.serviceName, edgeType:
|
|
10441
|
+
return { targetNodeId: subResourceId, serviceName: config.serviceName, edgeType: import_types36.EdgeType.CALLS };
|
|
9842
10442
|
}
|
|
9843
|
-
const projectLevelId = (0,
|
|
10443
|
+
const projectLevelId = (0, import_types36.infraId)("supabase", config.nodeRef);
|
|
9844
10444
|
if (graph.hasNode(projectLevelId)) {
|
|
9845
|
-
return { targetNodeId: projectLevelId, serviceName: config.serviceName, edgeType:
|
|
10445
|
+
return { targetNodeId: projectLevelId, serviceName: config.serviceName, edgeType: import_types36.EdgeType.CALLS };
|
|
9846
10446
|
}
|
|
9847
10447
|
return null;
|
|
9848
10448
|
};
|
|
@@ -9850,6 +10450,35 @@ function createSupabaseResolveTarget(graph, config) {
|
|
|
9850
10450
|
|
|
9851
10451
|
// src/connectors/supabase/index.ts
|
|
9852
10452
|
var DEFAULT_MAX_LOOKBACK_MS = 24 * 60 * 60 * 1e3;
|
|
10453
|
+
function errorCode(err) {
|
|
10454
|
+
const code = err?.code;
|
|
10455
|
+
return typeof code === "string" && code.length > 0 ? code : void 0;
|
|
10456
|
+
}
|
|
10457
|
+
function describeSupabasePostgresSurfaceFailure(projectRef, err) {
|
|
10458
|
+
const code = errorCode(err);
|
|
10459
|
+
let reason;
|
|
10460
|
+
switch (code) {
|
|
10461
|
+
case "42501":
|
|
10462
|
+
reason = "permission denied; grant pg_read_all_stats to the configured Postgres role";
|
|
10463
|
+
break;
|
|
10464
|
+
case "42P01":
|
|
10465
|
+
case "42704":
|
|
10466
|
+
reason = "pg_stat_statements is not enabled or visible to the configured Postgres role";
|
|
10467
|
+
break;
|
|
10468
|
+
case "28P01":
|
|
10469
|
+
case "28000":
|
|
10470
|
+
reason = "Postgres credential rejected";
|
|
10471
|
+
break;
|
|
10472
|
+
case "3D000":
|
|
10473
|
+
reason = "database not found";
|
|
10474
|
+
break;
|
|
10475
|
+
default: {
|
|
10476
|
+
const name = err instanceof Error && err.name ? err.name : "Error";
|
|
10477
|
+
reason = code ? `${name} ${code}` : name;
|
|
10478
|
+
}
|
|
10479
|
+
}
|
|
10480
|
+
return `supabase connector: pg_stat_statements surface unavailable for project ${projectRef} (${reason}); continuing with Management API log surface.`;
|
|
10481
|
+
}
|
|
9853
10482
|
var SupabaseConnector = class {
|
|
9854
10483
|
// `deps.fetchPgStatStatements` defaults to the real Postgres-backed
|
|
9855
10484
|
// implementation; tests override it to exercise the "both surfaces
|
|
@@ -9881,12 +10510,18 @@ var SupabaseConnector = class {
|
|
|
9881
10510
|
const signals = mapEdgeLogRowsToSignals(logRows);
|
|
9882
10511
|
if (creds.postgresConnectionString) {
|
|
9883
10512
|
const fetchStatements = this.deps.fetchPgStatStatements ?? fetchPgStatStatements;
|
|
9884
|
-
|
|
9885
|
-
|
|
9886
|
-
|
|
9887
|
-
|
|
9888
|
-
|
|
9889
|
-
|
|
10513
|
+
try {
|
|
10514
|
+
const statementRows = await fetchStatements(
|
|
10515
|
+
creds.postgresConnectionString,
|
|
10516
|
+
this.config.statementLimit ?? DEFAULT_STATEMENT_LIMIT,
|
|
10517
|
+
this.config.apiProjectRef
|
|
10518
|
+
);
|
|
10519
|
+
signals.push(...diffPgStatStatementsToSignals(statementRows, this.statementBaselines, now.toISOString()));
|
|
10520
|
+
} catch (err) {
|
|
10521
|
+
const summary = describeSupabasePostgresSurfaceFailure(this.config.apiProjectRef, err);
|
|
10522
|
+
if (this.deps.onPostgresSurfaceError) this.deps.onPostgresSurfaceError(err, summary);
|
|
10523
|
+
else console.warn(summary);
|
|
10524
|
+
}
|
|
9890
10525
|
}
|
|
9891
10526
|
return signals;
|
|
9892
10527
|
}
|
|
@@ -9900,7 +10535,7 @@ function createSupabaseConnector(graph, config, deps = {}) {
|
|
|
9900
10535
|
|
|
9901
10536
|
// src/connectors/railway/index.ts
|
|
9902
10537
|
init_cjs_shims();
|
|
9903
|
-
var
|
|
10538
|
+
var import_types40 = require("@neat.is/types");
|
|
9904
10539
|
|
|
9905
10540
|
// src/connectors/railway/client.ts
|
|
9906
10541
|
init_cjs_shims();
|
|
@@ -10045,7 +10680,7 @@ function buildRailwayRouteIndex(graph, serviceName) {
|
|
|
10045
10680
|
const out = [];
|
|
10046
10681
|
graph.forEachNode((_id, attrs) => {
|
|
10047
10682
|
const node = attrs;
|
|
10048
|
-
if (node.type !==
|
|
10683
|
+
if (node.type !== import_types40.NodeType.RouteNode) return;
|
|
10049
10684
|
const route = attrs;
|
|
10050
10685
|
if (route.service !== serviceName) return;
|
|
10051
10686
|
out.push({
|
|
@@ -10141,12 +10776,12 @@ function createRailwayResolveTarget(config) {
|
|
|
10141
10776
|
const serviceName = config.serviceNameById[config.serviceId];
|
|
10142
10777
|
if (!serviceName) return null;
|
|
10143
10778
|
if (signal.targetKind === ROUTE_TARGET_KIND) {
|
|
10144
|
-
return { targetNodeId: signal.targetName, serviceName, edgeType:
|
|
10779
|
+
return { targetNodeId: signal.targetName, serviceName, edgeType: import_types40.EdgeType.CALLS };
|
|
10145
10780
|
}
|
|
10146
10781
|
if (signal.targetKind === PEER_SERVICE_TARGET_KIND) {
|
|
10147
10782
|
const peerName = config.serviceNameById[signal.targetName];
|
|
10148
10783
|
if (!peerName) return null;
|
|
10149
|
-
return { targetNodeId: (0,
|
|
10784
|
+
return { targetNodeId: (0, import_types40.serviceId)(peerName), serviceName, edgeType: import_types40.EdgeType.CONNECTS_TO };
|
|
10150
10785
|
}
|
|
10151
10786
|
return null;
|
|
10152
10787
|
};
|
|
@@ -10261,9 +10896,9 @@ function parseFirebaseTargetName(targetName) {
|
|
|
10261
10896
|
const secondSep = rest.indexOf(FIELD_SEP);
|
|
10262
10897
|
if (secondSep === -1) return null;
|
|
10263
10898
|
const method = rest.slice(0, secondSep);
|
|
10264
|
-
const
|
|
10265
|
-
if (!resourceName || !method || !
|
|
10266
|
-
return { resourceName, method, path:
|
|
10899
|
+
const path61 = rest.slice(secondSep + 1);
|
|
10900
|
+
if (!resourceName || !method || !path61) return null;
|
|
10901
|
+
return { resourceName, method, path: path61 };
|
|
10267
10902
|
}
|
|
10268
10903
|
function resourceNameFor(type, labels) {
|
|
10269
10904
|
if (!labels) return null;
|
|
@@ -10300,14 +10935,14 @@ function mapLogEntryToSignal(entry2) {
|
|
|
10300
10935
|
if (!req) return null;
|
|
10301
10936
|
if (!req.requestMethod) return null;
|
|
10302
10937
|
const method = req.requestMethod.toUpperCase();
|
|
10303
|
-
const
|
|
10304
|
-
if (
|
|
10938
|
+
const path61 = pathFromRequestUrl(req.requestUrl);
|
|
10939
|
+
if (path61 === null) return null;
|
|
10305
10940
|
const timestamp = entry2.timestamp;
|
|
10306
10941
|
if (!timestamp) return null;
|
|
10307
10942
|
const isError = typeof req.status === "number" && req.status >= ERROR_STATUS_THRESHOLD2;
|
|
10308
10943
|
return {
|
|
10309
10944
|
targetKind: resourceType,
|
|
10310
|
-
targetName: packFirebaseTargetName({ resourceName, method, path:
|
|
10945
|
+
targetName: packFirebaseTargetName({ resourceName, method, path: path61 }),
|
|
10311
10946
|
callCount: 1,
|
|
10312
10947
|
errorCount: isError ? 1 : 0,
|
|
10313
10948
|
lastObservedIso: timestamp
|
|
@@ -10324,7 +10959,7 @@ function mapLogEntriesToSignals(entries) {
|
|
|
10324
10959
|
|
|
10325
10960
|
// src/connectors/firebase/resolve.ts
|
|
10326
10961
|
init_cjs_shims();
|
|
10327
|
-
var
|
|
10962
|
+
var import_types41 = require("@neat.is/types");
|
|
10328
10963
|
function neatServiceNameFor(resourceType, resourceName, serviceMap) {
|
|
10329
10964
|
switch (resourceType) {
|
|
10330
10965
|
case "cloud_function":
|
|
@@ -10339,7 +10974,7 @@ function routeEntriesFor(graph, serviceName) {
|
|
|
10339
10974
|
const entries = [];
|
|
10340
10975
|
graph.forEachNode((_id, attrs) => {
|
|
10341
10976
|
const node = attrs;
|
|
10342
|
-
if (node.type !==
|
|
10977
|
+
if (node.type !== import_types41.NodeType.RouteNode) return;
|
|
10343
10978
|
const route = attrs;
|
|
10344
10979
|
if (route.service !== serviceName) return;
|
|
10345
10980
|
entries.push({
|
|
@@ -10371,7 +11006,7 @@ function createFirebaseResolveTarget(graph, serviceMap) {
|
|
|
10371
11006
|
return {
|
|
10372
11007
|
targetNodeId: match.routeNodeId,
|
|
10373
11008
|
serviceName,
|
|
10374
|
-
edgeType:
|
|
11009
|
+
edgeType: import_types41.EdgeType.CALLS
|
|
10375
11010
|
};
|
|
10376
11011
|
};
|
|
10377
11012
|
}
|
|
@@ -10398,7 +11033,7 @@ init_cjs_shims();
|
|
|
10398
11033
|
|
|
10399
11034
|
// src/connectors/cloudflare/connector.ts
|
|
10400
11035
|
init_cjs_shims();
|
|
10401
|
-
var
|
|
11036
|
+
var import_types43 = require("@neat.is/types");
|
|
10402
11037
|
|
|
10403
11038
|
// src/connectors/cloudflare/client.ts
|
|
10404
11039
|
init_cjs_shims();
|
|
@@ -10489,396 +11124,124 @@ function parseHttpMethodFromTrigger(trigger) {
|
|
|
10489
11124
|
function parsePathFromTrigger(trigger) {
|
|
10490
11125
|
const trimmed = trigger.trim();
|
|
10491
11126
|
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
|
-
}
|
|
11127
|
+
if (spaceIdx === -1) return void 0;
|
|
11128
|
+
const rest = trimmed.slice(spaceIdx + 1).trim();
|
|
11129
|
+
return rest.length > 0 ? rest : void 0;
|
|
11130
|
+
}
|
|
11131
|
+
var ERROR_STATUS_THRESHOLD3 = 500;
|
|
11132
|
+
function mapEventToSignal(event) {
|
|
11133
|
+
const metadata = event.$metadata;
|
|
11134
|
+
const workers = event.$workers;
|
|
11135
|
+
const method = parseHttpMethodFromTrigger(metadata?.trigger);
|
|
11136
|
+
if (!method) return null;
|
|
11137
|
+
const scriptName = workers?.scriptName ?? metadata?.service;
|
|
11138
|
+
if (!scriptName) return null;
|
|
11139
|
+
const timestampMs = event.timestamp ?? metadata?.startTime;
|
|
11140
|
+
if (typeof timestampMs !== "number" || !Number.isFinite(timestampMs)) return null;
|
|
11141
|
+
const statusCode = metadata?.statusCode;
|
|
11142
|
+
const isError = typeof statusCode === "number" && statusCode >= ERROR_STATUS_THRESHOLD3;
|
|
11143
|
+
const path61 = metadata?.trigger ? parsePathFromTrigger(metadata.trigger) : void 0;
|
|
10711
11144
|
return {
|
|
10712
|
-
|
|
10713
|
-
|
|
10714
|
-
|
|
10715
|
-
|
|
10716
|
-
|
|
11145
|
+
targetKind: CLOUDFLARE_TARGET_KIND,
|
|
11146
|
+
targetName: scriptName,
|
|
11147
|
+
callCount: 1,
|
|
11148
|
+
errorCount: isError ? 1 : 0,
|
|
11149
|
+
lastObservedIso: new Date(timestampMs).toISOString(),
|
|
11150
|
+
method,
|
|
11151
|
+
...path61 ? { path: path61 } : {},
|
|
11152
|
+
...typeof statusCode === "number" ? { statusCode } : {},
|
|
11153
|
+
...typeof metadata?.duration === "number" ? { duration: metadata.duration } : {}
|
|
10717
11154
|
};
|
|
10718
11155
|
}
|
|
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`);
|
|
11156
|
+
|
|
11157
|
+
// src/connectors/cloudflare/connector.ts
|
|
11158
|
+
var DEFAULT_MAX_LOOKBACK_MS3 = 60 * 60 * 1e3;
|
|
11159
|
+
function resolveFromMs(since, maxLookbackMs) {
|
|
11160
|
+
const cap = maxLookbackMs ?? DEFAULT_MAX_LOOKBACK_MS3;
|
|
11161
|
+
const now = Date.now();
|
|
11162
|
+
const floor = now - cap;
|
|
11163
|
+
if (!since) return floor;
|
|
11164
|
+
const parsed = Date.parse(since);
|
|
11165
|
+
if (Number.isNaN(parsed)) return floor;
|
|
11166
|
+
return Math.max(parsed, floor);
|
|
10737
11167
|
}
|
|
10738
|
-
|
|
10739
|
-
|
|
10740
|
-
|
|
10741
|
-
}
|
|
10742
|
-
const fields = {};
|
|
10743
|
-
for (const [key, value] of Object.entries(ref)) {
|
|
10744
|
-
fields[key] = resolveRef(value, env);
|
|
11168
|
+
var CloudflareConnector = class {
|
|
11169
|
+
constructor(config) {
|
|
11170
|
+
this.config = config;
|
|
10745
11171
|
}
|
|
10746
|
-
|
|
10747
|
-
|
|
10748
|
-
|
|
10749
|
-
|
|
10750
|
-
const
|
|
10751
|
-
const
|
|
10752
|
-
|
|
10753
|
-
|
|
11172
|
+
config;
|
|
11173
|
+
provider = "cloudflare";
|
|
11174
|
+
async poll(ctx) {
|
|
11175
|
+
const toMs = Date.now();
|
|
11176
|
+
const fromMs = resolveFromMs(ctx.since, this.config.maxLookbackMs);
|
|
11177
|
+
const events = await queryWorkerInvocations(ctx, this.config, { fromMs, toMs });
|
|
11178
|
+
const signals = [];
|
|
11179
|
+
for (const event of events) {
|
|
11180
|
+
const signal = mapEventToSignal(event);
|
|
11181
|
+
if (signal) signals.push(signal);
|
|
10754
11182
|
}
|
|
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();
|
|
11183
|
+
return signals;
|
|
10777
11184
|
}
|
|
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));
|
|
11185
|
+
};
|
|
11186
|
+
function findTaggedWorkerFileNode(graph, workerName) {
|
|
11187
|
+
let found = null;
|
|
11188
|
+
graph.forEachNode((id, attrs) => {
|
|
11189
|
+
if (found) return;
|
|
11190
|
+
const a = attrs;
|
|
11191
|
+
if (a.type === import_types43.NodeType.FileNode && a.platform === "cloudflare" && a.platformName === workerName) {
|
|
11192
|
+
found = id;
|
|
10801
11193
|
}
|
|
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
11194
|
});
|
|
11195
|
+
return found;
|
|
10836
11196
|
}
|
|
10837
|
-
|
|
10838
|
-
const
|
|
10839
|
-
|
|
10840
|
-
|
|
10841
|
-
|
|
10842
|
-
|
|
10843
|
-
|
|
10844
|
-
|
|
10845
|
-
|
|
11197
|
+
function findMatchingRouteNode(graph, serviceName, method, path61) {
|
|
11198
|
+
const normalizedPath = normalizePathTemplate(path61);
|
|
11199
|
+
let found = null;
|
|
11200
|
+
graph.forEachNode((id, attrs) => {
|
|
11201
|
+
if (found) return;
|
|
11202
|
+
const a = attrs;
|
|
11203
|
+
if (a.type !== import_types43.NodeType.RouteNode || a.service !== serviceName) return;
|
|
11204
|
+
if (!a.pathTemplate || normalizePathTemplate(a.pathTemplate) !== normalizedPath) return;
|
|
11205
|
+
const routeMethod = (a.method ?? "").toUpperCase();
|
|
11206
|
+
if (routeMethod !== "ALL" && routeMethod !== method) return;
|
|
11207
|
+
found = id;
|
|
10846
11208
|
});
|
|
11209
|
+
return found;
|
|
10847
11210
|
}
|
|
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;
|
|
11211
|
+
function createCloudflareResolveTarget(config, graph) {
|
|
11212
|
+
return (signal) => {
|
|
11213
|
+
if (signal.targetKind !== CLOUDFLARE_TARGET_KIND) return null;
|
|
11214
|
+
const scriptName = signal.targetName;
|
|
11215
|
+
const { method, path: path61 } = signal;
|
|
11216
|
+
const resolveRouteGrain = (serviceName, wholeFileId) => {
|
|
11217
|
+
if (!method || !path61) return wholeFileId;
|
|
11218
|
+
return findMatchingRouteNode(graph, serviceName, method, path61) ?? wholeFileId;
|
|
11219
|
+
};
|
|
11220
|
+
const mapping = config.workers?.[scriptName];
|
|
11221
|
+
if (mapping) {
|
|
11222
|
+
const wholeFileId = (0, import_types43.fileId)(mapping.service, mapping.entryFile);
|
|
10871
11223
|
return {
|
|
10872
|
-
|
|
10873
|
-
|
|
10874
|
-
|
|
10875
|
-
status: set ? "set" : "unset"
|
|
11224
|
+
targetNodeId: resolveRouteGrain(mapping.service, wholeFileId),
|
|
11225
|
+
serviceName: mapping.service,
|
|
11226
|
+
edgeType: import_types43.EdgeType.CALLS
|
|
10876
11227
|
};
|
|
10877
11228
|
}
|
|
10878
|
-
|
|
11229
|
+
const taggedFileId = findTaggedWorkerFileNode(graph, scriptName);
|
|
11230
|
+
if (taggedFileId) {
|
|
11231
|
+
const fileNode = graph.getNodeAttributes(taggedFileId);
|
|
11232
|
+
return {
|
|
11233
|
+
targetNodeId: resolveRouteGrain(fileNode.service, taggedFileId),
|
|
11234
|
+
serviceName: fileNode.service,
|
|
11235
|
+
edgeType: import_types43.EdgeType.CALLS
|
|
11236
|
+
};
|
|
11237
|
+
}
|
|
11238
|
+
return {
|
|
11239
|
+
targetNodeId: (0, import_types43.infraId)("cloudflare-worker", scriptName),
|
|
11240
|
+
serviceName: scriptName,
|
|
11241
|
+
edgeType: import_types43.EdgeType.CALLS,
|
|
11242
|
+
ensureInfraNode: { kind: "cloudflare-worker", name: scriptName, provider: "cloudflare" }
|
|
11243
|
+
};
|
|
10879
11244
|
};
|
|
10880
|
-
if (typeof ref === "string") return [one(ref)];
|
|
10881
|
-
return Object.entries(ref).map(([k, v]) => one(v, k));
|
|
10882
11245
|
}
|
|
10883
11246
|
|
|
10884
11247
|
// src/connectors/registry.ts
|
|
@@ -11074,33 +11437,33 @@ init_auth();
|
|
|
11074
11437
|
|
|
11075
11438
|
// src/unrouted.ts
|
|
11076
11439
|
init_cjs_shims();
|
|
11077
|
-
var
|
|
11078
|
-
var
|
|
11440
|
+
var import_node_fs32 = require("fs");
|
|
11441
|
+
var import_node_path51 = __toESM(require("path"), 1);
|
|
11079
11442
|
|
|
11080
11443
|
// src/daemon.ts
|
|
11081
|
-
var
|
|
11444
|
+
var import_types46 = require("@neat.is/types");
|
|
11082
11445
|
function daemonJsonPath(scanPath) {
|
|
11083
|
-
return
|
|
11446
|
+
return import_node_path52.default.join(scanPath, "neat-out", "daemon.json");
|
|
11084
11447
|
}
|
|
11085
11448
|
function daemonsDiscoveryDir(home) {
|
|
11086
11449
|
const base = home && home.length > 0 ? home : neatHomeFromEnv();
|
|
11087
|
-
return
|
|
11450
|
+
return import_node_path52.default.join(base, "daemons");
|
|
11088
11451
|
}
|
|
11089
11452
|
function daemonDiscoveryPath(project, home) {
|
|
11090
|
-
return
|
|
11453
|
+
return import_node_path52.default.join(daemonsDiscoveryDir(home), `${sanitizeDiscoveryName(project)}.json`);
|
|
11091
11454
|
}
|
|
11092
11455
|
function sanitizeDiscoveryName(project) {
|
|
11093
11456
|
return project.replace(/[^A-Za-z0-9._-]/g, "_");
|
|
11094
11457
|
}
|
|
11095
11458
|
function neatHomeFromEnv() {
|
|
11096
11459
|
const env = process.env.NEAT_HOME;
|
|
11097
|
-
if (env && env.length > 0) return
|
|
11460
|
+
if (env && env.length > 0) return import_node_path52.default.resolve(env);
|
|
11098
11461
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
11099
|
-
return
|
|
11462
|
+
return import_node_path52.default.join(home, ".neat");
|
|
11100
11463
|
}
|
|
11101
11464
|
async function readDaemonRecord(scanPath) {
|
|
11102
11465
|
try {
|
|
11103
|
-
const raw = await
|
|
11466
|
+
const raw = await import_node_fs33.promises.readFile(daemonJsonPath(scanPath), "utf8");
|
|
11104
11467
|
const parsed = JSON.parse(raw);
|
|
11105
11468
|
if (typeof parsed.project === "string" && parsed.ports && typeof parsed.ports.rest === "number" && typeof parsed.ports.otlp === "number" && typeof parsed.ports.web === "number") {
|
|
11106
11469
|
return parsed;
|
|
@@ -11140,7 +11503,7 @@ async function clearDaemonRecord(record, home) {
|
|
|
11140
11503
|
} catch {
|
|
11141
11504
|
}
|
|
11142
11505
|
try {
|
|
11143
|
-
await
|
|
11506
|
+
await import_node_fs33.promises.unlink(daemonDiscoveryPath(record.project, home));
|
|
11144
11507
|
} catch {
|
|
11145
11508
|
}
|
|
11146
11509
|
}
|
|
@@ -11166,8 +11529,8 @@ init_otel_grpc();
|
|
|
11166
11529
|
|
|
11167
11530
|
// src/search.ts
|
|
11168
11531
|
init_cjs_shims();
|
|
11169
|
-
var
|
|
11170
|
-
var
|
|
11532
|
+
var import_node_fs34 = require("fs");
|
|
11533
|
+
var import_node_path53 = __toESM(require("path"), 1);
|
|
11171
11534
|
var import_node_crypto4 = require("crypto");
|
|
11172
11535
|
var DEFAULT_LIMIT = 10;
|
|
11173
11536
|
var NOMIC_DIM = 768;
|
|
@@ -11321,7 +11684,7 @@ async function pickEmbedder() {
|
|
|
11321
11684
|
}
|
|
11322
11685
|
async function readCache(cachePath) {
|
|
11323
11686
|
try {
|
|
11324
|
-
const raw = await
|
|
11687
|
+
const raw = await import_node_fs34.promises.readFile(cachePath, "utf8");
|
|
11325
11688
|
const parsed = JSON.parse(raw);
|
|
11326
11689
|
if (parsed.version !== 1) return null;
|
|
11327
11690
|
return parsed;
|
|
@@ -11330,8 +11693,8 @@ async function readCache(cachePath) {
|
|
|
11330
11693
|
}
|
|
11331
11694
|
}
|
|
11332
11695
|
async function writeCache(cachePath, cache) {
|
|
11333
|
-
await
|
|
11334
|
-
await
|
|
11696
|
+
await import_node_fs34.promises.mkdir(import_node_path53.default.dirname(cachePath), { recursive: true });
|
|
11697
|
+
await import_node_fs34.promises.writeFile(cachePath, JSON.stringify(cache));
|
|
11335
11698
|
}
|
|
11336
11699
|
var VectorIndex = class {
|
|
11337
11700
|
constructor(embedder, cachePath) {
|
|
@@ -11488,8 +11851,8 @@ var ALL_PHASES = [
|
|
|
11488
11851
|
];
|
|
11489
11852
|
function classifyChange(relPath) {
|
|
11490
11853
|
const phases = /* @__PURE__ */ new Set();
|
|
11491
|
-
const base =
|
|
11492
|
-
const segments = relPath.split(
|
|
11854
|
+
const base = import_node_path54.default.basename(relPath).toLowerCase();
|
|
11855
|
+
const segments = relPath.split(import_node_path54.default.sep).map((s) => s.toLowerCase());
|
|
11493
11856
|
if (base === "package.json" || base === "requirements.txt" || base === "pyproject.toml" || base === "setup.py") {
|
|
11494
11857
|
phases.add("services");
|
|
11495
11858
|
phases.add("aliases");
|
|
@@ -11610,16 +11973,16 @@ function countWatchableDirs(scanPath, limit) {
|
|
|
11610
11973
|
if (count >= limit) return;
|
|
11611
11974
|
let entries;
|
|
11612
11975
|
try {
|
|
11613
|
-
entries =
|
|
11976
|
+
entries = import_node_fs35.default.readdirSync(dir, { withFileTypes: true });
|
|
11614
11977
|
} catch {
|
|
11615
11978
|
return;
|
|
11616
11979
|
}
|
|
11617
11980
|
for (const e of entries) {
|
|
11618
11981
|
if (count >= limit) return;
|
|
11619
11982
|
if (!e.isDirectory()) continue;
|
|
11620
|
-
if (IGNORED_WATCH_PATHS.some((re) => re.test(
|
|
11983
|
+
if (IGNORED_WATCH_PATHS.some((re) => re.test(import_node_path54.default.join(dir, e.name) + import_node_path54.default.sep))) continue;
|
|
11621
11984
|
count++;
|
|
11622
|
-
if (depth < 2) visit(
|
|
11985
|
+
if (depth < 2) visit(import_node_path54.default.join(dir, e.name), depth + 1);
|
|
11623
11986
|
}
|
|
11624
11987
|
};
|
|
11625
11988
|
visit(scanPath, 0);
|
|
@@ -11637,8 +12000,8 @@ async function startWatch(graph, opts) {
|
|
|
11637
12000
|
const projectName = opts.project ?? DEFAULT_PROJECT;
|
|
11638
12001
|
await loadGraphFromDisk(graph, opts.outPath);
|
|
11639
12002
|
const detachEventBus = attachGraphToEventBus(graph, { project: projectName });
|
|
11640
|
-
const policyFilePath =
|
|
11641
|
-
const policyViolationsPath =
|
|
12003
|
+
const policyFilePath = import_node_path54.default.join(opts.scanPath, "policy.json");
|
|
12004
|
+
const policyViolationsPath = import_node_path54.default.join(import_node_path54.default.dirname(opts.outPath), "policy-violations.ndjson");
|
|
11642
12005
|
let policies = [];
|
|
11643
12006
|
try {
|
|
11644
12007
|
policies = await loadPolicyFile(policyFilePath);
|
|
@@ -11689,7 +12052,7 @@ async function startWatch(graph, opts) {
|
|
|
11689
12052
|
assertBindAuthority(host, auth.authToken);
|
|
11690
12053
|
const port = opts.port ?? 8080;
|
|
11691
12054
|
const otelPort = opts.otelPort ?? 4318;
|
|
11692
|
-
const cachePath = opts.embeddingsCachePath ??
|
|
12055
|
+
const cachePath = opts.embeddingsCachePath ?? import_node_path54.default.join(import_node_path54.default.dirname(opts.outPath), "embeddings.json");
|
|
11693
12056
|
let searchIndex;
|
|
11694
12057
|
try {
|
|
11695
12058
|
searchIndex = await buildSearchIndex(graph, { cachePath });
|
|
@@ -11707,7 +12070,7 @@ async function startWatch(graph, opts) {
|
|
|
11707
12070
|
// Paths are derived from the explicit options the watch caller passes
|
|
11708
12071
|
// — pathsForProject is only used to fill in the embeddings/snapshot
|
|
11709
12072
|
// fields so the registry shape is complete.
|
|
11710
|
-
...pathsForProject(projectName,
|
|
12073
|
+
...pathsForProject(projectName, import_node_path54.default.dirname(opts.outPath)),
|
|
11711
12074
|
snapshotPath: opts.outPath,
|
|
11712
12075
|
errorsPath: opts.errorsPath,
|
|
11713
12076
|
staleEventsPath: opts.staleEventsPath
|
|
@@ -11825,9 +12188,9 @@ async function startWatch(graph, opts) {
|
|
|
11825
12188
|
};
|
|
11826
12189
|
const onPath = (absPath) => {
|
|
11827
12190
|
if (shouldIgnore(absPath)) return;
|
|
11828
|
-
const rel =
|
|
12191
|
+
const rel = import_node_path54.default.relative(opts.scanPath, absPath);
|
|
11829
12192
|
if (!rel || rel.startsWith("..")) return;
|
|
11830
|
-
pendingPaths.add(rel.split(
|
|
12193
|
+
pendingPaths.add(rel.split(import_node_path54.default.sep).join("/"));
|
|
11831
12194
|
const phases = classifyChange(rel);
|
|
11832
12195
|
if (phases.size === 0) {
|
|
11833
12196
|
for (const p of ALL_PHASES) pending.add(p);
|
|
@@ -11883,8 +12246,8 @@ async function startWatch(graph, opts) {
|
|
|
11883
12246
|
|
|
11884
12247
|
// src/deploy/detect.ts
|
|
11885
12248
|
init_cjs_shims();
|
|
11886
|
-
var
|
|
11887
|
-
var
|
|
12249
|
+
var import_node_fs36 = require("fs");
|
|
12250
|
+
var import_node_path55 = __toESM(require("path"), 1);
|
|
11888
12251
|
var import_node_child_process2 = require("child_process");
|
|
11889
12252
|
var import_node_crypto5 = require("crypto");
|
|
11890
12253
|
function generateToken() {
|
|
@@ -11984,21 +12347,21 @@ async function runDeploy(opts = {}) {
|
|
|
11984
12347
|
const token = generateToken();
|
|
11985
12348
|
switch (substrate) {
|
|
11986
12349
|
case "docker-compose": {
|
|
11987
|
-
const artifactPath =
|
|
12350
|
+
const artifactPath = import_node_path55.default.join(cwd, "docker-compose.neat.yml");
|
|
11988
12351
|
const contents = emitDockerCompose(cwd);
|
|
11989
|
-
await
|
|
12352
|
+
await import_node_fs36.promises.writeFile(artifactPath, contents, "utf8");
|
|
11990
12353
|
return {
|
|
11991
12354
|
substrate,
|
|
11992
12355
|
artifactPath,
|
|
11993
12356
|
token,
|
|
11994
12357
|
contents,
|
|
11995
|
-
startCommand: `NEAT_AUTH_TOKEN=${token} docker compose -f ${
|
|
12358
|
+
startCommand: `NEAT_AUTH_TOKEN=${token} docker compose -f ${import_node_path55.default.basename(artifactPath)} up -d`
|
|
11996
12359
|
};
|
|
11997
12360
|
}
|
|
11998
12361
|
case "systemd": {
|
|
11999
|
-
const artifactPath =
|
|
12362
|
+
const artifactPath = import_node_path55.default.join(cwd, "neat.service");
|
|
12000
12363
|
const contents = emitSystemdUnit(cwd);
|
|
12001
|
-
await
|
|
12364
|
+
await import_node_fs36.promises.writeFile(artifactPath, contents, "utf8");
|
|
12002
12365
|
return {
|
|
12003
12366
|
substrate,
|
|
12004
12367
|
artifactPath,
|
|
@@ -12031,8 +12394,8 @@ init_cjs_shims();
|
|
|
12031
12394
|
|
|
12032
12395
|
// src/installers/javascript.ts
|
|
12033
12396
|
init_cjs_shims();
|
|
12034
|
-
var
|
|
12035
|
-
var
|
|
12397
|
+
var import_node_fs37 = require("fs");
|
|
12398
|
+
var import_node_path56 = __toESM(require("path"), 1);
|
|
12036
12399
|
var import_semver2 = __toESM(require("semver"), 1);
|
|
12037
12400
|
|
|
12038
12401
|
// src/installers/templates.ts
|
|
@@ -12647,15 +13010,15 @@ var OTEL_ENV = {
|
|
|
12647
13010
|
value: "http://localhost:4318/projects/<project>/v1/traces"
|
|
12648
13011
|
};
|
|
12649
13012
|
function serviceNodeName(pkg, serviceDir) {
|
|
12650
|
-
return pkg.name ??
|
|
13013
|
+
return pkg.name ?? import_node_path56.default.basename(serviceDir);
|
|
12651
13014
|
}
|
|
12652
13015
|
function projectToken(pkg, serviceDir, project) {
|
|
12653
13016
|
if (project && project.length > 0) return project;
|
|
12654
|
-
return pkg.name ??
|
|
13017
|
+
return pkg.name ?? import_node_path56.default.basename(serviceDir);
|
|
12655
13018
|
}
|
|
12656
13019
|
async function readJsonFile(p) {
|
|
12657
13020
|
try {
|
|
12658
|
-
const raw = await
|
|
13021
|
+
const raw = await import_node_fs37.promises.readFile(p, "utf8");
|
|
12659
13022
|
return JSON.parse(raw);
|
|
12660
13023
|
} catch {
|
|
12661
13024
|
return null;
|
|
@@ -12664,16 +13027,16 @@ async function readJsonFile(p) {
|
|
|
12664
13027
|
async function detectRuntimeKind(pkgRoot, pkg) {
|
|
12665
13028
|
const deps = allDeps(pkg);
|
|
12666
13029
|
if ("react-native" in deps || "expo" in deps) return "react-native";
|
|
12667
|
-
const appJson = await readJsonFile(
|
|
13030
|
+
const appJson = await readJsonFile(import_node_path56.default.join(pkgRoot, "app.json"));
|
|
12668
13031
|
if (appJson && typeof appJson === "object" && "expo" in appJson) {
|
|
12669
13032
|
return "react-native";
|
|
12670
13033
|
}
|
|
12671
|
-
if (await exists3(
|
|
13034
|
+
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
13035
|
return "browser-bundle";
|
|
12673
13036
|
}
|
|
12674
|
-
if (await exists3(
|
|
12675
|
-
if (await exists3(
|
|
12676
|
-
if (await exists3(
|
|
13037
|
+
if (await exists3(import_node_path56.default.join(pkgRoot, "wrangler.toml"))) return "cloudflare-workers";
|
|
13038
|
+
if (await exists3(import_node_path56.default.join(pkgRoot, "bun.lockb"))) return "bun";
|
|
13039
|
+
if (await exists3(import_node_path56.default.join(pkgRoot, "deno.json")) || await exists3(import_node_path56.default.join(pkgRoot, "deno.lock"))) {
|
|
12677
13040
|
return "deno";
|
|
12678
13041
|
}
|
|
12679
13042
|
const engines = pkg.engines ?? {};
|
|
@@ -12682,7 +13045,7 @@ async function detectRuntimeKind(pkgRoot, pkg) {
|
|
|
12682
13045
|
}
|
|
12683
13046
|
async function readPackageJson2(serviceDir) {
|
|
12684
13047
|
try {
|
|
12685
|
-
const raw = await
|
|
13048
|
+
const raw = await import_node_fs37.promises.readFile(import_node_path56.default.join(serviceDir, "package.json"), "utf8");
|
|
12686
13049
|
return JSON.parse(raw);
|
|
12687
13050
|
} catch {
|
|
12688
13051
|
return null;
|
|
@@ -12690,7 +13053,7 @@ async function readPackageJson2(serviceDir) {
|
|
|
12690
13053
|
}
|
|
12691
13054
|
async function exists3(p) {
|
|
12692
13055
|
try {
|
|
12693
|
-
await
|
|
13056
|
+
await import_node_fs37.promises.stat(p);
|
|
12694
13057
|
return true;
|
|
12695
13058
|
} catch {
|
|
12696
13059
|
return false;
|
|
@@ -12698,7 +13061,7 @@ async function exists3(p) {
|
|
|
12698
13061
|
}
|
|
12699
13062
|
async function readFileMaybe(p) {
|
|
12700
13063
|
try {
|
|
12701
|
-
return await
|
|
13064
|
+
return await import_node_fs37.promises.readFile(p, "utf8");
|
|
12702
13065
|
} catch {
|
|
12703
13066
|
return null;
|
|
12704
13067
|
}
|
|
@@ -12726,7 +13089,7 @@ function needsVersionUpgrade(installed, expected) {
|
|
|
12726
13089
|
var NEXT_CONFIG_CANDIDATES = ["next.config.js", "next.config.ts", "next.config.mjs"];
|
|
12727
13090
|
async function findNextConfig(serviceDir) {
|
|
12728
13091
|
for (const name of NEXT_CONFIG_CANDIDATES) {
|
|
12729
|
-
const candidate =
|
|
13092
|
+
const candidate = import_node_path56.default.join(serviceDir, name);
|
|
12730
13093
|
if (await exists3(candidate)) return candidate;
|
|
12731
13094
|
}
|
|
12732
13095
|
return null;
|
|
@@ -12795,7 +13158,7 @@ function hasRemixDependency(pkg) {
|
|
|
12795
13158
|
}
|
|
12796
13159
|
async function findRemixEntry(serviceDir) {
|
|
12797
13160
|
for (const rel of REMIX_ENTRY_CANDIDATES) {
|
|
12798
|
-
const candidate =
|
|
13161
|
+
const candidate = import_node_path56.default.join(serviceDir, rel);
|
|
12799
13162
|
if (await exists3(candidate)) return candidate;
|
|
12800
13163
|
}
|
|
12801
13164
|
return null;
|
|
@@ -12807,14 +13170,14 @@ function hasSvelteKitDependency(pkg) {
|
|
|
12807
13170
|
}
|
|
12808
13171
|
async function findSvelteKitHooks(serviceDir) {
|
|
12809
13172
|
for (const rel of SVELTEKIT_HOOKS_CANDIDATES) {
|
|
12810
|
-
const candidate =
|
|
13173
|
+
const candidate = import_node_path56.default.join(serviceDir, rel);
|
|
12811
13174
|
if (await exists3(candidate)) return candidate;
|
|
12812
13175
|
}
|
|
12813
13176
|
return null;
|
|
12814
13177
|
}
|
|
12815
13178
|
async function findSvelteKitConfig(serviceDir) {
|
|
12816
13179
|
for (const rel of SVELTEKIT_CONFIG_CANDIDATES) {
|
|
12817
|
-
const candidate =
|
|
13180
|
+
const candidate = import_node_path56.default.join(serviceDir, rel);
|
|
12818
13181
|
if (await exists3(candidate)) return candidate;
|
|
12819
13182
|
}
|
|
12820
13183
|
return null;
|
|
@@ -12825,7 +13188,7 @@ function hasNuxtDependency(pkg) {
|
|
|
12825
13188
|
}
|
|
12826
13189
|
async function findNuxtConfig(serviceDir) {
|
|
12827
13190
|
for (const name of NUXT_CONFIG_CANDIDATES) {
|
|
12828
|
-
const candidate =
|
|
13191
|
+
const candidate = import_node_path56.default.join(serviceDir, name);
|
|
12829
13192
|
if (await exists3(candidate)) return candidate;
|
|
12830
13193
|
}
|
|
12831
13194
|
return null;
|
|
@@ -12836,7 +13199,7 @@ function hasAstroDependency(pkg) {
|
|
|
12836
13199
|
}
|
|
12837
13200
|
async function findAstroConfig(serviceDir) {
|
|
12838
13201
|
for (const name of ASTRO_CONFIG_CANDIDATES) {
|
|
12839
|
-
const candidate =
|
|
13202
|
+
const candidate = import_node_path56.default.join(serviceDir, name);
|
|
12840
13203
|
if (await exists3(candidate)) return candidate;
|
|
12841
13204
|
}
|
|
12842
13205
|
return null;
|
|
@@ -12850,7 +13213,7 @@ function parseNextMajor(range) {
|
|
|
12850
13213
|
return Number.isFinite(n) ? n : null;
|
|
12851
13214
|
}
|
|
12852
13215
|
async function isTypeScriptProject(serviceDir) {
|
|
12853
|
-
return exists3(
|
|
13216
|
+
return exists3(import_node_path56.default.join(serviceDir, "tsconfig.json"));
|
|
12854
13217
|
}
|
|
12855
13218
|
var INDEX_EXTENSIONS = [".ts", ".tsx", ".js", ".mjs", ".cjs"];
|
|
12856
13219
|
var INDEX_CANDIDATES = INDEX_EXTENSIONS.map((ext) => `index${ext}`);
|
|
@@ -12899,7 +13262,7 @@ function entryFromScript(script) {
|
|
|
12899
13262
|
}
|
|
12900
13263
|
async function resolveEntry(serviceDir, pkg) {
|
|
12901
13264
|
if (typeof pkg.main === "string" && pkg.main.length > 0) {
|
|
12902
|
-
const candidate =
|
|
13265
|
+
const candidate = import_node_path56.default.resolve(serviceDir, pkg.main);
|
|
12903
13266
|
if (await exists3(candidate)) return candidate;
|
|
12904
13267
|
}
|
|
12905
13268
|
if (pkg.bin) {
|
|
@@ -12913,40 +13276,40 @@ async function resolveEntry(serviceDir, pkg) {
|
|
|
12913
13276
|
if (typeof first === "string") binEntry = first;
|
|
12914
13277
|
}
|
|
12915
13278
|
if (binEntry) {
|
|
12916
|
-
const candidate =
|
|
13279
|
+
const candidate = import_node_path56.default.resolve(serviceDir, binEntry);
|
|
12917
13280
|
if (await exists3(candidate)) return candidate;
|
|
12918
13281
|
}
|
|
12919
13282
|
}
|
|
12920
13283
|
const startEntry = entryFromScript(pkg.scripts?.start);
|
|
12921
13284
|
if (startEntry) {
|
|
12922
|
-
const candidate =
|
|
13285
|
+
const candidate = import_node_path56.default.resolve(serviceDir, startEntry);
|
|
12923
13286
|
if (await exists3(candidate)) return candidate;
|
|
12924
13287
|
}
|
|
12925
13288
|
const devEntry = entryFromScript(pkg.scripts?.dev);
|
|
12926
13289
|
if (devEntry) {
|
|
12927
|
-
const candidate =
|
|
13290
|
+
const candidate = import_node_path56.default.resolve(serviceDir, devEntry);
|
|
12928
13291
|
if (await exists3(candidate)) return candidate;
|
|
12929
13292
|
}
|
|
12930
13293
|
for (const rel of SRC_INDEX_CANDIDATES) {
|
|
12931
|
-
const candidate =
|
|
13294
|
+
const candidate = import_node_path56.default.join(serviceDir, rel);
|
|
12932
13295
|
if (await exists3(candidate)) return candidate;
|
|
12933
13296
|
}
|
|
12934
13297
|
for (const rel of SRC_NAMED_CANDIDATES) {
|
|
12935
|
-
const candidate =
|
|
13298
|
+
const candidate = import_node_path56.default.join(serviceDir, rel);
|
|
12936
13299
|
if (await exists3(candidate)) return candidate;
|
|
12937
13300
|
}
|
|
12938
13301
|
for (const rel of ROOT_NAMED_CANDIDATES) {
|
|
12939
|
-
const candidate =
|
|
13302
|
+
const candidate = import_node_path56.default.join(serviceDir, rel);
|
|
12940
13303
|
if (await exists3(candidate)) return candidate;
|
|
12941
13304
|
}
|
|
12942
13305
|
for (const name of INDEX_CANDIDATES) {
|
|
12943
|
-
const candidate =
|
|
13306
|
+
const candidate = import_node_path56.default.join(serviceDir, name);
|
|
12944
13307
|
if (await exists3(candidate)) return candidate;
|
|
12945
13308
|
}
|
|
12946
13309
|
return null;
|
|
12947
13310
|
}
|
|
12948
13311
|
function dispatchEntry(entryFile, pkg) {
|
|
12949
|
-
const ext =
|
|
13312
|
+
const ext = import_node_path56.default.extname(entryFile).toLowerCase();
|
|
12950
13313
|
if (ext === ".ts" || ext === ".tsx") return "ts";
|
|
12951
13314
|
if (ext === ".mjs") return "esm";
|
|
12952
13315
|
if (ext === ".cjs") return "cjs";
|
|
@@ -12963,9 +13326,9 @@ function otelInitContents(flavor) {
|
|
|
12963
13326
|
return OTEL_INIT_CJS;
|
|
12964
13327
|
}
|
|
12965
13328
|
function injectionLine(flavor, entryFile, otelInitFile) {
|
|
12966
|
-
let rel =
|
|
13329
|
+
let rel = import_node_path56.default.relative(import_node_path56.default.dirname(entryFile), otelInitFile);
|
|
12967
13330
|
if (!rel.startsWith(".")) rel = `./${rel}`;
|
|
12968
|
-
rel = rel.split(
|
|
13331
|
+
rel = rel.split(import_node_path56.default.sep).join("/");
|
|
12969
13332
|
if (flavor === "cjs") return `require('${rel}')`;
|
|
12970
13333
|
if (flavor === "esm") return `import '${rel}'`;
|
|
12971
13334
|
const tsRel = rel.replace(/\.ts$/, "");
|
|
@@ -12978,27 +13341,27 @@ function lineIsOtelInjection(line) {
|
|
|
12978
13341
|
}
|
|
12979
13342
|
async function detectsSrcLayout(serviceDir) {
|
|
12980
13343
|
const [hasSrcApp, hasSrcPages, hasRootApp, hasRootPages] = await Promise.all([
|
|
12981
|
-
exists3(
|
|
12982
|
-
exists3(
|
|
12983
|
-
exists3(
|
|
12984
|
-
exists3(
|
|
13344
|
+
exists3(import_node_path56.default.join(serviceDir, "src", "app")),
|
|
13345
|
+
exists3(import_node_path56.default.join(serviceDir, "src", "pages")),
|
|
13346
|
+
exists3(import_node_path56.default.join(serviceDir, "app")),
|
|
13347
|
+
exists3(import_node_path56.default.join(serviceDir, "pages"))
|
|
12985
13348
|
]);
|
|
12986
13349
|
return (hasSrcApp || hasSrcPages) && !hasRootApp && !hasRootPages;
|
|
12987
13350
|
}
|
|
12988
13351
|
async function planNext(serviceDir, pkg, manifestPath, nextConfigPath, project) {
|
|
12989
13352
|
const useTs = await isTypeScriptProject(serviceDir);
|
|
12990
13353
|
const srcLayout = await detectsSrcLayout(serviceDir);
|
|
12991
|
-
const baseDir = srcLayout ?
|
|
12992
|
-
const instrumentationFile =
|
|
12993
|
-
const instrumentationNodeFile =
|
|
13354
|
+
const baseDir = srcLayout ? import_node_path56.default.join(serviceDir, "src") : serviceDir;
|
|
13355
|
+
const instrumentationFile = import_node_path56.default.join(baseDir, useTs ? "instrumentation.ts" : "instrumentation.js");
|
|
13356
|
+
const instrumentationNodeFile = import_node_path56.default.join(
|
|
12994
13357
|
baseDir,
|
|
12995
13358
|
useTs ? "instrumentation.node.ts" : "instrumentation.node.js"
|
|
12996
13359
|
);
|
|
12997
|
-
const instrumentationEdgeFile =
|
|
13360
|
+
const instrumentationEdgeFile = import_node_path56.default.join(
|
|
12998
13361
|
baseDir,
|
|
12999
13362
|
useTs ? "instrumentation.edge.ts" : "instrumentation.edge.js"
|
|
13000
13363
|
);
|
|
13001
|
-
const envNeatFile =
|
|
13364
|
+
const envNeatFile = import_node_path56.default.join(baseDir, ".env.neat");
|
|
13002
13365
|
const existingDeps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
13003
13366
|
const dependencyEdits = [];
|
|
13004
13367
|
for (const sdk of SDK_PACKAGES) {
|
|
@@ -13075,7 +13438,7 @@ async function planNext(serviceDir, pkg, manifestPath, nextConfigPath, project)
|
|
|
13075
13438
|
const nextMajor = parseNextMajor(nextRange);
|
|
13076
13439
|
if (nextMajor !== null && nextMajor < 15) {
|
|
13077
13440
|
try {
|
|
13078
|
-
const raw = await
|
|
13441
|
+
const raw = await import_node_fs37.promises.readFile(nextConfigPath, "utf8");
|
|
13079
13442
|
if (!raw.includes("instrumentationHook")) {
|
|
13080
13443
|
nextConfigEdit = {
|
|
13081
13444
|
file: nextConfigPath,
|
|
@@ -13123,7 +13486,7 @@ function buildDependencyEdits(pkg, manifestPath) {
|
|
|
13123
13486
|
return edits;
|
|
13124
13487
|
}
|
|
13125
13488
|
async function queueEnvNeat(serviceDir, pkg, project, generatedFiles) {
|
|
13126
|
-
const envNeatFile =
|
|
13489
|
+
const envNeatFile = import_node_path56.default.join(serviceDir, ".env.neat");
|
|
13127
13490
|
if (!await exists3(envNeatFile)) {
|
|
13128
13491
|
generatedFiles.push({
|
|
13129
13492
|
file: envNeatFile,
|
|
@@ -13158,7 +13521,7 @@ function fileImportsOtelHook(raw, specifiers) {
|
|
|
13158
13521
|
}
|
|
13159
13522
|
async function planRemix(serviceDir, pkg, manifestPath, entryFile, project) {
|
|
13160
13523
|
const useTs = await isTypeScriptProject(serviceDir);
|
|
13161
|
-
const otelServerFile =
|
|
13524
|
+
const otelServerFile = import_node_path56.default.join(
|
|
13162
13525
|
serviceDir,
|
|
13163
13526
|
useTs ? "app/otel.server.ts" : "app/otel.server.js"
|
|
13164
13527
|
);
|
|
@@ -13179,7 +13542,7 @@ async function planRemix(serviceDir, pkg, manifestPath, entryFile, project) {
|
|
|
13179
13542
|
await queueEnvNeat(serviceDir, pkg, project, generatedFiles);
|
|
13180
13543
|
const entrypointEdits = [];
|
|
13181
13544
|
try {
|
|
13182
|
-
const raw = await
|
|
13545
|
+
const raw = await import_node_fs37.promises.readFile(entryFile, "utf8");
|
|
13183
13546
|
if (!fileImportsOtelHook(raw, ["./otel.server"])) {
|
|
13184
13547
|
const lines = raw.split(/\r?\n/);
|
|
13185
13548
|
const firstReal = lines[0]?.startsWith("#!") ? lines[1] ?? "" : lines[0] ?? "";
|
|
@@ -13215,11 +13578,11 @@ async function planRemix(serviceDir, pkg, manifestPath, entryFile, project) {
|
|
|
13215
13578
|
}
|
|
13216
13579
|
async function planSvelteKit(serviceDir, pkg, manifestPath, hooksFile, project) {
|
|
13217
13580
|
const useTs = await isTypeScriptProject(serviceDir);
|
|
13218
|
-
const otelInitFile =
|
|
13581
|
+
const otelInitFile = import_node_path56.default.join(
|
|
13219
13582
|
serviceDir,
|
|
13220
13583
|
useTs ? "src/otel-init.ts" : "src/otel-init.js"
|
|
13221
13584
|
);
|
|
13222
|
-
const resolvedHooksFile = hooksFile ??
|
|
13585
|
+
const resolvedHooksFile = hooksFile ?? import_node_path56.default.join(serviceDir, useTs ? "src/hooks.server.ts" : "src/hooks.server.js");
|
|
13223
13586
|
const dependencyEdits = buildDependencyEdits(pkg, manifestPath);
|
|
13224
13587
|
const generatedFiles = [];
|
|
13225
13588
|
const entrypointEdits = [];
|
|
@@ -13244,7 +13607,7 @@ async function planSvelteKit(serviceDir, pkg, manifestPath, hooksFile, project)
|
|
|
13244
13607
|
});
|
|
13245
13608
|
} else {
|
|
13246
13609
|
try {
|
|
13247
|
-
const raw = await
|
|
13610
|
+
const raw = await import_node_fs37.promises.readFile(hooksFile, "utf8");
|
|
13248
13611
|
if (!fileImportsOtelHook(raw, ["./otel-init"])) {
|
|
13249
13612
|
const lines = raw.split(/\r?\n/);
|
|
13250
13613
|
const firstReal = lines[0]?.startsWith("#!") ? lines[1] ?? "" : lines[0] ?? "";
|
|
@@ -13281,11 +13644,11 @@ async function planSvelteKit(serviceDir, pkg, manifestPath, hooksFile, project)
|
|
|
13281
13644
|
}
|
|
13282
13645
|
async function planNuxt(serviceDir, pkg, manifestPath, project) {
|
|
13283
13646
|
const useTs = await isTypeScriptProject(serviceDir);
|
|
13284
|
-
const otelPluginFile =
|
|
13647
|
+
const otelPluginFile = import_node_path56.default.join(
|
|
13285
13648
|
serviceDir,
|
|
13286
13649
|
useTs ? "server/plugins/otel.ts" : "server/plugins/otel.js"
|
|
13287
13650
|
);
|
|
13288
|
-
const otelInitFile =
|
|
13651
|
+
const otelInitFile = import_node_path56.default.join(
|
|
13289
13652
|
serviceDir,
|
|
13290
13653
|
useTs ? "server/plugins/otel-init.ts" : "server/plugins/otel-init.js"
|
|
13291
13654
|
);
|
|
@@ -13336,19 +13699,19 @@ async function planNuxt(serviceDir, pkg, manifestPath, project) {
|
|
|
13336
13699
|
var ASTRO_MIDDLEWARE_CANDIDATES = ["src/middleware.ts", "src/middleware.js"];
|
|
13337
13700
|
async function findAstroMiddleware(serviceDir) {
|
|
13338
13701
|
for (const rel of ASTRO_MIDDLEWARE_CANDIDATES) {
|
|
13339
|
-
const candidate =
|
|
13702
|
+
const candidate = import_node_path56.default.join(serviceDir, rel);
|
|
13340
13703
|
if (await exists3(candidate)) return candidate;
|
|
13341
13704
|
}
|
|
13342
13705
|
return null;
|
|
13343
13706
|
}
|
|
13344
13707
|
async function planAstro(serviceDir, pkg, manifestPath, project) {
|
|
13345
13708
|
const useTs = await isTypeScriptProject(serviceDir);
|
|
13346
|
-
const otelInitFile =
|
|
13709
|
+
const otelInitFile = import_node_path56.default.join(
|
|
13347
13710
|
serviceDir,
|
|
13348
13711
|
useTs ? "src/otel-init.ts" : "src/otel-init.js"
|
|
13349
13712
|
);
|
|
13350
13713
|
const existingMiddleware = await findAstroMiddleware(serviceDir);
|
|
13351
|
-
const middlewareFile = existingMiddleware ??
|
|
13714
|
+
const middlewareFile = existingMiddleware ?? import_node_path56.default.join(serviceDir, useTs ? "src/middleware.ts" : "src/middleware.js");
|
|
13352
13715
|
const dependencyEdits = buildDependencyEdits(pkg, manifestPath);
|
|
13353
13716
|
const generatedFiles = [];
|
|
13354
13717
|
const entrypointEdits = [];
|
|
@@ -13373,7 +13736,7 @@ async function planAstro(serviceDir, pkg, manifestPath, project) {
|
|
|
13373
13736
|
});
|
|
13374
13737
|
} else {
|
|
13375
13738
|
try {
|
|
13376
|
-
const raw = await
|
|
13739
|
+
const raw = await import_node_fs37.promises.readFile(existingMiddleware, "utf8");
|
|
13377
13740
|
if (!fileImportsOtelHook(raw, ["./otel-init"])) {
|
|
13378
13741
|
const lines = raw.split(/\r?\n/);
|
|
13379
13742
|
const firstReal = lines[0]?.startsWith("#!") ? lines[1] ?? "" : lines[0] ?? "";
|
|
@@ -13444,7 +13807,7 @@ async function findFrameworkDispatch(serviceDir, pkg, manifestPath, project) {
|
|
|
13444
13807
|
}
|
|
13445
13808
|
async function plan(serviceDir, opts) {
|
|
13446
13809
|
const pkg = await readPackageJson2(serviceDir);
|
|
13447
|
-
const manifestPath =
|
|
13810
|
+
const manifestPath = import_node_path56.default.join(serviceDir, "package.json");
|
|
13448
13811
|
const project = opts?.project;
|
|
13449
13812
|
const empty = {
|
|
13450
13813
|
language: "javascript",
|
|
@@ -13479,8 +13842,8 @@ async function plan(serviceDir, opts) {
|
|
|
13479
13842
|
return { ...empty, libOnly: true };
|
|
13480
13843
|
}
|
|
13481
13844
|
const flavor = dispatchEntry(entryFile, pkg);
|
|
13482
|
-
const otelInitFile =
|
|
13483
|
-
const envNeatFile =
|
|
13845
|
+
const otelInitFile = import_node_path56.default.join(import_node_path56.default.dirname(entryFile), otelInitFilename(flavor));
|
|
13846
|
+
const envNeatFile = import_node_path56.default.join(serviceDir, ".env.neat");
|
|
13484
13847
|
const existingDeps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
13485
13848
|
const dependencyEdits = [];
|
|
13486
13849
|
for (const sdk of SDK_PACKAGES) {
|
|
@@ -13504,7 +13867,7 @@ async function plan(serviceDir, opts) {
|
|
|
13504
13867
|
}
|
|
13505
13868
|
const entrypointEdits = [];
|
|
13506
13869
|
try {
|
|
13507
|
-
const raw = await
|
|
13870
|
+
const raw = await import_node_fs37.promises.readFile(entryFile, "utf8");
|
|
13508
13871
|
const lines = raw.split(/\r?\n/);
|
|
13509
13872
|
const firstReal = lines[0]?.startsWith("#!") ? lines[1] ?? "" : lines[0] ?? "";
|
|
13510
13873
|
if (!lineIsOtelInjection(firstReal)) {
|
|
@@ -13549,13 +13912,13 @@ async function plan(serviceDir, opts) {
|
|
|
13549
13912
|
};
|
|
13550
13913
|
}
|
|
13551
13914
|
function isAllowedWritePath(serviceDir, target) {
|
|
13552
|
-
const rel =
|
|
13915
|
+
const rel = import_node_path56.default.relative(serviceDir, target);
|
|
13553
13916
|
if (rel.startsWith("..")) return false;
|
|
13554
|
-
const base =
|
|
13917
|
+
const base = import_node_path56.default.basename(target);
|
|
13555
13918
|
if (base === "package.json") return true;
|
|
13556
13919
|
if (base === ".env.neat") return true;
|
|
13557
13920
|
if (/^otel-init\.(?:js|cjs|mjs|ts)$/.test(base)) return true;
|
|
13558
|
-
const relPosix = rel.split(
|
|
13921
|
+
const relPosix = rel.split(import_node_path56.default.sep).join("/");
|
|
13559
13922
|
if (/^instrumentation(?:\.(?:node|edge))?\.(?:js|cjs|mjs|ts)$/.test(base)) {
|
|
13560
13923
|
if (relPosix === base) return true;
|
|
13561
13924
|
if (relPosix === `src/${base}`) return true;
|
|
@@ -13572,10 +13935,10 @@ function isAllowedWritePath(serviceDir, target) {
|
|
|
13572
13935
|
return false;
|
|
13573
13936
|
}
|
|
13574
13937
|
async function writeAtomic(file, contents) {
|
|
13575
|
-
await
|
|
13938
|
+
await import_node_fs37.promises.mkdir(import_node_path56.default.dirname(file), { recursive: true });
|
|
13576
13939
|
const tmp = `${file}.${process.pid}.${Date.now()}.tmp`;
|
|
13577
|
-
await
|
|
13578
|
-
await
|
|
13940
|
+
await import_node_fs37.promises.writeFile(tmp, contents, "utf8");
|
|
13941
|
+
await import_node_fs37.promises.rename(tmp, file);
|
|
13579
13942
|
}
|
|
13580
13943
|
async function apply(installPlan) {
|
|
13581
13944
|
const { serviceDir } = installPlan;
|
|
@@ -13651,7 +14014,7 @@ async function apply(installPlan) {
|
|
|
13651
14014
|
for (const target of allTargets) {
|
|
13652
14015
|
if (await exists3(target)) {
|
|
13653
14016
|
try {
|
|
13654
|
-
originals.set(target, await
|
|
14017
|
+
originals.set(target, await import_node_fs37.promises.readFile(target, "utf8"));
|
|
13655
14018
|
} catch {
|
|
13656
14019
|
}
|
|
13657
14020
|
}
|
|
@@ -13734,14 +14097,14 @@ async function rollback(installPlan, originals, createdFiles) {
|
|
|
13734
14097
|
const removed = [];
|
|
13735
14098
|
for (const [file, raw] of originals.entries()) {
|
|
13736
14099
|
try {
|
|
13737
|
-
await
|
|
14100
|
+
await import_node_fs37.promises.writeFile(file, raw, "utf8");
|
|
13738
14101
|
restored.push(file);
|
|
13739
14102
|
} catch {
|
|
13740
14103
|
}
|
|
13741
14104
|
}
|
|
13742
14105
|
for (const file of createdFiles) {
|
|
13743
14106
|
try {
|
|
13744
|
-
await
|
|
14107
|
+
await import_node_fs37.promises.unlink(file);
|
|
13745
14108
|
removed.push(file);
|
|
13746
14109
|
} catch {
|
|
13747
14110
|
}
|
|
@@ -13756,8 +14119,8 @@ async function rollback(installPlan, originals, createdFiles) {
|
|
|
13756
14119
|
...removed.map((f) => `removed: ${f}`),
|
|
13757
14120
|
""
|
|
13758
14121
|
];
|
|
13759
|
-
const rollbackPath =
|
|
13760
|
-
await
|
|
14122
|
+
const rollbackPath = import_node_path56.default.join(installPlan.serviceDir, "neat-rollback.patch");
|
|
14123
|
+
await import_node_fs37.promises.writeFile(rollbackPath, lines.join("\n"), "utf8");
|
|
13761
14124
|
}
|
|
13762
14125
|
function injectInstrumentationHook(raw) {
|
|
13763
14126
|
if (raw.includes("instrumentationHook")) return raw;
|
|
@@ -13786,8 +14149,8 @@ var javascriptInstaller = {
|
|
|
13786
14149
|
|
|
13787
14150
|
// src/installers/python.ts
|
|
13788
14151
|
init_cjs_shims();
|
|
13789
|
-
var
|
|
13790
|
-
var
|
|
14152
|
+
var import_node_fs38 = require("fs");
|
|
14153
|
+
var import_node_path57 = __toESM(require("path"), 1);
|
|
13791
14154
|
var SDK_PACKAGES2 = [
|
|
13792
14155
|
{ name: "opentelemetry-distro", version: ">=0.49b0" },
|
|
13793
14156
|
{ name: "opentelemetry-exporter-otlp", version: ">=1.28.0" }
|
|
@@ -13799,7 +14162,7 @@ var OTEL_ENV2 = {
|
|
|
13799
14162
|
};
|
|
13800
14163
|
async function exists4(p) {
|
|
13801
14164
|
try {
|
|
13802
|
-
await
|
|
14165
|
+
await import_node_fs38.promises.stat(p);
|
|
13803
14166
|
return true;
|
|
13804
14167
|
} catch {
|
|
13805
14168
|
return false;
|
|
@@ -13808,7 +14171,7 @@ async function exists4(p) {
|
|
|
13808
14171
|
async function detect2(serviceDir) {
|
|
13809
14172
|
const markers = ["requirements.txt", "pyproject.toml", "setup.py"];
|
|
13810
14173
|
for (const m of markers) {
|
|
13811
|
-
if (await exists4(
|
|
14174
|
+
if (await exists4(import_node_path57.default.join(serviceDir, m))) return true;
|
|
13812
14175
|
}
|
|
13813
14176
|
return false;
|
|
13814
14177
|
}
|
|
@@ -13818,9 +14181,9 @@ function reqPackageName(line) {
|
|
|
13818
14181
|
return head.replace(/[<>=!~].*$/, "").toLowerCase();
|
|
13819
14182
|
}
|
|
13820
14183
|
async function planRequirementsTxtEdits(serviceDir) {
|
|
13821
|
-
const file =
|
|
14184
|
+
const file = import_node_path57.default.join(serviceDir, "requirements.txt");
|
|
13822
14185
|
if (!await exists4(file)) return null;
|
|
13823
|
-
const raw = await
|
|
14186
|
+
const raw = await import_node_fs38.promises.readFile(file, "utf8");
|
|
13824
14187
|
const presentNames = new Set(
|
|
13825
14188
|
raw.split(/\r?\n/).map(reqPackageName).filter((n) => n.length > 0)
|
|
13826
14189
|
);
|
|
@@ -13828,9 +14191,9 @@ async function planRequirementsTxtEdits(serviceDir) {
|
|
|
13828
14191
|
return { manifest: file, missing: [...missing] };
|
|
13829
14192
|
}
|
|
13830
14193
|
async function planProcfileEdits(serviceDir) {
|
|
13831
|
-
const procfile =
|
|
14194
|
+
const procfile = import_node_path57.default.join(serviceDir, "Procfile");
|
|
13832
14195
|
if (!await exists4(procfile)) return [];
|
|
13833
|
-
const raw = await
|
|
14196
|
+
const raw = await import_node_fs38.promises.readFile(procfile, "utf8");
|
|
13834
14197
|
const edits = [];
|
|
13835
14198
|
for (const line of raw.split(/\r?\n/)) {
|
|
13836
14199
|
if (line.length === 0) continue;
|
|
@@ -13882,8 +14245,8 @@ async function applyRequirementsTxt(manifest, edits, original) {
|
|
|
13882
14245
|
const next = `${original}${trailing}${newlines.join("\n")}
|
|
13883
14246
|
`;
|
|
13884
14247
|
const tmp = `${manifest}.${process.pid}.${Date.now()}.tmp`;
|
|
13885
|
-
await
|
|
13886
|
-
await
|
|
14248
|
+
await import_node_fs38.promises.writeFile(tmp, next, "utf8");
|
|
14249
|
+
await import_node_fs38.promises.rename(tmp, manifest);
|
|
13887
14250
|
}
|
|
13888
14251
|
async function applyProcfile(procfile, edits, original) {
|
|
13889
14252
|
let next = original;
|
|
@@ -13892,8 +14255,8 @@ async function applyProcfile(procfile, edits, original) {
|
|
|
13892
14255
|
next = next.replace(e.before, e.after);
|
|
13893
14256
|
}
|
|
13894
14257
|
const tmp = `${procfile}.${process.pid}.${Date.now()}.tmp`;
|
|
13895
|
-
await
|
|
13896
|
-
await
|
|
14258
|
+
await import_node_fs38.promises.writeFile(tmp, next, "utf8");
|
|
14259
|
+
await import_node_fs38.promises.rename(tmp, procfile);
|
|
13897
14260
|
}
|
|
13898
14261
|
async function apply2(installPlan) {
|
|
13899
14262
|
const { serviceDir } = installPlan;
|
|
@@ -13906,7 +14269,7 @@ async function apply2(installPlan) {
|
|
|
13906
14269
|
const originals = /* @__PURE__ */ new Map();
|
|
13907
14270
|
for (const file of touched) {
|
|
13908
14271
|
try {
|
|
13909
|
-
originals.set(file, await
|
|
14272
|
+
originals.set(file, await import_node_fs38.promises.readFile(file, "utf8"));
|
|
13910
14273
|
} catch {
|
|
13911
14274
|
}
|
|
13912
14275
|
}
|
|
@@ -13917,7 +14280,7 @@ async function apply2(installPlan) {
|
|
|
13917
14280
|
if (raw === void 0) {
|
|
13918
14281
|
throw new Error(`python installer: cannot read ${file} during apply`);
|
|
13919
14282
|
}
|
|
13920
|
-
const base =
|
|
14283
|
+
const base = import_node_path57.default.basename(file);
|
|
13921
14284
|
if (base === "requirements.txt") {
|
|
13922
14285
|
const edits = installPlan.dependencyEdits.filter((e) => e.file === file);
|
|
13923
14286
|
if (edits.length > 0) {
|
|
@@ -13942,7 +14305,7 @@ async function rollback2(installPlan, originals) {
|
|
|
13942
14305
|
const restored = [];
|
|
13943
14306
|
for (const [file, raw] of originals.entries()) {
|
|
13944
14307
|
try {
|
|
13945
|
-
await
|
|
14308
|
+
await import_node_fs38.promises.writeFile(file, raw, "utf8");
|
|
13946
14309
|
restored.push(file);
|
|
13947
14310
|
} catch {
|
|
13948
14311
|
}
|
|
@@ -13956,8 +14319,8 @@ async function rollback2(installPlan, originals) {
|
|
|
13956
14319
|
...restored.map((f) => `restored: ${f}`),
|
|
13957
14320
|
""
|
|
13958
14321
|
];
|
|
13959
|
-
const rollbackPath =
|
|
13960
|
-
await
|
|
14322
|
+
const rollbackPath = import_node_path57.default.join(installPlan.serviceDir, "neat-rollback.patch");
|
|
14323
|
+
await import_node_fs38.promises.writeFile(rollbackPath, lines.join("\n"), "utf8");
|
|
13961
14324
|
}
|
|
13962
14325
|
var pythonInstaller = {
|
|
13963
14326
|
name: "python",
|
|
@@ -14080,10 +14443,10 @@ function renderPatch(sections) {
|
|
|
14080
14443
|
|
|
14081
14444
|
// src/orchestrator.ts
|
|
14082
14445
|
init_cjs_shims();
|
|
14083
|
-
var
|
|
14446
|
+
var import_node_fs39 = require("fs");
|
|
14084
14447
|
var import_node_http = __toESM(require("http"), 1);
|
|
14085
14448
|
var import_node_net = __toESM(require("net"), 1);
|
|
14086
|
-
var
|
|
14449
|
+
var import_node_path58 = __toESM(require("path"), 1);
|
|
14087
14450
|
var import_node_child_process3 = require("child_process");
|
|
14088
14451
|
var import_node_readline = __toESM(require("readline"), 1);
|
|
14089
14452
|
async function extractAndPersist(opts) {
|
|
@@ -14092,7 +14455,7 @@ async function extractAndPersist(opts) {
|
|
|
14092
14455
|
const graphKey = opts.projectExplicit ? opts.project : DEFAULT_PROJECT;
|
|
14093
14456
|
resetGraph(graphKey);
|
|
14094
14457
|
const graph = getGraph(graphKey);
|
|
14095
|
-
const projectPaths = pathsForProject(graphKey,
|
|
14458
|
+
const projectPaths = pathsForProject(graphKey, import_node_path58.default.join(opts.scanPath, "neat-out"));
|
|
14096
14459
|
const extraction = await extractFromDirectory(graph, opts.scanPath, {
|
|
14097
14460
|
errorsPath: projectPaths.errorsPath
|
|
14098
14461
|
});
|
|
@@ -14144,7 +14507,7 @@ async function applyInstallersOver(services, project, options = {}) {
|
|
|
14144
14507
|
libOnly++;
|
|
14145
14508
|
const appDeps = svc.pkg ? appFrameworkDependencies(svc.pkg) : [];
|
|
14146
14509
|
if (appDeps.length > 0) {
|
|
14147
|
-
const svcName =
|
|
14510
|
+
const svcName = import_node_path58.default.basename(svc.dir);
|
|
14148
14511
|
const list = appDeps.join(", ");
|
|
14149
14512
|
console.warn(
|
|
14150
14513
|
`neat: runtime layer won't engage for ${svcName}: no entry point found.
|
|
@@ -14157,7 +14520,7 @@ async function applyInstallersOver(services, project, options = {}) {
|
|
|
14157
14520
|
console.log(`skipping ${svc.dir}: browser bundle; browser-OTel support lands in a future release.`);
|
|
14158
14521
|
} else if (outcome.outcome === "react-native") {
|
|
14159
14522
|
reactNative++;
|
|
14160
|
-
const svcName =
|
|
14523
|
+
const svcName = import_node_path58.default.basename(svc.dir);
|
|
14161
14524
|
console.log(
|
|
14162
14525
|
`neat: ${svc.dir} detected as React Native / Expo
|
|
14163
14526
|
The installer doesn't cover this runtime deterministically.
|
|
@@ -14168,7 +14531,7 @@ async function applyInstallersOver(services, project, options = {}) {
|
|
|
14168
14531
|
);
|
|
14169
14532
|
} else if (outcome.outcome === "bun") {
|
|
14170
14533
|
bun++;
|
|
14171
|
-
const svcName =
|
|
14534
|
+
const svcName = import_node_path58.default.basename(svc.dir);
|
|
14172
14535
|
console.log(
|
|
14173
14536
|
`neat: ${svc.dir} detected as Bun
|
|
14174
14537
|
The installer doesn't cover this runtime deterministically.
|
|
@@ -14179,7 +14542,7 @@ async function applyInstallersOver(services, project, options = {}) {
|
|
|
14179
14542
|
);
|
|
14180
14543
|
} else if (outcome.outcome === "deno") {
|
|
14181
14544
|
deno++;
|
|
14182
|
-
const svcName =
|
|
14545
|
+
const svcName = import_node_path58.default.basename(svc.dir);
|
|
14183
14546
|
console.log(
|
|
14184
14547
|
`neat: ${svc.dir} detected as Deno
|
|
14185
14548
|
The installer doesn't cover this runtime deterministically.
|
|
@@ -14190,7 +14553,7 @@ async function applyInstallersOver(services, project, options = {}) {
|
|
|
14190
14553
|
);
|
|
14191
14554
|
} else if (outcome.outcome === "cloudflare-workers") {
|
|
14192
14555
|
cloudflareWorkers++;
|
|
14193
|
-
const svcName =
|
|
14556
|
+
const svcName = import_node_path58.default.basename(svc.dir);
|
|
14194
14557
|
console.log(
|
|
14195
14558
|
`neat: ${svc.dir} detected as Cloudflare Workers
|
|
14196
14559
|
The installer doesn't cover this runtime deterministically.
|
|
@@ -14201,7 +14564,7 @@ async function applyInstallersOver(services, project, options = {}) {
|
|
|
14201
14564
|
);
|
|
14202
14565
|
} else if (outcome.outcome === "electron") {
|
|
14203
14566
|
electron++;
|
|
14204
|
-
const svcName =
|
|
14567
|
+
const svcName = import_node_path58.default.basename(svc.dir);
|
|
14205
14568
|
console.log(
|
|
14206
14569
|
`neat: ${svc.dir} detected as Electron
|
|
14207
14570
|
The installer doesn't cover this runtime deterministically.
|
|
@@ -14214,7 +14577,7 @@ async function applyInstallersOver(services, project, options = {}) {
|
|
|
14214
14577
|
if (svc.pkg && (outcome.outcome === "instrumented" || outcome.outcome === "already-instrumented")) {
|
|
14215
14578
|
const gaps = uninstrumentedLibraries(svc.pkg);
|
|
14216
14579
|
if (gaps.length > 0) {
|
|
14217
|
-
const svcName =
|
|
14580
|
+
const svcName = import_node_path58.default.basename(svc.dir);
|
|
14218
14581
|
const list = gaps.join(", ");
|
|
14219
14582
|
const subject = gaps.length === 1 ? "this library" : "these libraries";
|
|
14220
14583
|
const aux = gaps.length === 1 ? "isn't" : "aren't";
|
|
@@ -14420,24 +14783,24 @@ async function persistedPortsFor(scanPath) {
|
|
|
14420
14783
|
return { rest: record.ports.rest, otlp: record.ports.otlp, web: record.ports.web };
|
|
14421
14784
|
}
|
|
14422
14785
|
async function acquireSpawnLock(scanPath) {
|
|
14423
|
-
const lockPath =
|
|
14424
|
-
await
|
|
14786
|
+
const lockPath = import_node_path58.default.join(scanPath, "neat-out", "daemon.spawn.lock");
|
|
14787
|
+
await import_node_fs39.promises.mkdir(import_node_path58.default.dirname(lockPath), { recursive: true });
|
|
14425
14788
|
const STALE_LOCK_MS = 6e4;
|
|
14426
14789
|
try {
|
|
14427
|
-
const fd = await
|
|
14790
|
+
const fd = await import_node_fs39.promises.open(lockPath, "wx");
|
|
14428
14791
|
await fd.writeFile(`${process.pid}
|
|
14429
14792
|
`, "utf8");
|
|
14430
14793
|
await fd.close();
|
|
14431
14794
|
return async () => {
|
|
14432
|
-
await
|
|
14795
|
+
await import_node_fs39.promises.unlink(lockPath).catch(() => {
|
|
14433
14796
|
});
|
|
14434
14797
|
};
|
|
14435
14798
|
} catch (err) {
|
|
14436
14799
|
if (err.code !== "EEXIST") return null;
|
|
14437
14800
|
try {
|
|
14438
|
-
const stat = await
|
|
14801
|
+
const stat = await import_node_fs39.promises.stat(lockPath);
|
|
14439
14802
|
if (Date.now() - stat.mtimeMs > STALE_LOCK_MS) {
|
|
14440
|
-
await
|
|
14803
|
+
await import_node_fs39.promises.unlink(lockPath).catch(() => {
|
|
14441
14804
|
});
|
|
14442
14805
|
return acquireSpawnLock(scanPath);
|
|
14443
14806
|
}
|
|
@@ -14466,13 +14829,13 @@ async function healthIsForProject(restPort, project) {
|
|
|
14466
14829
|
return false;
|
|
14467
14830
|
}
|
|
14468
14831
|
function daemonLogPath(projectPath2) {
|
|
14469
|
-
return
|
|
14832
|
+
return import_node_path58.default.join(projectPath2, "neat-out", "daemon.log");
|
|
14470
14833
|
}
|
|
14471
14834
|
function spawnDaemonDetached(spec) {
|
|
14472
|
-
const here =
|
|
14835
|
+
const here = import_node_path58.default.dirname(new URL(importMetaUrl).pathname);
|
|
14473
14836
|
const candidates = [
|
|
14474
|
-
|
|
14475
|
-
|
|
14837
|
+
import_node_path58.default.join(here, "neatd.cjs"),
|
|
14838
|
+
import_node_path58.default.join(here, "neatd.js")
|
|
14476
14839
|
];
|
|
14477
14840
|
let entry2 = null;
|
|
14478
14841
|
const fsSync = require("fs");
|
|
@@ -14502,7 +14865,7 @@ function spawnDaemonDetached(spec) {
|
|
|
14502
14865
|
let logFd = null;
|
|
14503
14866
|
if (spec) {
|
|
14504
14867
|
const logPath = daemonLogPath(spec.projectPath);
|
|
14505
|
-
fsSync.mkdirSync(
|
|
14868
|
+
fsSync.mkdirSync(import_node_path58.default.dirname(logPath), { recursive: true });
|
|
14506
14869
|
logFd = fsSync.openSync(logPath, "a");
|
|
14507
14870
|
}
|
|
14508
14871
|
const child = (0, import_node_child_process3.spawn)(process.execPath, [entry2, "start"], {
|
|
@@ -14541,7 +14904,7 @@ async function runOrchestrator(opts) {
|
|
|
14541
14904
|
browser: "skipped"
|
|
14542
14905
|
}
|
|
14543
14906
|
};
|
|
14544
|
-
const stat = await
|
|
14907
|
+
const stat = await import_node_fs39.promises.stat(opts.scanPath).catch(() => null);
|
|
14545
14908
|
if (!stat || !stat.isDirectory()) {
|
|
14546
14909
|
console.error(`neat: ${opts.scanPath} is not a directory`);
|
|
14547
14910
|
result.exitCode = 2;
|
|
@@ -14701,7 +15064,7 @@ async function runOrchestrator(opts) {
|
|
|
14701
15064
|
result.steps.browser = openBrowser(dashboardUrl);
|
|
14702
15065
|
}
|
|
14703
15066
|
const daemonRunning = result.steps.daemon === "spawned" || result.steps.daemon === "already-running";
|
|
14704
|
-
const daemonLog = daemonRunning ?
|
|
15067
|
+
const daemonLog = daemonRunning ? import_node_path58.default.relative(opts.scanPath, daemonLogPath(opts.scanPath)) : null;
|
|
14705
15068
|
printSummary(result, graph, dashboardUrl, daemonLog);
|
|
14706
15069
|
return result;
|
|
14707
15070
|
}
|
|
@@ -15069,11 +15432,11 @@ async function runConnectorCommand(rawArgs, deps = {}) {
|
|
|
15069
15432
|
|
|
15070
15433
|
// src/cli-verbs.ts
|
|
15071
15434
|
init_cjs_shims();
|
|
15072
|
-
var
|
|
15435
|
+
var import_node_path59 = __toESM(require("path"), 1);
|
|
15073
15436
|
|
|
15074
15437
|
// src/cli-client.ts
|
|
15075
15438
|
init_cjs_shims();
|
|
15076
|
-
var
|
|
15439
|
+
var import_types47 = require("@neat.is/types");
|
|
15077
15440
|
var HttpError = class extends Error {
|
|
15078
15441
|
constructor(status2, message, responseBody = "") {
|
|
15079
15442
|
super(message);
|
|
@@ -15098,10 +15461,10 @@ function createHttpClient(baseUrl, bearerToken) {
|
|
|
15098
15461
|
const root = baseUrl.replace(/\/$/, "");
|
|
15099
15462
|
const authHeader = bearerToken && bearerToken.length > 0 ? { authorization: `Bearer ${bearerToken}` } : {};
|
|
15100
15463
|
return {
|
|
15101
|
-
async get(
|
|
15464
|
+
async get(path61) {
|
|
15102
15465
|
let res;
|
|
15103
15466
|
try {
|
|
15104
|
-
res = await fetch(`${root}${
|
|
15467
|
+
res = await fetch(`${root}${path61}`, {
|
|
15105
15468
|
headers: { ...authHeader }
|
|
15106
15469
|
});
|
|
15107
15470
|
} catch (err) {
|
|
@@ -15113,16 +15476,16 @@ function createHttpClient(baseUrl, bearerToken) {
|
|
|
15113
15476
|
const body = await res.text().catch(() => "");
|
|
15114
15477
|
throw new HttpError(
|
|
15115
15478
|
res.status,
|
|
15116
|
-
`${res.status} ${res.statusText} on GET ${
|
|
15479
|
+
`${res.status} ${res.statusText} on GET ${path61}: ${body}`,
|
|
15117
15480
|
body
|
|
15118
15481
|
);
|
|
15119
15482
|
}
|
|
15120
15483
|
return await res.json();
|
|
15121
15484
|
},
|
|
15122
|
-
async post(
|
|
15485
|
+
async post(path61, body) {
|
|
15123
15486
|
let res;
|
|
15124
15487
|
try {
|
|
15125
|
-
res = await fetch(`${root}${
|
|
15488
|
+
res = await fetch(`${root}${path61}`, {
|
|
15126
15489
|
method: "POST",
|
|
15127
15490
|
headers: { "content-type": "application/json", ...authHeader },
|
|
15128
15491
|
body: JSON.stringify(body)
|
|
@@ -15136,7 +15499,7 @@ function createHttpClient(baseUrl, bearerToken) {
|
|
|
15136
15499
|
const text = await res.text().catch(() => "");
|
|
15137
15500
|
throw new HttpError(
|
|
15138
15501
|
res.status,
|
|
15139
|
-
`${res.status} ${res.statusText} on POST ${
|
|
15502
|
+
`${res.status} ${res.statusText} on POST ${path61}: ${text}`,
|
|
15140
15503
|
text
|
|
15141
15504
|
);
|
|
15142
15505
|
}
|
|
@@ -15150,12 +15513,12 @@ function projectPath(project, suffix) {
|
|
|
15150
15513
|
}
|
|
15151
15514
|
async function runRootCause(client, input) {
|
|
15152
15515
|
const qs = input.errorId ? `?errorId=${encodeURIComponent(input.errorId)}` : "";
|
|
15153
|
-
const
|
|
15516
|
+
const path61 = projectPath(
|
|
15154
15517
|
input.project,
|
|
15155
15518
|
`/graph/root-cause/${encodeURIComponent(input.errorNode)}${qs}`
|
|
15156
15519
|
);
|
|
15157
15520
|
try {
|
|
15158
|
-
const result = await client.get(
|
|
15521
|
+
const result = await client.get(path61);
|
|
15159
15522
|
const arrowPath = result.traversalPath.join(" \u2190 ");
|
|
15160
15523
|
const provenances = result.edgeProvenances.length ? result.edgeProvenances.join(", ") : "(direct, no edges traversed)";
|
|
15161
15524
|
const summary = `Root cause for ${input.errorNode} is ${result.rootCauseNode}. ` + result.rootCauseReason + (result.fixRecommendation ? ` Recommended fix: ${result.fixRecommendation}.` : "");
|
|
@@ -15181,12 +15544,12 @@ async function runRootCause(client, input) {
|
|
|
15181
15544
|
}
|
|
15182
15545
|
async function runBlastRadius(client, input) {
|
|
15183
15546
|
const qs = input.depth !== void 0 ? `?depth=${input.depth}` : "";
|
|
15184
|
-
const
|
|
15547
|
+
const path61 = projectPath(
|
|
15185
15548
|
input.project,
|
|
15186
15549
|
`/graph/blast-radius/${encodeURIComponent(input.nodeId)}${qs}`
|
|
15187
15550
|
);
|
|
15188
15551
|
try {
|
|
15189
|
-
const result = await client.get(
|
|
15552
|
+
const result = await client.get(path61);
|
|
15190
15553
|
if (result.totalAffected === 0) {
|
|
15191
15554
|
return {
|
|
15192
15555
|
summary: `${result.origin} has no dependents. Nothing else would break if it failed.`
|
|
@@ -15215,17 +15578,17 @@ async function runBlastRadius(client, input) {
|
|
|
15215
15578
|
}
|
|
15216
15579
|
}
|
|
15217
15580
|
function formatBlastEntry(n) {
|
|
15218
|
-
const tag = n.edgeProvenance ===
|
|
15581
|
+
const tag = n.edgeProvenance === import_types47.Provenance.STALE ? " [STALE \u2014 last seen too long ago]" : "";
|
|
15219
15582
|
return ` \u2022 ${n.nodeId} (distance ${n.distance}, ${n.edgeProvenance})${tag}`;
|
|
15220
15583
|
}
|
|
15221
15584
|
async function runDependencies(client, input) {
|
|
15222
15585
|
const depth = input.depth ?? 3;
|
|
15223
|
-
const
|
|
15586
|
+
const path61 = projectPath(
|
|
15224
15587
|
input.project,
|
|
15225
15588
|
`/graph/dependencies/${encodeURIComponent(input.nodeId)}?depth=${depth}`
|
|
15226
15589
|
);
|
|
15227
15590
|
try {
|
|
15228
|
-
const result = await client.get(
|
|
15591
|
+
const result = await client.get(path61);
|
|
15229
15592
|
if (result.total === 0) {
|
|
15230
15593
|
return {
|
|
15231
15594
|
summary: depth === 1 ? `${input.nodeId} has no direct dependencies in the graph.` : `${input.nodeId} has no dependencies (BFS to depth ${depth}).`
|
|
@@ -15272,7 +15635,7 @@ async function runObservedDependencies(client, input) {
|
|
|
15272
15635
|
if (result.observed) {
|
|
15273
15636
|
return {
|
|
15274
15637
|
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:
|
|
15638
|
+
provenance: import_types47.Provenance.OBSERVED
|
|
15276
15639
|
};
|
|
15277
15640
|
}
|
|
15278
15641
|
const note = result.hasExtractedOutbound ? " Static (EXTRACTED) dependencies exist but no runtime traffic has been seen \u2014 is OTel running?" : "";
|
|
@@ -15282,7 +15645,7 @@ async function runObservedDependencies(client, input) {
|
|
|
15282
15645
|
return {
|
|
15283
15646
|
summary: `${input.nodeId} has ${result.dependencies.length} runtime dependenc${result.dependencies.length === 1 ? "y" : "ies"} confirmed by OTel.`,
|
|
15284
15647
|
block: blockLines.join("\n"),
|
|
15285
|
-
provenance:
|
|
15648
|
+
provenance: import_types47.Provenance.OBSERVED
|
|
15286
15649
|
};
|
|
15287
15650
|
} catch (err) {
|
|
15288
15651
|
if (err instanceof HttpError && err.status === 404) {
|
|
@@ -15317,9 +15680,9 @@ function formatDuration(ms) {
|
|
|
15317
15680
|
return `${Math.round(h / 24)}d`;
|
|
15318
15681
|
}
|
|
15319
15682
|
async function runIncidents(client, input) {
|
|
15320
|
-
const
|
|
15683
|
+
const path61 = input.nodeId ? projectPath(input.project, `/incidents/${encodeURIComponent(input.nodeId)}`) : projectPath(input.project, "/incidents");
|
|
15321
15684
|
try {
|
|
15322
|
-
const body = await client.get(
|
|
15685
|
+
const body = await client.get(path61);
|
|
15323
15686
|
const events = body.events;
|
|
15324
15687
|
if (events.length === 0) {
|
|
15325
15688
|
return {
|
|
@@ -15336,7 +15699,7 @@ async function runIncidents(client, input) {
|
|
|
15336
15699
|
return {
|
|
15337
15700
|
summary: `${target} has ${body.total} recorded incident${body.total === 1 ? "" : "s"}; showing the ${ordered.length} most recent.`,
|
|
15338
15701
|
block: blockLines.join("\n"),
|
|
15339
|
-
provenance:
|
|
15702
|
+
provenance: import_types47.Provenance.OBSERVED
|
|
15340
15703
|
};
|
|
15341
15704
|
} catch (err) {
|
|
15342
15705
|
if (err instanceof HttpError && err.status === 404) {
|
|
@@ -15445,7 +15808,7 @@ async function runStaleEdges(client, input) {
|
|
|
15445
15808
|
return {
|
|
15446
15809
|
summary: `${events.length} stale-edge transition${events.length === 1 ? "" : "s"} recorded${input.edgeType ? ` for ${input.edgeType}` : ""}.`,
|
|
15447
15810
|
block: blockLines.join("\n"),
|
|
15448
|
-
provenance:
|
|
15811
|
+
provenance: import_types47.Provenance.STALE
|
|
15449
15812
|
};
|
|
15450
15813
|
}
|
|
15451
15814
|
async function runPolicies(client, input) {
|
|
@@ -15609,7 +15972,7 @@ async function resolveProjectEntry(opts) {
|
|
|
15609
15972
|
const cwd = opts.cwd ?? process.cwd();
|
|
15610
15973
|
const resolvedCwd = await normalizeProjectPath(cwd);
|
|
15611
15974
|
for (const entry2 of entries) {
|
|
15612
|
-
if (resolvedCwd === entry2.path || resolvedCwd.startsWith(`${entry2.path}${
|
|
15975
|
+
if (resolvedCwd === entry2.path || resolvedCwd.startsWith(`${entry2.path}${import_node_path59.default.sep}`)) {
|
|
15613
15976
|
return entry2;
|
|
15614
15977
|
}
|
|
15615
15978
|
}
|
|
@@ -15762,7 +16125,7 @@ async function runSync(opts) {
|
|
|
15762
16125
|
}
|
|
15763
16126
|
|
|
15764
16127
|
// src/cli.ts
|
|
15765
|
-
var
|
|
16128
|
+
var import_types48 = require("@neat.is/types");
|
|
15766
16129
|
function isNpxInvocation() {
|
|
15767
16130
|
if (process.env.npm_command === "exec") return true;
|
|
15768
16131
|
const execpath = process.env.npm_execpath ?? "";
|
|
@@ -16055,7 +16418,7 @@ async function buildPatchSections(services, project) {
|
|
|
16055
16418
|
}
|
|
16056
16419
|
async function runInit(opts) {
|
|
16057
16420
|
const written = [];
|
|
16058
|
-
const stat = await
|
|
16421
|
+
const stat = await import_node_fs40.promises.stat(opts.scanPath).catch(() => null);
|
|
16059
16422
|
if (!stat || !stat.isDirectory()) {
|
|
16060
16423
|
console.error(`neat init: ${opts.scanPath} is not a directory`);
|
|
16061
16424
|
return { exitCode: 2, writtenFiles: written };
|
|
@@ -16064,13 +16427,13 @@ async function runInit(opts) {
|
|
|
16064
16427
|
printDiscoveryReport(opts, services);
|
|
16065
16428
|
const sections = opts.noInstall ? [] : await buildPatchSections(services, opts.project);
|
|
16066
16429
|
const patch = renderPatch(sections);
|
|
16067
|
-
const patchPath =
|
|
16430
|
+
const patchPath = import_node_path60.default.join(opts.scanPath, "neat.patch");
|
|
16068
16431
|
if (opts.dryRun) {
|
|
16069
|
-
await
|
|
16432
|
+
await import_node_fs40.promises.writeFile(patchPath, patch, "utf8");
|
|
16070
16433
|
written.push(patchPath);
|
|
16071
16434
|
console.log(`dry-run: patch written to ${patchPath}`);
|
|
16072
|
-
const gitignorePath =
|
|
16073
|
-
const gitignoreExists = await
|
|
16435
|
+
const gitignorePath = import_node_path60.default.join(opts.scanPath, ".gitignore");
|
|
16436
|
+
const gitignoreExists = await import_node_fs40.promises.stat(gitignorePath).then(() => true).catch(() => false);
|
|
16074
16437
|
const verb = gitignoreExists ? "append" : "create";
|
|
16075
16438
|
console.log(`dry-run: would ${verb} ${gitignorePath} (add neat-out/)`);
|
|
16076
16439
|
console.log("rerun without --dry-run to register and snapshot.");
|
|
@@ -16081,9 +16444,9 @@ async function runInit(opts) {
|
|
|
16081
16444
|
const graph = getGraph(graphKey);
|
|
16082
16445
|
const projectPaths = pathsForProject(
|
|
16083
16446
|
graphKey,
|
|
16084
|
-
|
|
16447
|
+
import_node_path60.default.join(opts.scanPath, "neat-out")
|
|
16085
16448
|
);
|
|
16086
|
-
const errorsPath =
|
|
16449
|
+
const errorsPath = import_node_path60.default.join(import_node_path60.default.dirname(opts.outPath), import_node_path60.default.basename(projectPaths.errorsPath));
|
|
16087
16450
|
const result = await extractFromDirectory(graph, opts.scanPath, { errorsPath });
|
|
16088
16451
|
await saveGraphToDisk(graph, opts.outPath);
|
|
16089
16452
|
written.push(opts.outPath);
|
|
@@ -16162,7 +16525,7 @@ async function runInit(opts) {
|
|
|
16162
16525
|
console.log("Run `npm install` (or your language equivalent) to refresh lockfiles.");
|
|
16163
16526
|
}
|
|
16164
16527
|
} else {
|
|
16165
|
-
await
|
|
16528
|
+
await import_node_fs40.promises.writeFile(patchPath, patch, "utf8");
|
|
16166
16529
|
written.push(patchPath);
|
|
16167
16530
|
}
|
|
16168
16531
|
}
|
|
@@ -16202,9 +16565,9 @@ var CLAUDE_SKILL_CONFIG = {
|
|
|
16202
16565
|
};
|
|
16203
16566
|
function claudeConfigPath() {
|
|
16204
16567
|
const override = process.env.NEAT_CLAUDE_CONFIG;
|
|
16205
|
-
if (override && override.length > 0) return
|
|
16568
|
+
if (override && override.length > 0) return import_node_path60.default.resolve(override);
|
|
16206
16569
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
16207
|
-
return
|
|
16570
|
+
return import_node_path60.default.join(home, ".claude.json");
|
|
16208
16571
|
}
|
|
16209
16572
|
async function runSkill(opts) {
|
|
16210
16573
|
const snippet2 = JSON.stringify(CLAUDE_SKILL_CONFIG, null, 2) + "\n";
|
|
@@ -16216,7 +16579,7 @@ async function runSkill(opts) {
|
|
|
16216
16579
|
const target = claudeConfigPath();
|
|
16217
16580
|
let existing = {};
|
|
16218
16581
|
try {
|
|
16219
|
-
existing = JSON.parse(await
|
|
16582
|
+
existing = JSON.parse(await import_node_fs40.promises.readFile(target, "utf8"));
|
|
16220
16583
|
} catch (err) {
|
|
16221
16584
|
if (err.code !== "ENOENT") {
|
|
16222
16585
|
console.error(`neat skill: failed to read ${target} \u2014 ${err.message}`);
|
|
@@ -16228,8 +16591,8 @@ async function runSkill(opts) {
|
|
|
16228
16591
|
...existing,
|
|
16229
16592
|
mcpServers: { ...mcp, neat: CLAUDE_SKILL_CONFIG.mcpServers.neat }
|
|
16230
16593
|
};
|
|
16231
|
-
await
|
|
16232
|
-
await
|
|
16594
|
+
await import_node_fs40.promises.mkdir(import_node_path60.default.dirname(target), { recursive: true });
|
|
16595
|
+
await import_node_fs40.promises.writeFile(target, JSON.stringify(merged, null, 2) + "\n", "utf8");
|
|
16233
16596
|
console.log(`neat skill: wrote mcpServers.neat to ${target}`);
|
|
16234
16597
|
console.log("restart Claude Code to pick up the new MCP server.");
|
|
16235
16598
|
return { exitCode: 0 };
|
|
@@ -16283,12 +16646,12 @@ async function main() {
|
|
|
16283
16646
|
console.error("neat init: --apply and --dry-run are mutually exclusive");
|
|
16284
16647
|
process.exit(2);
|
|
16285
16648
|
}
|
|
16286
|
-
const scanPath =
|
|
16649
|
+
const scanPath = import_node_path60.default.resolve(target);
|
|
16287
16650
|
const projectExplicit = parsed.project !== null;
|
|
16288
|
-
const projectName = projectExplicit ? project :
|
|
16651
|
+
const projectName = projectExplicit ? project : import_node_path60.default.basename(scanPath);
|
|
16289
16652
|
const projectKey = projectExplicit ? project : DEFAULT_PROJECT;
|
|
16290
|
-
const fallback = pathsForProject(projectKey,
|
|
16291
|
-
const outPath =
|
|
16653
|
+
const fallback = pathsForProject(projectKey, import_node_path60.default.join(scanPath, "neat-out")).snapshotPath;
|
|
16654
|
+
const outPath = import_node_path60.default.resolve(process.env.NEAT_OUT_PATH ?? fallback);
|
|
16292
16655
|
const result = await runInit({
|
|
16293
16656
|
scanPath,
|
|
16294
16657
|
outPath,
|
|
@@ -16309,21 +16672,21 @@ async function main() {
|
|
|
16309
16672
|
usage();
|
|
16310
16673
|
process.exit(2);
|
|
16311
16674
|
}
|
|
16312
|
-
const scanPath =
|
|
16313
|
-
const stat = await
|
|
16675
|
+
const scanPath = import_node_path60.default.resolve(target);
|
|
16676
|
+
const stat = await import_node_fs40.promises.stat(scanPath).catch(() => null);
|
|
16314
16677
|
if (!stat || !stat.isDirectory()) {
|
|
16315
16678
|
console.error(`neat watch: ${scanPath} is not a directory`);
|
|
16316
16679
|
process.exit(2);
|
|
16317
16680
|
}
|
|
16318
|
-
const projectPaths = pathsForProject(project,
|
|
16319
|
-
const outPath =
|
|
16320
|
-
const errorsPath =
|
|
16321
|
-
process.env.NEAT_ERRORS_PATH ??
|
|
16681
|
+
const projectPaths = pathsForProject(project, import_node_path60.default.join(scanPath, "neat-out"));
|
|
16682
|
+
const outPath = import_node_path60.default.resolve(process.env.NEAT_OUT_PATH ?? projectPaths.snapshotPath);
|
|
16683
|
+
const errorsPath = import_node_path60.default.resolve(
|
|
16684
|
+
process.env.NEAT_ERRORS_PATH ?? import_node_path60.default.join(import_node_path60.default.dirname(outPath), import_node_path60.default.basename(projectPaths.errorsPath))
|
|
16322
16685
|
);
|
|
16323
|
-
const staleEventsPath =
|
|
16324
|
-
process.env.NEAT_STALE_EVENTS_PATH ??
|
|
16686
|
+
const staleEventsPath = import_node_path60.default.resolve(
|
|
16687
|
+
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
16688
|
);
|
|
16326
|
-
const embeddingsCachePath = process.env.NEAT_EMBEDDINGS_CACHE_PATH ?
|
|
16689
|
+
const embeddingsCachePath = process.env.NEAT_EMBEDDINGS_CACHE_PATH ? import_node_path60.default.resolve(process.env.NEAT_EMBEDDINGS_CACHE_PATH) : void 0;
|
|
16327
16690
|
const handle = await startWatch(getGraph(project), {
|
|
16328
16691
|
scanPath,
|
|
16329
16692
|
outPath,
|
|
@@ -16506,11 +16869,11 @@ async function main() {
|
|
|
16506
16869
|
process.exit(1);
|
|
16507
16870
|
}
|
|
16508
16871
|
async function tryOrchestrator(cmd, parsed) {
|
|
16509
|
-
const scanPath =
|
|
16510
|
-
const stat = await
|
|
16872
|
+
const scanPath = import_node_path60.default.resolve(cmd);
|
|
16873
|
+
const stat = await import_node_fs40.promises.stat(scanPath).catch(() => null);
|
|
16511
16874
|
if (!stat || !stat.isDirectory()) return null;
|
|
16512
16875
|
const projectExplicit = parsed.project !== null;
|
|
16513
|
-
const projectName = projectExplicit ? parsed.project :
|
|
16876
|
+
const projectName = projectExplicit ? parsed.project : import_node_path60.default.basename(scanPath);
|
|
16514
16877
|
const result = await runOrchestrator({
|
|
16515
16878
|
scanPath,
|
|
16516
16879
|
project: projectName,
|
|
@@ -16699,10 +17062,10 @@ async function runQueryVerb(cmd, parsed) {
|
|
|
16699
17062
|
const parts = parsed.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
16700
17063
|
const out = [];
|
|
16701
17064
|
for (const p of parts) {
|
|
16702
|
-
const r =
|
|
17065
|
+
const r = import_types48.DivergenceTypeSchema.safeParse(p);
|
|
16703
17066
|
if (!r.success) {
|
|
16704
17067
|
console.error(
|
|
16705
|
-
`neat divergences: unknown --type "${p}". allowed: ${
|
|
17068
|
+
`neat divergences: unknown --type "${p}". allowed: ${import_types48.DivergenceTypeSchema.options.join(", ")}`
|
|
16706
17069
|
);
|
|
16707
17070
|
return 2;
|
|
16708
17071
|
}
|