@neat.is/core 0.7.8 → 0.7.10
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-LN75Z624.js → chunk-6T7ZHODF.js} +554 -65
- package/dist/chunk-6T7ZHODF.js.map +1 -0
- package/dist/{chunk-BIY46Q6U.js → chunk-BC53SCT7.js} +36 -2
- package/dist/chunk-BC53SCT7.js.map +1 -0
- package/dist/{chunk-OVRVDSUO.js → chunk-D4OX6MUX.js} +58 -4
- package/dist/chunk-D4OX6MUX.js.map +1 -0
- package/dist/{chunk-4GCV46AP.js → chunk-FCO5Z3RW.js} +2 -2
- package/dist/{chunk-Y43UCVZS.js → chunk-UUYCTH2E.js} +24 -8
- package/dist/chunk-UUYCTH2E.js.map +1 -0
- package/dist/cli.cjs +1392 -314
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +704 -176
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +645 -77
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +28 -1
- package/dist/index.d.ts +28 -1
- package/dist/index.js +4 -4
- package/dist/neatd.cjs +645 -77
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +3 -3
- package/dist/{otel-grpc-DPSAGRUU.js → otel-grpc-CO47JRIN.js} +3 -3
- package/dist/server.cjs +618 -74
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +4 -4
- package/package.json +2 -2
- package/dist/chunk-BIY46Q6U.js.map +0 -1
- package/dist/chunk-LN75Z624.js.map +0 -1
- package/dist/chunk-OVRVDSUO.js.map +0 -1
- package/dist/chunk-Y43UCVZS.js.map +0 -1
- /package/dist/{chunk-4GCV46AP.js.map → chunk-FCO5Z3RW.js.map} +0 -0
- /package/dist/{otel-grpc-DPSAGRUU.js.map → otel-grpc-CO47JRIN.js.map} +0 -0
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 path84 = (req.url.split("?")[0] ?? "").replace(/\/+$/, "");
|
|
65
|
+
if (exactUnauthPaths.has(path84) || PROJECT_SCOPED_UNAUTH_PATTERN.test(path84)) {
|
|
66
66
|
done();
|
|
67
67
|
return;
|
|
68
68
|
}
|
|
@@ -415,8 +415,8 @@ function websocketChannelPathOf(attrs) {
|
|
|
415
415
|
const v = attrs[key];
|
|
416
416
|
if (typeof v === "string" && v.length > 0) {
|
|
417
417
|
const q = v.indexOf("?");
|
|
418
|
-
const
|
|
419
|
-
if (
|
|
418
|
+
const path84 = q === -1 ? v : v.slice(0, q);
|
|
419
|
+
if (path84.length > 0) return path84;
|
|
420
420
|
}
|
|
421
421
|
}
|
|
422
422
|
return void 0;
|
|
@@ -504,13 +504,18 @@ function loadProtobufResponseEncoder() {
|
|
|
504
504
|
);
|
|
505
505
|
return exportTraceServiceResponseType;
|
|
506
506
|
}
|
|
507
|
-
function encodeProtobufResponseBody() {
|
|
508
|
-
if (cachedProtobufResponseBody) return cachedProtobufResponseBody;
|
|
507
|
+
function encodeProtobufResponseBody(rejected, message) {
|
|
509
508
|
const Type = loadProtobufResponseEncoder();
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
509
|
+
if (!rejected) {
|
|
510
|
+
if (cachedProtobufResponseBody) return cachedProtobufResponseBody;
|
|
511
|
+
const msg2 = Type.create({});
|
|
512
|
+
cachedProtobufResponseBody = Buffer.from(Type.encode(msg2).finish());
|
|
513
|
+
return cachedProtobufResponseBody;
|
|
514
|
+
}
|
|
515
|
+
const msg = Type.fromObject({
|
|
516
|
+
partial_success: { rejected_spans: rejected, error_message: message ?? "" }
|
|
517
|
+
});
|
|
518
|
+
return Buffer.from(Type.encode(msg).finish());
|
|
514
519
|
}
|
|
515
520
|
async function decodeProtobufBody(buf) {
|
|
516
521
|
const Type = loadProtobufDecoder();
|
|
@@ -625,6 +630,13 @@ async function buildOtelReceiver(opts) {
|
|
|
625
630
|
}
|
|
626
631
|
return reply.code(200).header("content-type", "application/json").send({ partialSuccess: {} });
|
|
627
632
|
}
|
|
633
|
+
function sendOtlpPartial(reply, flavor, rejected, message) {
|
|
634
|
+
if (flavor === "protobuf") {
|
|
635
|
+
const buf = encodeProtobufResponseBody(rejected, message);
|
|
636
|
+
return reply.code(200).header("content-type", "application/x-protobuf").send(buf);
|
|
637
|
+
}
|
|
638
|
+
return reply.code(200).header("content-type", "application/json").send({ partialSuccess: { rejectedSpans: rejected, errorMessage: message } });
|
|
639
|
+
}
|
|
628
640
|
app.addContentTypeParser(
|
|
629
641
|
"application/x-protobuf",
|
|
630
642
|
{ parseAs: "buffer", bodyLimit: opts.bodyLimit ?? 16 * 1024 * 1024 },
|
|
@@ -652,6 +664,10 @@ async function buildOtelReceiver(opts) {
|
|
|
652
664
|
}
|
|
653
665
|
}
|
|
654
666
|
enqueue(spans);
|
|
667
|
+
if (opts.classifyBareRoutability) {
|
|
668
|
+
const { rejected, message } = opts.classifyBareRoutability(spans);
|
|
669
|
+
if (rejected > 0) return sendOtlpPartial(reply, result.flavor, rejected, message);
|
|
670
|
+
}
|
|
655
671
|
return sendOtlpSuccess(reply, result.flavor);
|
|
656
672
|
});
|
|
657
673
|
app.post("/projects/:project/v1/traces", async (req, reply) => {
|
|
@@ -755,9 +771,9 @@ __export(cli_exports, {
|
|
|
755
771
|
});
|
|
756
772
|
module.exports = __toCommonJS(cli_exports);
|
|
757
773
|
init_cjs_shims();
|
|
758
|
-
var
|
|
774
|
+
var import_node_path83 = __toESM(require("path"), 1);
|
|
759
775
|
var import_node_os8 = __toESM(require("os"), 1);
|
|
760
|
-
var
|
|
776
|
+
var import_node_fs48 = require("fs");
|
|
761
777
|
|
|
762
778
|
// src/banner.ts
|
|
763
779
|
init_cjs_shims();
|
|
@@ -1325,19 +1341,19 @@ function confidenceFromMix(edges, now = Date.now()) {
|
|
|
1325
1341
|
function longestIncomingWalk(graph, start, maxDepth) {
|
|
1326
1342
|
let best = { path: [start], edges: [] };
|
|
1327
1343
|
const visited = /* @__PURE__ */ new Set([start]);
|
|
1328
|
-
function step(node,
|
|
1329
|
-
if (
|
|
1330
|
-
best = { path: [...
|
|
1344
|
+
function step(node, path84, edges) {
|
|
1345
|
+
if (path84.length > best.path.length) {
|
|
1346
|
+
best = { path: [...path84], edges: [...edges] };
|
|
1331
1347
|
}
|
|
1332
|
-
if (
|
|
1348
|
+
if (path84.length - 1 >= maxDepth) return;
|
|
1333
1349
|
const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
|
|
1334
1350
|
for (const [srcId, edge] of incoming) {
|
|
1335
1351
|
if (visited.has(srcId)) continue;
|
|
1336
1352
|
visited.add(srcId);
|
|
1337
|
-
|
|
1353
|
+
path84.push(srcId);
|
|
1338
1354
|
edges.push(edge);
|
|
1339
|
-
step(srcId,
|
|
1340
|
-
|
|
1355
|
+
step(srcId, path84, edges);
|
|
1356
|
+
path84.pop();
|
|
1341
1357
|
edges.pop();
|
|
1342
1358
|
visited.delete(srcId);
|
|
1343
1359
|
}
|
|
@@ -1544,26 +1560,26 @@ function dominantFailingCall(graph, serviceId9, visited) {
|
|
|
1544
1560
|
return best;
|
|
1545
1561
|
}
|
|
1546
1562
|
function followFailingCallChain(graph, originServiceId, maxDepth) {
|
|
1547
|
-
const
|
|
1563
|
+
const path84 = [originServiceId];
|
|
1548
1564
|
const edges = [];
|
|
1549
1565
|
const visited = /* @__PURE__ */ new Set([originServiceId]);
|
|
1550
1566
|
let current = originServiceId;
|
|
1551
1567
|
for (let depth = 0; depth < maxDepth; depth++) {
|
|
1552
1568
|
const hop = dominantFailingCall(graph, current, visited);
|
|
1553
1569
|
if (!hop) break;
|
|
1554
|
-
|
|
1570
|
+
path84.push(hop.nextService);
|
|
1555
1571
|
edges.push(hop.edge);
|
|
1556
1572
|
visited.add(hop.nextService);
|
|
1557
1573
|
current = hop.nextService;
|
|
1558
1574
|
}
|
|
1559
1575
|
if (edges.length === 0) return null;
|
|
1560
|
-
return { path:
|
|
1576
|
+
return { path: path84, edges, culprit: current };
|
|
1561
1577
|
}
|
|
1562
1578
|
function crossServiceRootCause(graph, originId, incidents, errorEvent) {
|
|
1563
1579
|
const chain = followFailingCallChain(graph, originId, ROOT_CAUSE_MAX_DEPTH);
|
|
1564
1580
|
if (!chain) return null;
|
|
1565
1581
|
const culprit = chain.culprit;
|
|
1566
|
-
const
|
|
1582
|
+
const path84 = [...chain.path];
|
|
1567
1583
|
const edgeProvenances = chain.edges.map((e) => e.provenance);
|
|
1568
1584
|
const baseConfidence = confidenceFromMix(chain.edges);
|
|
1569
1585
|
const confidence = Math.max(0, Math.min(1, baseConfidence * INCIDENT_ROOT_CAUSE_CONFIDENCE));
|
|
@@ -1571,14 +1587,14 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
|
|
|
1571
1587
|
if (loc) {
|
|
1572
1588
|
let rootCauseNode = culprit;
|
|
1573
1589
|
if (loc.fileNode) {
|
|
1574
|
-
|
|
1590
|
+
path84.push(loc.fileNode);
|
|
1575
1591
|
edgeProvenances.push(import_types.Provenance.OBSERVED);
|
|
1576
1592
|
rootCauseNode = loc.fileNode;
|
|
1577
1593
|
}
|
|
1578
1594
|
return import_types.RootCauseResultSchema.parse({
|
|
1579
1595
|
rootCauseNode,
|
|
1580
1596
|
rootCauseReason: loc.rootCauseReason,
|
|
1581
|
-
traversalPath:
|
|
1597
|
+
traversalPath: path84,
|
|
1582
1598
|
edgeProvenances,
|
|
1583
1599
|
confidence,
|
|
1584
1600
|
...loc.fixRecommendation ? { fixRecommendation: loc.fixRecommendation } : {}
|
|
@@ -1590,7 +1606,7 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
|
|
|
1590
1606
|
return import_types.RootCauseResultSchema.parse({
|
|
1591
1607
|
rootCauseNode: culprit,
|
|
1592
1608
|
rootCauseReason: `${culpritName} is failing downstream calls (${errs} observed error${errs === 1 ? "" : "s"})`,
|
|
1593
|
-
traversalPath:
|
|
1609
|
+
traversalPath: path84,
|
|
1594
1610
|
edgeProvenances,
|
|
1595
1611
|
confidence,
|
|
1596
1612
|
fixRecommendation: `Inspect ${culpritName}'s failing handler`
|
|
@@ -2258,6 +2274,7 @@ var import_yaml = require("yaml");
|
|
|
2258
2274
|
var import_types3 = require("@neat.is/types");
|
|
2259
2275
|
var SERVICE_FILE_EXTENSIONS = /* @__PURE__ */ new Set([".js", ".mjs", ".cjs", ".ts", ".tsx", ".py", ".go", ".rb", ".php"]);
|
|
2260
2276
|
var CONFIG_FILE_EXTENSIONS = /* @__PURE__ */ new Set([".yaml", ".yml"]);
|
|
2277
|
+
var JSON_CONFIG_FILENAMES = /* @__PURE__ */ new Set(["app.json", "app.config.json", "eas.json"]);
|
|
2261
2278
|
var IGNORED_DIRS = /* @__PURE__ */ new Set([
|
|
2262
2279
|
"node_modules",
|
|
2263
2280
|
".git",
|
|
@@ -2297,6 +2314,7 @@ async function isPythonVenvDir(dir) {
|
|
|
2297
2314
|
function isConfigFile(name) {
|
|
2298
2315
|
const ext = import_node_path4.default.extname(name);
|
|
2299
2316
|
if (CONFIG_FILE_EXTENSIONS.has(ext)) return { match: true, fileType: ext.slice(1) };
|
|
2317
|
+
if (JSON_CONFIG_FILENAMES.has(name)) return { match: true, fileType: "json" };
|
|
2300
2318
|
if (name === ".env" || name.startsWith(".env.")) {
|
|
2301
2319
|
if (isEnvTemplateFile(name)) return { match: false, fileType: "" };
|
|
2302
2320
|
if (isNeatAuthoredEnvFile(name)) return { match: false, fileType: "" };
|
|
@@ -3125,8 +3143,8 @@ function chiRoutesFromSource(source, parser) {
|
|
|
3125
3143
|
chiWalk(tree.rootNode, "", out);
|
|
3126
3144
|
return out;
|
|
3127
3145
|
}
|
|
3128
|
-
function stripChiRegex(
|
|
3129
|
-
return
|
|
3146
|
+
function stripChiRegex(path84) {
|
|
3147
|
+
return path84.replace(/\{([^{}:]+):[^{}]*\}/g, "{$1}");
|
|
3130
3148
|
}
|
|
3131
3149
|
function chiWalk(node, prefix, out) {
|
|
3132
3150
|
for (let i = 0; i < node.namedChildCount; i++) {
|
|
@@ -3798,9 +3816,9 @@ function rubyRocketRoute(args) {
|
|
|
3798
3816
|
if (!pair || pair.type !== "pair") continue;
|
|
3799
3817
|
const k = pair.childForFieldName("key");
|
|
3800
3818
|
if (k?.type !== "string") continue;
|
|
3801
|
-
const
|
|
3802
|
-
if (
|
|
3803
|
-
return { path:
|
|
3819
|
+
const path84 = rubyLiteral(k);
|
|
3820
|
+
if (path84 === null) continue;
|
|
3821
|
+
return { path: path84, target: rubyLiteral(pair.childForFieldName("value")) };
|
|
3804
3822
|
}
|
|
3805
3823
|
return null;
|
|
3806
3824
|
}
|
|
@@ -4420,7 +4438,7 @@ async function expressMountPrefixes(files, serviceDir, tsPaths) {
|
|
|
4420
4438
|
};
|
|
4421
4439
|
const filePrefix = /* @__PURE__ */ new Map();
|
|
4422
4440
|
const conflicted = /* @__PURE__ */ new Set();
|
|
4423
|
-
const
|
|
4441
|
+
const apply6 = (file, prefix) => {
|
|
4424
4442
|
if (conflicted.has(file)) return;
|
|
4425
4443
|
const existing = filePrefix.get(file);
|
|
4426
4444
|
if (existing === void 0) filePrefix.set(file, prefix);
|
|
@@ -4437,7 +4455,7 @@ async function expressMountPrefixes(files, serviceDir, tsPaths) {
|
|
|
4437
4455
|
const info = fileInfo.get(file);
|
|
4438
4456
|
const rv = info?.routerVars.get(name);
|
|
4439
4457
|
if (!info || !rv) return;
|
|
4440
|
-
if (rv.declares && info.appVars.size === 0)
|
|
4458
|
+
if (rv.declares && info.appVars.size === 0) apply6(file, accPrefix);
|
|
4441
4459
|
for (const m of rv.mounts) {
|
|
4442
4460
|
if (!m.target) continue;
|
|
4443
4461
|
const t = resolveTarget(m.target, file);
|
|
@@ -4885,10 +4903,15 @@ function resolveDistToSrc(absFilepath, line) {
|
|
|
4885
4903
|
}
|
|
4886
4904
|
if (!entry2) return null;
|
|
4887
4905
|
try {
|
|
4888
|
-
const
|
|
4889
|
-
|
|
4890
|
-
|
|
4891
|
-
|
|
4906
|
+
const queryLine = line !== void 0 && Number.isFinite(line) ? line : 1;
|
|
4907
|
+
let pos = entry2.consumer.originalPositionFor({ line: queryLine, column: 0 });
|
|
4908
|
+
if (!pos || !pos.source) {
|
|
4909
|
+
pos = entry2.consumer.originalPositionFor({
|
|
4910
|
+
line: queryLine,
|
|
4911
|
+
column: 0,
|
|
4912
|
+
bias: sourceMapJs.SourceMapConsumer.LEAST_UPPER_BOUND
|
|
4913
|
+
});
|
|
4914
|
+
}
|
|
4892
4915
|
if (!pos || !pos.source) return null;
|
|
4893
4916
|
const root = entry2.consumer.sourceRoot ?? "";
|
|
4894
4917
|
const resolved = import_node_path9.default.resolve(entry2.dir, root, pos.source);
|
|
@@ -4897,6 +4920,9 @@ function resolveDistToSrc(absFilepath, line) {
|
|
|
4897
4920
|
return null;
|
|
4898
4921
|
}
|
|
4899
4922
|
}
|
|
4923
|
+
function hasAdjacentSourceMap(absFilepath) {
|
|
4924
|
+
return sourceMapCache.get(absFilepath) != null;
|
|
4925
|
+
}
|
|
4900
4926
|
function callSiteFromSpan(span, serviceNode, scanPath) {
|
|
4901
4927
|
const filepath = codeFilepathOf(span.attributes);
|
|
4902
4928
|
if (filepath === void 0) return null;
|
|
@@ -4912,7 +4938,7 @@ function callSiteFromSpan(span, serviceNode, scanPath) {
|
|
|
4912
4938
|
}
|
|
4913
4939
|
const relPath = relPathForRuntimeFile(effectivePath, serviceNode, scanPath);
|
|
4914
4940
|
if (!relPath) return null;
|
|
4915
|
-
if (!resolved && abs.endsWith(".js") && relPath.startsWith("dist/") && serviceNode?.name) {
|
|
4941
|
+
if (!resolved && abs.endsWith(".js") && relPath.startsWith("dist/") && !hasAdjacentSourceMap(abs) && serviceNode?.name) {
|
|
4916
4942
|
warnNoSourceMaps(serviceNode.name);
|
|
4917
4943
|
}
|
|
4918
4944
|
const fn = codeFunctionOf(span.attributes);
|
|
@@ -5188,7 +5214,7 @@ function resolveServiceId(graph, host, env) {
|
|
|
5188
5214
|
function frontierIdFor(host) {
|
|
5189
5215
|
return (0, import_types8.frontierId)(host);
|
|
5190
5216
|
}
|
|
5191
|
-
function
|
|
5217
|
+
function resolveFusedServiceId(graph, serviceName, env) {
|
|
5192
5218
|
const id = (0, import_types8.serviceId)(serviceName, env);
|
|
5193
5219
|
if (graph.hasNode(id)) return id;
|
|
5194
5220
|
const wanted = serviceName.toLowerCase();
|
|
@@ -5198,17 +5224,21 @@ function ensureServiceNode(graph, serviceName, env) {
|
|
|
5198
5224
|
if (svc.discoveredVia === "otel") return false;
|
|
5199
5225
|
return typeof svc.name === "string" && svc.name.toLowerCase() === wanted;
|
|
5200
5226
|
});
|
|
5201
|
-
|
|
5227
|
+
return extractedId ?? id;
|
|
5228
|
+
}
|
|
5229
|
+
function ensureServiceNode(graph, serviceName, env) {
|
|
5230
|
+
const resolved = resolveFusedServiceId(graph, serviceName, env);
|
|
5231
|
+
if (graph.hasNode(resolved)) return resolved;
|
|
5202
5232
|
const node = {
|
|
5203
|
-
id,
|
|
5233
|
+
id: resolved,
|
|
5204
5234
|
type: import_types8.NodeType.ServiceNode,
|
|
5205
5235
|
name: serviceName,
|
|
5206
5236
|
language: "unknown",
|
|
5207
5237
|
discoveredVia: "otel",
|
|
5208
5238
|
...env !== "unknown" ? { env } : {}
|
|
5209
5239
|
};
|
|
5210
|
-
graph.addNode(
|
|
5211
|
-
return
|
|
5240
|
+
graph.addNode(resolved, node);
|
|
5241
|
+
return resolved;
|
|
5212
5242
|
}
|
|
5213
5243
|
function ensureInfraNode(graph, kind, name, provider) {
|
|
5214
5244
|
const id = (0, import_types8.infraId)(kind, name);
|
|
@@ -5401,8 +5431,23 @@ async function appendErrorEvent(ctx, ev) {
|
|
|
5401
5431
|
await import_node_fs8.promises.mkdir(import_node_path9.default.dirname(ctx.errorsPath), { recursive: true });
|
|
5402
5432
|
await import_node_fs8.promises.appendFile(ctx.errorsPath, JSON.stringify(ev) + "\n", "utf8");
|
|
5403
5433
|
}
|
|
5434
|
+
async function appendConnectorIncident(errorsPath, input) {
|
|
5435
|
+
const ev = {
|
|
5436
|
+
id: input.id,
|
|
5437
|
+
timestamp: input.timestamp,
|
|
5438
|
+
service: input.service,
|
|
5439
|
+
traceId: input.id,
|
|
5440
|
+
spanId: input.id,
|
|
5441
|
+
errorType: input.errorType,
|
|
5442
|
+
errorMessage: input.errorMessage,
|
|
5443
|
+
...input.attributes && Object.keys(input.attributes).length > 0 ? { attributes: input.attributes } : {},
|
|
5444
|
+
affectedNode: input.affectedNode
|
|
5445
|
+
};
|
|
5446
|
+
await import_node_fs8.promises.mkdir(import_node_path9.default.dirname(errorsPath), { recursive: true });
|
|
5447
|
+
await import_node_fs8.promises.appendFile(errorsPath, JSON.stringify(ev) + "\n", "utf8");
|
|
5448
|
+
}
|
|
5404
5449
|
function incidentAffectedNode(span, graph, scanPath) {
|
|
5405
|
-
const sid = (0, import_types8.serviceId)(span.service, span.env);
|
|
5450
|
+
const sid = graph ? resolveFusedServiceId(graph, span.service, span.env) : (0, import_types8.serviceId)(span.service, span.env);
|
|
5406
5451
|
const serviceNode = graph && graph.hasNode(sid) ? graph.getNodeAttributes(sid) : void 0;
|
|
5407
5452
|
const callSite = callSiteFromSpan(span, serviceNode, scanPath);
|
|
5408
5453
|
if (callSite) {
|
|
@@ -6804,7 +6849,7 @@ function disambiguate(defs) {
|
|
|
6804
6849
|
}
|
|
6805
6850
|
async function addSymbols(graph, services) {
|
|
6806
6851
|
const parsers = /* @__PURE__ */ new Map();
|
|
6807
|
-
const
|
|
6852
|
+
const parserForExt5 = (ext) => {
|
|
6808
6853
|
const grammar = GRAMMAR_BY_EXT[ext];
|
|
6809
6854
|
if (!grammar) return null;
|
|
6810
6855
|
let parser = parsers.get(ext);
|
|
@@ -6820,7 +6865,7 @@ async function addSymbols(graph, services) {
|
|
|
6820
6865
|
for (const service of services) {
|
|
6821
6866
|
const files = await loadSourceFiles(service.dir);
|
|
6822
6867
|
for (const file of files) {
|
|
6823
|
-
const parser =
|
|
6868
|
+
const parser = parserForExt5(import_node_path18.default.extname(file.path));
|
|
6824
6869
|
if (!parser) continue;
|
|
6825
6870
|
const relPath = toPosix(import_node_path18.default.relative(service.dir, file.path));
|
|
6826
6871
|
let defs;
|
|
@@ -6972,7 +7017,7 @@ function stringInner(node) {
|
|
|
6972
7017
|
}
|
|
6973
7018
|
async function addSymbolEdges(graph, services) {
|
|
6974
7019
|
const parsers = /* @__PURE__ */ new Map();
|
|
6975
|
-
const
|
|
7020
|
+
const parserForExt5 = (ext) => {
|
|
6976
7021
|
const grammar = GRAMMAR_BY_EXT[ext];
|
|
6977
7022
|
if (!grammar) return null;
|
|
6978
7023
|
let parser = parsers.get(ext);
|
|
@@ -6989,7 +7034,7 @@ async function addSymbolEdges(graph, services) {
|
|
|
6989
7034
|
const tsPaths = await loadTsPathConfig(service.dir);
|
|
6990
7035
|
const files = await loadSourceFiles(service.dir);
|
|
6991
7036
|
for (const file of files) {
|
|
6992
|
-
const parser =
|
|
7037
|
+
const parser = parserForExt5(import_node_path19.default.extname(file.path));
|
|
6993
7038
|
if (!parser) continue;
|
|
6994
7039
|
const relPath = toPosix(import_node_path19.default.relative(service.dir, file.path));
|
|
6995
7040
|
const fileDir = import_node_path19.default.dirname(file.path);
|
|
@@ -7251,7 +7296,7 @@ function firstReferenceLines(root, wanted) {
|
|
|
7251
7296
|
}
|
|
7252
7297
|
async function addServerActions(graph, services) {
|
|
7253
7298
|
const parsers = /* @__PURE__ */ new Map();
|
|
7254
|
-
const
|
|
7299
|
+
const parserForExt5 = (ext) => {
|
|
7255
7300
|
const grammar = GRAMMAR_BY_EXT[ext];
|
|
7256
7301
|
if (!grammar) return null;
|
|
7257
7302
|
let parser = parsers.get(ext);
|
|
@@ -7274,7 +7319,7 @@ async function addServerActions(graph, services) {
|
|
|
7274
7319
|
const files = await loadSourceFiles(service.dir);
|
|
7275
7320
|
for (const file of files) {
|
|
7276
7321
|
if (isTestPath(file.path)) continue;
|
|
7277
|
-
const parser =
|
|
7322
|
+
const parser = parserForExt5(import_node_path20.default.extname(file.path));
|
|
7278
7323
|
if (!parser) continue;
|
|
7279
7324
|
const relPath = toPosix(import_node_path20.default.relative(service.dir, file.path));
|
|
7280
7325
|
let root;
|
|
@@ -7337,7 +7382,7 @@ async function addServerActions(graph, services) {
|
|
|
7337
7382
|
}
|
|
7338
7383
|
for (const file of files) {
|
|
7339
7384
|
if (isTestPath(file.path)) continue;
|
|
7340
|
-
const parser =
|
|
7385
|
+
const parser = parserForExt5(import_node_path20.default.extname(file.path));
|
|
7341
7386
|
if (!parser) continue;
|
|
7342
7387
|
const relPath = toPosix(import_node_path20.default.relative(service.dir, file.path));
|
|
7343
7388
|
const fileDir = import_node_path20.default.dirname(file.path);
|
|
@@ -8301,6 +8346,7 @@ init_cjs_shims();
|
|
|
8301
8346
|
var import_node_path31 = __toESM(require("path"), 1);
|
|
8302
8347
|
var import_tree_sitter6 = __toESM(require("tree-sitter"), 1);
|
|
8303
8348
|
var import_tree_sitter_javascript4 = __toESM(require("tree-sitter-javascript"), 1);
|
|
8349
|
+
var import_tree_sitter_typescript2 = __toESM(require("tree-sitter-typescript"), 1);
|
|
8304
8350
|
var import_tree_sitter_python3 = __toESM(require("tree-sitter-python"), 1);
|
|
8305
8351
|
var import_types20 = require("@neat.is/types");
|
|
8306
8352
|
var STRING_LITERAL_NODE_TYPES = /* @__PURE__ */ new Set(["string_fragment", "string_content"]);
|
|
@@ -8351,19 +8397,27 @@ function callsFromSource(source, parser, knownHosts) {
|
|
|
8351
8397
|
}
|
|
8352
8398
|
return out;
|
|
8353
8399
|
}
|
|
8354
|
-
|
|
8355
|
-
|
|
8356
|
-
|
|
8357
|
-
|
|
8358
|
-
|
|
8359
|
-
|
|
8360
|
-
|
|
8361
|
-
|
|
8362
|
-
|
|
8400
|
+
var GRAMMAR_BY_EXT2 = {
|
|
8401
|
+
".ts": import_tree_sitter_typescript2.default.typescript,
|
|
8402
|
+
".tsx": import_tree_sitter_typescript2.default.tsx,
|
|
8403
|
+
".js": import_tree_sitter_javascript4.default,
|
|
8404
|
+
".jsx": import_tree_sitter_javascript4.default,
|
|
8405
|
+
".mjs": import_tree_sitter_javascript4.default,
|
|
8406
|
+
".cjs": import_tree_sitter_javascript4.default,
|
|
8407
|
+
".py": import_tree_sitter_python3.default
|
|
8408
|
+
};
|
|
8409
|
+
function parserForExt(ext, cache) {
|
|
8410
|
+
const grammar = GRAMMAR_BY_EXT2[ext] ?? import_tree_sitter_javascript4.default;
|
|
8411
|
+
let parser = cache.get(grammar);
|
|
8412
|
+
if (!parser) {
|
|
8413
|
+
parser = new import_tree_sitter6.default();
|
|
8414
|
+
parser.setLanguage(grammar);
|
|
8415
|
+
cache.set(grammar, parser);
|
|
8416
|
+
}
|
|
8417
|
+
return parser;
|
|
8363
8418
|
}
|
|
8364
8419
|
async function addHttpCallEdges(graph, services) {
|
|
8365
|
-
const
|
|
8366
|
-
const pyParser = makePyParser3();
|
|
8420
|
+
const parserCache = /* @__PURE__ */ new Map();
|
|
8367
8421
|
const { knownHosts, hostToNodeId } = buildServiceHostIndex(services);
|
|
8368
8422
|
let nodesAdded = 0;
|
|
8369
8423
|
let edgesAdded = 0;
|
|
@@ -8372,7 +8426,7 @@ async function addHttpCallEdges(graph, services) {
|
|
|
8372
8426
|
const seen = /* @__PURE__ */ new Set();
|
|
8373
8427
|
for (const file of files) {
|
|
8374
8428
|
if (isTestPath(file.path)) continue;
|
|
8375
|
-
const parser = import_node_path31.default.extname(file.path)
|
|
8429
|
+
const parser = parserForExt(import_node_path31.default.extname(file.path), parserCache);
|
|
8376
8430
|
let sites;
|
|
8377
8431
|
try {
|
|
8378
8432
|
sites = callsFromSource(file.content, parser, knownHosts);
|
|
@@ -8445,7 +8499,7 @@ function parseSource5(parser, source) {
|
|
|
8445
8499
|
(index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK5)
|
|
8446
8500
|
);
|
|
8447
8501
|
}
|
|
8448
|
-
function
|
|
8502
|
+
function makeJsParser3() {
|
|
8449
8503
|
const p = new import_tree_sitter7.default();
|
|
8450
8504
|
p.setLanguage(import_tree_sitter_javascript5.default);
|
|
8451
8505
|
return p;
|
|
@@ -8623,7 +8677,7 @@ function findRoute(entries, method, normalizedPath) {
|
|
|
8623
8677
|
);
|
|
8624
8678
|
}
|
|
8625
8679
|
async function addRouteCallEdges(graph, services) {
|
|
8626
|
-
const jsParser =
|
|
8680
|
+
const jsParser = makeJsParser3();
|
|
8627
8681
|
const { knownHosts, hostToNodeId } = buildServiceHostIndex(services);
|
|
8628
8682
|
const routeIndex = buildRouteIndex(graph);
|
|
8629
8683
|
if (routeIndex.size === 0) return { nodesAdded: 0, edgesAdded: 0 };
|
|
@@ -9015,7 +9069,7 @@ var import_tree_sitter_javascript6 = __toESM(require("tree-sitter-javascript"),
|
|
|
9015
9069
|
var import_types27 = require("@neat.is/types");
|
|
9016
9070
|
var FIRESTORE_CLIENT_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])firebase\/firestore['"`]/;
|
|
9017
9071
|
var FIRESTORE_ADMIN_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])firebase-admin(?:\/firestore)?['"`]/;
|
|
9018
|
-
function
|
|
9072
|
+
function parserForExt2(ext) {
|
|
9019
9073
|
const p = new import_tree_sitter8.default();
|
|
9020
9074
|
p.setLanguage(GRAMMAR_BY_EXT[ext] ?? import_tree_sitter_javascript6.default);
|
|
9021
9075
|
return p;
|
|
@@ -9180,7 +9234,7 @@ function firestoreEndpointsFromFile(file, serviceDir) {
|
|
|
9180
9234
|
const hasAdmin = FIRESTORE_ADMIN_IMPORT_RE.test(file.content);
|
|
9181
9235
|
if (!hasClient && !hasAdmin) return [];
|
|
9182
9236
|
const fileSdk = hasClient && !hasAdmin ? "client" : hasAdmin && !hasClient ? "admin" : null;
|
|
9183
|
-
const tree = parseSource3(
|
|
9237
|
+
const tree = parseSource3(parserForExt2(import_node_path38.default.extname(file.path)), file.content);
|
|
9184
9238
|
const clientVars = firestoreClientVars(tree.rootNode);
|
|
9185
9239
|
const collLine = /* @__PURE__ */ new Map();
|
|
9186
9240
|
const writes = /* @__PURE__ */ new Map();
|
|
@@ -9597,7 +9651,7 @@ var import_tree_sitter_python4 = __toESM(require("tree-sitter-python"), 1);
|
|
|
9597
9651
|
var import_types29 = require("@neat.is/types");
|
|
9598
9652
|
var SQLALCHEMY_IMPORT_RE = /(?:from|import)\s+(?:flask_sqlalchemy|sqlalchemy)\b/;
|
|
9599
9653
|
var PARSE_CHUNK6 = 16384;
|
|
9600
|
-
function
|
|
9654
|
+
function makePyParser3() {
|
|
9601
9655
|
const p = new import_tree_sitter9.default();
|
|
9602
9656
|
p.setLanguage(import_tree_sitter_python4.default);
|
|
9603
9657
|
return p;
|
|
@@ -9704,7 +9758,7 @@ function foreignKeyParentTable(call) {
|
|
|
9704
9758
|
}
|
|
9705
9759
|
function sqlalchemyForeignKeys(file, serviceDir) {
|
|
9706
9760
|
if (!SQLALCHEMY_IMPORT_RE.test(file.content)) return [];
|
|
9707
|
-
const tree = parseSource6(
|
|
9761
|
+
const tree = parseSource6(makePyParser3(), file.content);
|
|
9708
9762
|
const out = [];
|
|
9709
9763
|
const seen = /* @__PURE__ */ new Set();
|
|
9710
9764
|
walk3(tree.rootNode, (node) => {
|
|
@@ -9741,7 +9795,7 @@ function sqlalchemyForeignKeys(file, serviceDir) {
|
|
|
9741
9795
|
}
|
|
9742
9796
|
function sqlalchemyEndpointsFromFile(file, serviceDir) {
|
|
9743
9797
|
if (!SQLALCHEMY_IMPORT_RE.test(file.content)) return [];
|
|
9744
|
-
const tree = parseSource6(
|
|
9798
|
+
const tree = parseSource6(makePyParser3(), file.content);
|
|
9745
9799
|
const out = [];
|
|
9746
9800
|
const seen = /* @__PURE__ */ new Set();
|
|
9747
9801
|
const push = (name, line, columns) => {
|
|
@@ -9793,7 +9847,7 @@ function sqlalchemyEndpointsFromFile(file, serviceDir) {
|
|
|
9793
9847
|
function buildSqlalchemyModelRegistry(files) {
|
|
9794
9848
|
const table = /* @__PURE__ */ new Map();
|
|
9795
9849
|
const ambiguous = /* @__PURE__ */ new Set();
|
|
9796
|
-
const parser =
|
|
9850
|
+
const parser = makePyParser3();
|
|
9797
9851
|
for (const file of files) {
|
|
9798
9852
|
if (!SQLALCHEMY_IMPORT_RE.test(file.content)) continue;
|
|
9799
9853
|
const tree = parseSource6(parser, file.content);
|
|
@@ -9842,7 +9896,7 @@ function importsModelName(content, name) {
|
|
|
9842
9896
|
function pythonOrmCrossFileEndpoints(files, serviceDir) {
|
|
9843
9897
|
const registry = buildSqlalchemyModelRegistry(files);
|
|
9844
9898
|
if (registry.size === 0) return [];
|
|
9845
|
-
const parser =
|
|
9899
|
+
const parser = makePyParser3();
|
|
9846
9900
|
const out = [];
|
|
9847
9901
|
const seen = /* @__PURE__ */ new Set();
|
|
9848
9902
|
for (const file of files) {
|
|
@@ -9880,7 +9934,7 @@ var import_tree_sitter_python5 = __toESM(require("tree-sitter-python"), 1);
|
|
|
9880
9934
|
var import_types30 = require("@neat.is/types");
|
|
9881
9935
|
var DJANGO_IMPORT_RE = /(?:from|import)\s+django\b/;
|
|
9882
9936
|
var PARSE_CHUNK7 = 16384;
|
|
9883
|
-
function
|
|
9937
|
+
function makePyParser4() {
|
|
9884
9938
|
const p = new import_tree_sitter10.default();
|
|
9885
9939
|
p.setLanguage(import_tree_sitter_python5.default);
|
|
9886
9940
|
return p;
|
|
@@ -9941,7 +9995,7 @@ function readMeta(body) {
|
|
|
9941
9995
|
}
|
|
9942
9996
|
function djangoOrmEndpointsFromFile(file, serviceDir) {
|
|
9943
9997
|
if (!DJANGO_IMPORT_RE.test(file.content)) return [];
|
|
9944
|
-
const tree = parseSource7(
|
|
9998
|
+
const tree = parseSource7(makePyParser4(), file.content);
|
|
9945
9999
|
const out = [];
|
|
9946
10000
|
const seen = /* @__PURE__ */ new Set();
|
|
9947
10001
|
const defaultAppLabel = import_node_path41.default.basename(import_node_path41.default.dirname(file.path));
|
|
@@ -9976,7 +10030,7 @@ var import_tree_sitter_javascript7 = __toESM(require("tree-sitter-javascript"),
|
|
|
9976
10030
|
var import_types31 = require("@neat.is/types");
|
|
9977
10031
|
var DRIZZLE_IMPORT_RE = /drizzle-orm/;
|
|
9978
10032
|
var TABLE_BUILDERS = /* @__PURE__ */ new Set(["pgTable", "mysqlTable", "sqliteTable"]);
|
|
9979
|
-
function
|
|
10033
|
+
function parserForExt3(ext) {
|
|
9980
10034
|
const p = new import_tree_sitter11.default();
|
|
9981
10035
|
p.setLanguage(GRAMMAR_BY_EXT[ext] ?? import_tree_sitter_javascript7.default);
|
|
9982
10036
|
return p;
|
|
@@ -10048,7 +10102,7 @@ function columnsFromObject(obj) {
|
|
|
10048
10102
|
}
|
|
10049
10103
|
function drizzleEndpointsFromFile(file, serviceDir) {
|
|
10050
10104
|
if (!DRIZZLE_IMPORT_RE.test(file.content)) return [];
|
|
10051
|
-
const tree = parseSource3(
|
|
10105
|
+
const tree = parseSource3(parserForExt3(import_node_path42.default.extname(file.path)), file.content);
|
|
10052
10106
|
const out = [];
|
|
10053
10107
|
const seen = /* @__PURE__ */ new Set();
|
|
10054
10108
|
const walk9 = (node) => {
|
|
@@ -10137,7 +10191,7 @@ function referencesTargetVar(call) {
|
|
|
10137
10191
|
}
|
|
10138
10192
|
function drizzleForeignKeys(file, serviceDir) {
|
|
10139
10193
|
if (!DRIZZLE_IMPORT_RE.test(file.content)) return [];
|
|
10140
|
-
const tree = parseSource3(
|
|
10194
|
+
const tree = parseSource3(parserForExt3(import_node_path42.default.extname(file.path)), file.content);
|
|
10141
10195
|
const { tables, varToTable } = collectDrizzleTables(tree.rootNode);
|
|
10142
10196
|
const out = [];
|
|
10143
10197
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -11205,8 +11259,41 @@ var import_tree_sitter14 = __toESM(require("tree-sitter"), 1);
|
|
|
11205
11259
|
var import_tree_sitter_go3 = __toESM(require("tree-sitter-go"), 1);
|
|
11206
11260
|
var import_types35 = require("@neat.is/types");
|
|
11207
11261
|
init_otel();
|
|
11208
|
-
var SQL_METHODS = /* @__PURE__ */ new Set(["Exec", "ExecContext", "Query", "QueryContext", "QueryRow", "QueryRowContext"]);
|
|
11209
11262
|
var PARSE_CHUNK10 = 16384;
|
|
11263
|
+
var DATABASE_SQL_METHODS = /* @__PURE__ */ new Set([
|
|
11264
|
+
"Query",
|
|
11265
|
+
"QueryContext",
|
|
11266
|
+
"QueryRow",
|
|
11267
|
+
"QueryRowContext",
|
|
11268
|
+
"Exec",
|
|
11269
|
+
"ExecContext",
|
|
11270
|
+
"Prepare",
|
|
11271
|
+
"PrepareContext"
|
|
11272
|
+
]);
|
|
11273
|
+
var SQLX_METHODS = /* @__PURE__ */ new Set([
|
|
11274
|
+
"Get",
|
|
11275
|
+
"Select",
|
|
11276
|
+
"Queryx",
|
|
11277
|
+
"QueryRowx",
|
|
11278
|
+
"NamedExec",
|
|
11279
|
+
"NamedQuery",
|
|
11280
|
+
"MustExec",
|
|
11281
|
+
"Preparex",
|
|
11282
|
+
"GetContext",
|
|
11283
|
+
"SelectContext"
|
|
11284
|
+
]);
|
|
11285
|
+
var DATABASE_SQL_IMPORT = "database/sql";
|
|
11286
|
+
var SQLX_IMPORT = "github.com/jmoiron/sqlx";
|
|
11287
|
+
function makeGoParser3() {
|
|
11288
|
+
const p = new import_tree_sitter14.default();
|
|
11289
|
+
p.setLanguage(import_tree_sitter_go3.default);
|
|
11290
|
+
return p;
|
|
11291
|
+
}
|
|
11292
|
+
function parseSource10(parser, source) {
|
|
11293
|
+
return parser.parse(
|
|
11294
|
+
(index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK10)
|
|
11295
|
+
);
|
|
11296
|
+
}
|
|
11210
11297
|
function walk7(node, visit) {
|
|
11211
11298
|
visit(node);
|
|
11212
11299
|
for (let i = 0; i < node.namedChildCount; i++) {
|
|
@@ -11214,25 +11301,54 @@ function walk7(node, visit) {
|
|
|
11214
11301
|
if (child) walk7(child, visit);
|
|
11215
11302
|
}
|
|
11216
11303
|
}
|
|
11304
|
+
function goStringLiteralValue(node) {
|
|
11305
|
+
if (!node) return null;
|
|
11306
|
+
if (node.type === "interpreted_string_literal" || node.type === "raw_string_literal") {
|
|
11307
|
+
const t = node.text;
|
|
11308
|
+
return t.length >= 2 ? t.slice(1, -1) : "";
|
|
11309
|
+
}
|
|
11310
|
+
return null;
|
|
11311
|
+
}
|
|
11312
|
+
function goImportsAny(root, names) {
|
|
11313
|
+
let found = false;
|
|
11314
|
+
walk7(root, (node) => {
|
|
11315
|
+
if (found || node.type !== "import_spec") return;
|
|
11316
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
11317
|
+
const value = goStringLiteralValue(node.namedChild(i));
|
|
11318
|
+
if (value !== null && names.has(value)) found = true;
|
|
11319
|
+
}
|
|
11320
|
+
});
|
|
11321
|
+
return found;
|
|
11322
|
+
}
|
|
11323
|
+
function firstStringLiteralArg(argsNode) {
|
|
11324
|
+
if (!argsNode) return null;
|
|
11325
|
+
for (let i = 0; i < argsNode.namedChildCount; i++) {
|
|
11326
|
+
const value = goStringLiteralValue(argsNode.namedChild(i));
|
|
11327
|
+
if (value !== null) return value;
|
|
11328
|
+
}
|
|
11329
|
+
return null;
|
|
11330
|
+
}
|
|
11217
11331
|
function goSqlEndpointsFromFile(file, serviceDir) {
|
|
11218
11332
|
if (import_node_path48.default.extname(file.path) !== ".go") return [];
|
|
11219
|
-
|
|
11220
|
-
|
|
11221
|
-
const
|
|
11222
|
-
|
|
11223
|
-
);
|
|
11333
|
+
if (!file.content.includes(DATABASE_SQL_IMPORT) && !file.content.includes(SQLX_IMPORT)) return [];
|
|
11334
|
+
const tree = parseSource10(makeGoParser3(), file.content);
|
|
11335
|
+
const importsDatabaseSql = goImportsAny(tree.rootNode, /* @__PURE__ */ new Set([DATABASE_SQL_IMPORT]));
|
|
11336
|
+
const importsSqlx = goImportsAny(tree.rootNode, /* @__PURE__ */ new Set([SQLX_IMPORT]));
|
|
11337
|
+
if (!importsDatabaseSql && !importsSqlx) return [];
|
|
11224
11338
|
const out = [];
|
|
11225
11339
|
walk7(tree.rootNode, (node) => {
|
|
11226
11340
|
if (node.type !== "call_expression") return;
|
|
11227
11341
|
const fn = node.childForFieldName("function");
|
|
11228
11342
|
if (fn?.type !== "selector_expression") return;
|
|
11229
11343
|
const method = fn.childForFieldName("field")?.text;
|
|
11230
|
-
if (!method
|
|
11231
|
-
const
|
|
11232
|
-
if (!
|
|
11233
|
-
const sql =
|
|
11344
|
+
if (!method) return;
|
|
11345
|
+
const recognized = DATABASE_SQL_METHODS.has(method) || importsSqlx && SQLX_METHODS.has(method);
|
|
11346
|
+
if (!recognized) return;
|
|
11347
|
+
const sql = firstStringLiteralArg(node.childForFieldName("arguments"));
|
|
11348
|
+
if (sql === null) return;
|
|
11234
11349
|
const table = tableFromSqlStatement(sql);
|
|
11235
11350
|
if (!table) return;
|
|
11351
|
+
const columns = columnsFromSqlStatement(sql);
|
|
11236
11352
|
const line = node.startPosition.row + 1;
|
|
11237
11353
|
out.push({
|
|
11238
11354
|
infraId: (0, import_types35.infraId)("sql-table", table),
|
|
@@ -11240,7 +11356,12 @@ function goSqlEndpointsFromFile(file, serviceDir) {
|
|
|
11240
11356
|
kind: "sql-table",
|
|
11241
11357
|
edgeType: "CALLS",
|
|
11242
11358
|
confidenceKind: "verified-call-site",
|
|
11243
|
-
|
|
11359
|
+
...columns.length > 0 ? { columns } : {},
|
|
11360
|
+
evidence: {
|
|
11361
|
+
file: toPosix(import_node_path48.default.relative(serviceDir, file.path)),
|
|
11362
|
+
line,
|
|
11363
|
+
snippet: snippet(file.content, line)
|
|
11364
|
+
}
|
|
11244
11365
|
});
|
|
11245
11366
|
});
|
|
11246
11367
|
return out;
|
|
@@ -11254,12 +11375,12 @@ var import_tree_sitter_go4 = __toESM(require("tree-sitter-go"), 1);
|
|
|
11254
11375
|
var import_types36 = require("@neat.is/types");
|
|
11255
11376
|
var GORM_IMPORT_RE = /gorm\.io\/gorm/;
|
|
11256
11377
|
var PARSE_CHUNK11 = 16384;
|
|
11257
|
-
function
|
|
11378
|
+
function makeGoParser4() {
|
|
11258
11379
|
const p = new import_tree_sitter15.default();
|
|
11259
11380
|
p.setLanguage(import_tree_sitter_go4.default);
|
|
11260
11381
|
return p;
|
|
11261
11382
|
}
|
|
11262
|
-
function
|
|
11383
|
+
function parseSource11(parser, source) {
|
|
11263
11384
|
return parser.parse(
|
|
11264
11385
|
(index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK11)
|
|
11265
11386
|
);
|
|
@@ -11683,7 +11804,7 @@ function collectColumns(struct, structs, seen, prefix, out, emitted) {
|
|
|
11683
11804
|
function gormEndpointsFromFile(file, serviceDir) {
|
|
11684
11805
|
if (import_node_path49.default.extname(file.path) !== ".go") return [];
|
|
11685
11806
|
if (!GORM_IMPORT_RE.test(file.content)) return [];
|
|
11686
|
-
const tree =
|
|
11807
|
+
const tree = parseSource11(makeGoParser4(), file.content);
|
|
11687
11808
|
const { structs, models, tableFor } = analyze(tree);
|
|
11688
11809
|
const out = [];
|
|
11689
11810
|
const seenTables = /* @__PURE__ */ new Set();
|
|
@@ -11714,7 +11835,7 @@ function gormEndpointsFromFile(file, serviceDir) {
|
|
|
11714
11835
|
function gormForeignKeys(file, serviceDir) {
|
|
11715
11836
|
if (import_node_path49.default.extname(file.path) !== ".go") return [];
|
|
11716
11837
|
if (!GORM_IMPORT_RE.test(file.content)) return [];
|
|
11717
|
-
const tree =
|
|
11838
|
+
const tree = parseSource11(makeGoParser4(), file.content);
|
|
11718
11839
|
const { structs, models, tableFor } = analyze(tree);
|
|
11719
11840
|
const out = [];
|
|
11720
11841
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -12844,7 +12965,7 @@ var import_tree_sitter_javascript8 = __toESM(require("tree-sitter-javascript"),
|
|
|
12844
12965
|
var import_types47 = require("@neat.is/types");
|
|
12845
12966
|
var ZOD_IMPORT_RE = /\bzod\b/;
|
|
12846
12967
|
var ZOD_OBJECTS = /* @__PURE__ */ new Set(["z", "zod"]);
|
|
12847
|
-
function
|
|
12968
|
+
function parserForExt4(ext) {
|
|
12848
12969
|
const p = new import_tree_sitter16.default();
|
|
12849
12970
|
p.setLanguage(GRAMMAR_BY_EXT[ext] ?? import_tree_sitter_javascript8.default);
|
|
12850
12971
|
return p;
|
|
@@ -12933,7 +13054,7 @@ function topLevelSchemas(root) {
|
|
|
12933
13054
|
}
|
|
12934
13055
|
function zodShapesFromFile(file, serviceDir) {
|
|
12935
13056
|
if (!ZOD_IMPORT_RE.test(file.content)) return [];
|
|
12936
|
-
const tree = parseSource3(
|
|
13057
|
+
const tree = parseSource3(parserForExt4(import_node_path58.default.extname(file.path)), file.content);
|
|
12937
13058
|
const out = [];
|
|
12938
13059
|
const seen = /* @__PURE__ */ new Set();
|
|
12939
13060
|
for (const { name, call } of topLevelSchemas(tree.rootNode)) {
|
|
@@ -13941,7 +14062,7 @@ var import_chokidar = __toESM(require("chokidar"), 1);
|
|
|
13941
14062
|
init_cjs_shims();
|
|
13942
14063
|
var import_fastify2 = __toESM(require("fastify"), 1);
|
|
13943
14064
|
var import_cors = __toESM(require("@fastify/cors"), 1);
|
|
13944
|
-
var
|
|
14065
|
+
var import_types86 = require("@neat.is/types");
|
|
13945
14066
|
|
|
13946
14067
|
// src/extend/index.ts
|
|
13947
14068
|
init_cjs_shims();
|
|
@@ -15270,6 +15391,23 @@ async function runConnectorPoll(connector, ctx, graph, resolveTarget) {
|
|
|
15270
15391
|
unresolved++;
|
|
15271
15392
|
continue;
|
|
15272
15393
|
}
|
|
15394
|
+
if (signal.incident) {
|
|
15395
|
+
ensureServiceNode(graph, resolved.serviceName, NO_ENV);
|
|
15396
|
+
if (!ctx.errorsPath || !graph.hasNode(resolved.targetNodeId)) {
|
|
15397
|
+
unresolved++;
|
|
15398
|
+
continue;
|
|
15399
|
+
}
|
|
15400
|
+
await appendConnectorIncident(ctx.errorsPath, {
|
|
15401
|
+
id: signal.incident.id,
|
|
15402
|
+
timestamp: signal.incident.timestamp,
|
|
15403
|
+
service: signal.incident.service,
|
|
15404
|
+
errorType: signal.incident.errorType,
|
|
15405
|
+
errorMessage: signal.incident.errorMessage,
|
|
15406
|
+
...signal.incident.attributes ? { attributes: signal.incident.attributes } : {},
|
|
15407
|
+
affectedNode: resolved.targetNodeId
|
|
15408
|
+
});
|
|
15409
|
+
continue;
|
|
15410
|
+
}
|
|
15273
15411
|
if (resolved.ensureInfraNode) {
|
|
15274
15412
|
const { kind, name, provider } = resolved.ensureInfraNode;
|
|
15275
15413
|
ensureInfraNode(graph, kind, name, provider);
|
|
@@ -15735,10 +15873,10 @@ var SUPABASE_RPC_TARGET_KIND = "supabase-rpc";
|
|
|
15735
15873
|
// src/connectors/supabase/map.ts
|
|
15736
15874
|
var REST_RPC_PATH_RE = /^\/rest\/v1\/rpc\/([^/?]+)/;
|
|
15737
15875
|
var REST_TABLE_PATH_RE = /^\/rest\/v1\/([^/?]+)/;
|
|
15738
|
-
function targetFromRestPath(
|
|
15739
|
-
const rpcMatch = REST_RPC_PATH_RE.exec(
|
|
15876
|
+
function targetFromRestPath(path84) {
|
|
15877
|
+
const rpcMatch = REST_RPC_PATH_RE.exec(path84);
|
|
15740
15878
|
if (rpcMatch) return { targetKind: SUPABASE_RPC_TARGET_KIND, name: rpcMatch[1] };
|
|
15741
|
-
const tableMatch = REST_TABLE_PATH_RE.exec(
|
|
15879
|
+
const tableMatch = REST_TABLE_PATH_RE.exec(path84);
|
|
15742
15880
|
if (tableMatch) return { targetKind: SUPABASE_TABLE_TARGET_KIND, name: tableMatch[1] };
|
|
15743
15881
|
return null;
|
|
15744
15882
|
}
|
|
@@ -16342,9 +16480,9 @@ function parseFirebaseTargetName(targetName) {
|
|
|
16342
16480
|
const secondSep = rest.indexOf(FIELD_SEP);
|
|
16343
16481
|
if (secondSep === -1) return null;
|
|
16344
16482
|
const method = rest.slice(0, secondSep);
|
|
16345
|
-
const
|
|
16346
|
-
if (!resourceName || !method || !
|
|
16347
|
-
return { resourceName, method, path:
|
|
16483
|
+
const path84 = rest.slice(secondSep + 1);
|
|
16484
|
+
if (!resourceName || !method || !path84) return null;
|
|
16485
|
+
return { resourceName, method, path: path84 };
|
|
16348
16486
|
}
|
|
16349
16487
|
function resourceNameFor(type, labels) {
|
|
16350
16488
|
if (!labels) return null;
|
|
@@ -16382,14 +16520,14 @@ function mapLogEntryToSignal(entry2) {
|
|
|
16382
16520
|
if (!req) return null;
|
|
16383
16521
|
if (typeof req.requestMethod !== "string" || req.requestMethod.length === 0) return null;
|
|
16384
16522
|
const method = req.requestMethod.toUpperCase();
|
|
16385
|
-
const
|
|
16386
|
-
if (
|
|
16523
|
+
const path84 = pathFromRequestUrl(req.requestUrl);
|
|
16524
|
+
if (path84 === null) return null;
|
|
16387
16525
|
const timestamp = entry2.timestamp;
|
|
16388
16526
|
if (typeof timestamp !== "string" || timestamp.length === 0) return null;
|
|
16389
16527
|
const isError = typeof req.status === "number" && req.status >= ERROR_STATUS_THRESHOLD2;
|
|
16390
16528
|
return {
|
|
16391
16529
|
targetKind: resourceType,
|
|
16392
|
-
targetName: packFirebaseTargetName({ resourceName, method, path:
|
|
16530
|
+
targetName: packFirebaseTargetName({ resourceName, method, path: path84 }),
|
|
16393
16531
|
callCount: 1,
|
|
16394
16532
|
errorCount: isError ? 1 : 0,
|
|
16395
16533
|
lastObservedIso: timestamp
|
|
@@ -16596,7 +16734,7 @@ function mapEventToSignal(event) {
|
|
|
16596
16734
|
if (Number.isNaN(observedAt.getTime())) return null;
|
|
16597
16735
|
const statusCode = metadata?.statusCode;
|
|
16598
16736
|
const isError = typeof statusCode === "number" && statusCode >= ERROR_STATUS_THRESHOLD3;
|
|
16599
|
-
const
|
|
16737
|
+
const path84 = metadata?.trigger ? parsePathFromTrigger(metadata.trigger) : void 0;
|
|
16600
16738
|
return {
|
|
16601
16739
|
targetKind: CLOUDFLARE_TARGET_KIND,
|
|
16602
16740
|
targetName: scriptName,
|
|
@@ -16604,7 +16742,7 @@ function mapEventToSignal(event) {
|
|
|
16604
16742
|
errorCount: isError ? 1 : 0,
|
|
16605
16743
|
lastObservedIso: observedAt.toISOString(),
|
|
16606
16744
|
method,
|
|
16607
|
-
...
|
|
16745
|
+
...path84 ? { path: path84 } : {},
|
|
16608
16746
|
...typeof statusCode === "number" ? { statusCode } : {},
|
|
16609
16747
|
...typeof metadata?.duration === "number" ? { duration: metadata.duration } : {}
|
|
16610
16748
|
};
|
|
@@ -16650,8 +16788,8 @@ function findTaggedWorkerFileNode(graph, workerName) {
|
|
|
16650
16788
|
});
|
|
16651
16789
|
return found;
|
|
16652
16790
|
}
|
|
16653
|
-
function findMatchingRouteNode(graph, serviceName, method,
|
|
16654
|
-
const normalizedPath = normalizePathTemplate(
|
|
16791
|
+
function findMatchingRouteNode(graph, serviceName, method, path84) {
|
|
16792
|
+
const normalizedPath = normalizePathTemplate(path84);
|
|
16655
16793
|
let found = null;
|
|
16656
16794
|
graph.forEachNode((id, attrs) => {
|
|
16657
16795
|
if (found) return;
|
|
@@ -16668,10 +16806,10 @@ function createCloudflareResolveTarget(config, graph) {
|
|
|
16668
16806
|
return (signal) => {
|
|
16669
16807
|
if (signal.targetKind !== CLOUDFLARE_TARGET_KIND) return null;
|
|
16670
16808
|
const scriptName = signal.targetName;
|
|
16671
|
-
const { method, path:
|
|
16809
|
+
const { method, path: path84 } = signal;
|
|
16672
16810
|
const resolveRouteGrain = (serviceName, wholeFileId) => {
|
|
16673
|
-
if (!method || !
|
|
16674
|
-
return findMatchingRouteNode(graph, serviceName, method,
|
|
16811
|
+
if (!method || !path84) return wholeFileId;
|
|
16812
|
+
return findMatchingRouteNode(graph, serviceName, method, path84) ?? wholeFileId;
|
|
16675
16813
|
};
|
|
16676
16814
|
const mapping = config.workers?.[scriptName];
|
|
16677
16815
|
if (mapping) {
|
|
@@ -17023,9 +17161,9 @@ function parseCloudRunTargetName(targetName) {
|
|
|
17023
17161
|
const secondSep = rest.indexOf(FIELD_SEP2);
|
|
17024
17162
|
if (secondSep === -1) return null;
|
|
17025
17163
|
const method = rest.slice(0, secondSep);
|
|
17026
|
-
const
|
|
17027
|
-
if (!serviceName || !method || !
|
|
17028
|
-
return { serviceName, method, path:
|
|
17164
|
+
const path84 = rest.slice(secondSep + 1);
|
|
17165
|
+
if (!serviceName || !method || !path84) return null;
|
|
17166
|
+
return { serviceName, method, path: path84 };
|
|
17029
17167
|
}
|
|
17030
17168
|
|
|
17031
17169
|
// src/connectors/cloud-run/map.ts
|
|
@@ -17054,14 +17192,14 @@ function mapLogEntryToSignal2(entry2) {
|
|
|
17054
17192
|
if (!req) return null;
|
|
17055
17193
|
if (typeof req.requestMethod !== "string" || req.requestMethod.length === 0) return null;
|
|
17056
17194
|
const method = req.requestMethod.toUpperCase();
|
|
17057
|
-
const
|
|
17058
|
-
if (
|
|
17195
|
+
const path84 = pathFromRequestUrl2(req.requestUrl);
|
|
17196
|
+
if (path84 === null) return null;
|
|
17059
17197
|
const timestamp = entry2.timestamp;
|
|
17060
17198
|
if (typeof timestamp !== "string" || timestamp.length === 0) return null;
|
|
17061
17199
|
const isError = typeof req.status === "number" && req.status >= ERROR_STATUS_THRESHOLD4;
|
|
17062
17200
|
return {
|
|
17063
17201
|
targetKind: CLOUD_RUN_TARGET_KIND,
|
|
17064
|
-
targetName: packCloudRunTargetName({ serviceName, method, path:
|
|
17202
|
+
targetName: packCloudRunTargetName({ serviceName, method, path: path84 }),
|
|
17065
17203
|
callCount: 1,
|
|
17066
17204
|
errorCount: isError ? 1 : 0,
|
|
17067
17205
|
lastObservedIso: timestamp
|
|
@@ -17100,14 +17238,14 @@ function createCloudRunResolveTarget(graph, config) {
|
|
|
17100
17238
|
if (signal.targetKind !== CLOUD_RUN_TARGET_KIND) return null;
|
|
17101
17239
|
const identity = parseCloudRunTargetName(signal.targetName);
|
|
17102
17240
|
if (!identity) return null;
|
|
17103
|
-
const { serviceName: gcpServiceName, method, path:
|
|
17241
|
+
const { serviceName: gcpServiceName, method, path: path84 } = identity;
|
|
17104
17242
|
const mappedService = config.serviceMap?.[gcpServiceName];
|
|
17105
17243
|
if (mappedService) {
|
|
17106
17244
|
const routeNodeId = findMatchingRouteNode2(
|
|
17107
17245
|
graph,
|
|
17108
17246
|
mappedService,
|
|
17109
17247
|
method,
|
|
17110
|
-
normalizePathTemplate(
|
|
17248
|
+
normalizePathTemplate(path84)
|
|
17111
17249
|
);
|
|
17112
17250
|
if (routeNodeId) {
|
|
17113
17251
|
return { targetNodeId: routeNodeId, serviceName: mappedService, edgeType: import_types71.EdgeType.CALLS };
|
|
@@ -17504,6 +17642,338 @@ function createPlanetscaleConnector(graph, config, deps = {}) {
|
|
|
17504
17642
|
};
|
|
17505
17643
|
}
|
|
17506
17644
|
|
|
17645
|
+
// src/connectors/eas/index.ts
|
|
17646
|
+
init_cjs_shims();
|
|
17647
|
+
|
|
17648
|
+
// src/connectors/eas/client.ts
|
|
17649
|
+
init_cjs_shims();
|
|
17650
|
+
|
|
17651
|
+
// src/connectors/eas/types.ts
|
|
17652
|
+
init_cjs_shims();
|
|
17653
|
+
function readEasCredentials(raw) {
|
|
17654
|
+
const token = raw["token"];
|
|
17655
|
+
if (typeof token !== "string" || token.length === 0) {
|
|
17656
|
+
throw new Error("eas connector: credentials.token (EXPO_TOKEN) must be a non-empty string");
|
|
17657
|
+
}
|
|
17658
|
+
return { token };
|
|
17659
|
+
}
|
|
17660
|
+
var EAS_STATUS_ERRORED = "ERRORED";
|
|
17661
|
+
var TRANSIENT_BUILD_PHASES = /* @__PURE__ */ new Set([
|
|
17662
|
+
"SPIN_UP_BUILDER",
|
|
17663
|
+
"PREPARE_CREDENTIALS",
|
|
17664
|
+
"RESTORE_CACHE",
|
|
17665
|
+
"UPLOAD_APPLICATION_ARCHIVE"
|
|
17666
|
+
]);
|
|
17667
|
+
var INTERNAL_ERROR_CODE = /INTERNAL_SERVER_ERROR|EAS_BUILD_.*INTERNAL|_INTERNAL_ERROR|UNKNOWN_ERROR/i;
|
|
17668
|
+
function isTransientFailure(err) {
|
|
17669
|
+
if (!err) return false;
|
|
17670
|
+
const phase = typeof err.buildPhase === "string" ? err.buildPhase : void 0;
|
|
17671
|
+
if (phase) {
|
|
17672
|
+
if (TRANSIENT_BUILD_PHASES.has(phase)) return true;
|
|
17673
|
+
if (/^SPIN_UP|_CREDENTIALS$|^RESTORE_CACHE/i.test(phase)) return true;
|
|
17674
|
+
}
|
|
17675
|
+
const code = typeof err.errorCode === "string" ? err.errorCode : void 0;
|
|
17676
|
+
if (code && INTERNAL_ERROR_CODE.test(code)) return true;
|
|
17677
|
+
return false;
|
|
17678
|
+
}
|
|
17679
|
+
var FIELD_SEP3 = "\0";
|
|
17680
|
+
var EAS_TARGET_KIND = "eas-build";
|
|
17681
|
+
function packEasTargetName(identity) {
|
|
17682
|
+
return [identity.serviceName, identity.phase].join(FIELD_SEP3);
|
|
17683
|
+
}
|
|
17684
|
+
function parseEasTargetName(targetName) {
|
|
17685
|
+
const sep = targetName.indexOf(FIELD_SEP3);
|
|
17686
|
+
if (sep === -1) return null;
|
|
17687
|
+
const serviceName = targetName.slice(0, sep);
|
|
17688
|
+
const phase = targetName.slice(sep + 1);
|
|
17689
|
+
if (!serviceName) return null;
|
|
17690
|
+
return { serviceName, phase };
|
|
17691
|
+
}
|
|
17692
|
+
|
|
17693
|
+
// src/connectors/eas/client.ts
|
|
17694
|
+
var DEFAULT_EAS_API_URL = "https://api.expo.dev/graphql";
|
|
17695
|
+
var DEFAULT_PAGE_SIZE = 50;
|
|
17696
|
+
var DEFAULT_MAX_PAGES = 10;
|
|
17697
|
+
var DEFAULT_MAX_LOG_BYTES = 16 * 1024;
|
|
17698
|
+
var DEFAULT_MAX_LOOKBACK_MS6 = 7 * 24 * 60 * 60 * 1e3;
|
|
17699
|
+
var BUILDS_QUERY = `
|
|
17700
|
+
query NeatEasErroredBuilds($appId: String!, $offset: Int!, $limit: Int!) {
|
|
17701
|
+
app {
|
|
17702
|
+
byId(appId: $appId) {
|
|
17703
|
+
builds(offset: $offset, limit: $limit, filter: { status: ERRORED }) {
|
|
17704
|
+
id
|
|
17705
|
+
status
|
|
17706
|
+
platform
|
|
17707
|
+
buildProfile
|
|
17708
|
+
gitCommitHash
|
|
17709
|
+
gitCommitMessage
|
|
17710
|
+
gitRef
|
|
17711
|
+
isGitWorkingTreeDirty
|
|
17712
|
+
createdAt
|
|
17713
|
+
completedAt
|
|
17714
|
+
error {
|
|
17715
|
+
buildPhase
|
|
17716
|
+
errorCode
|
|
17717
|
+
message
|
|
17718
|
+
docsUrl
|
|
17719
|
+
}
|
|
17720
|
+
logFileUrls
|
|
17721
|
+
}
|
|
17722
|
+
}
|
|
17723
|
+
}
|
|
17724
|
+
}
|
|
17725
|
+
`;
|
|
17726
|
+
async function easGraphQL(apiUrl, token, query, variables, accountKey, fetchImpl) {
|
|
17727
|
+
const res = await junctionFetch(
|
|
17728
|
+
apiUrl,
|
|
17729
|
+
{
|
|
17730
|
+
method: "POST",
|
|
17731
|
+
headers: {
|
|
17732
|
+
"Content-Type": "application/json",
|
|
17733
|
+
...bearerAuthHeader(token)
|
|
17734
|
+
},
|
|
17735
|
+
body: JSON.stringify({ query, variables })
|
|
17736
|
+
},
|
|
17737
|
+
// accountKey: the Expo app id — the per-(provider, accountKey) rate-limit
|
|
17738
|
+
// bucket (ADR-131), the closest thing this connector carries to "one account".
|
|
17739
|
+
{ provider: "eas", accountKey, ...fetchImpl ? { fetchImpl } : {} }
|
|
17740
|
+
);
|
|
17741
|
+
if (!res.ok) {
|
|
17742
|
+
throw new Error(`Expo GraphQL request failed: ${res.status} ${res.statusText}`);
|
|
17743
|
+
}
|
|
17744
|
+
const body = await res.json();
|
|
17745
|
+
if (body.errors && body.errors.length > 0) {
|
|
17746
|
+
throw new Error(`Expo GraphQL errors: ${body.errors.map((e) => e.message).join("; ")}`);
|
|
17747
|
+
}
|
|
17748
|
+
if (!body.data) throw new Error("Expo GraphQL response carried no data");
|
|
17749
|
+
return body.data;
|
|
17750
|
+
}
|
|
17751
|
+
async function fetchErroredBuilds(token, config, fetchImpl) {
|
|
17752
|
+
const apiUrl = config.apiUrl ?? DEFAULT_EAS_API_URL;
|
|
17753
|
+
const pageSize = config.pageSize ?? DEFAULT_PAGE_SIZE;
|
|
17754
|
+
const maxPages = config.maxPages ?? DEFAULT_MAX_PAGES;
|
|
17755
|
+
const out = [];
|
|
17756
|
+
const seen = /* @__PURE__ */ new Set();
|
|
17757
|
+
for (let page = 0; page < maxPages; page++) {
|
|
17758
|
+
const data = await easGraphQL(
|
|
17759
|
+
apiUrl,
|
|
17760
|
+
token,
|
|
17761
|
+
BUILDS_QUERY,
|
|
17762
|
+
{ appId: config.appId, offset: page * pageSize, limit: pageSize },
|
|
17763
|
+
config.appId,
|
|
17764
|
+
fetchImpl
|
|
17765
|
+
);
|
|
17766
|
+
const builds = data.app?.byId?.builds;
|
|
17767
|
+
if (!Array.isArray(builds)) break;
|
|
17768
|
+
let added = 0;
|
|
17769
|
+
for (const b of builds) {
|
|
17770
|
+
if (!b || typeof b.id !== "string" || b.id.length === 0) continue;
|
|
17771
|
+
if (b.status !== EAS_STATUS_ERRORED) continue;
|
|
17772
|
+
if (seen.has(b.id)) continue;
|
|
17773
|
+
seen.add(b.id);
|
|
17774
|
+
out.push(b);
|
|
17775
|
+
added++;
|
|
17776
|
+
}
|
|
17777
|
+
if (builds.length < pageSize) break;
|
|
17778
|
+
if (added === 0) break;
|
|
17779
|
+
}
|
|
17780
|
+
return out;
|
|
17781
|
+
}
|
|
17782
|
+
async function fetchBuildLogs(logFileUrls, maxBytes = DEFAULT_MAX_LOG_BYTES, fetchImpl) {
|
|
17783
|
+
if (!Array.isArray(logFileUrls) || logFileUrls.length === 0) return "";
|
|
17784
|
+
const doFetch = fetchImpl ?? fetch;
|
|
17785
|
+
const chunks = [];
|
|
17786
|
+
for (const url of logFileUrls) {
|
|
17787
|
+
if (typeof url !== "string" || url.length === 0) continue;
|
|
17788
|
+
try {
|
|
17789
|
+
const res = await doFetch(url);
|
|
17790
|
+
if (!res.ok) continue;
|
|
17791
|
+
chunks.push(await res.text());
|
|
17792
|
+
} catch {
|
|
17793
|
+
}
|
|
17794
|
+
}
|
|
17795
|
+
const joined = chunks.join("\n");
|
|
17796
|
+
return joined.length > maxBytes ? joined.slice(joined.length - maxBytes) : joined;
|
|
17797
|
+
}
|
|
17798
|
+
|
|
17799
|
+
// src/connectors/eas/map.ts
|
|
17800
|
+
init_cjs_shims();
|
|
17801
|
+
function buildEventTime(build) {
|
|
17802
|
+
if (typeof build.completedAt === "string" && build.completedAt.length > 0) return build.completedAt;
|
|
17803
|
+
if (typeof build.createdAt === "string" && build.createdAt.length > 0) return build.createdAt;
|
|
17804
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
17805
|
+
}
|
|
17806
|
+
function incidentMessage2(build) {
|
|
17807
|
+
const err = build.error ?? {};
|
|
17808
|
+
const phase = typeof err.buildPhase === "string" && err.buildPhase.length > 0 ? ` at ${err.buildPhase}` : "";
|
|
17809
|
+
const detail = typeof err.message === "string" && err.message.trim().length > 0 && err.message.trim() || typeof err.errorCode === "string" && err.errorCode.length > 0 && err.errorCode || "no error detail reported";
|
|
17810
|
+
let msg = `EAS build failed${phase}: ${detail}`;
|
|
17811
|
+
if (build.isGitWorkingTreeDirty === true) {
|
|
17812
|
+
msg += " (built from a dirty working tree \u2014 the commit may not represent what built)";
|
|
17813
|
+
}
|
|
17814
|
+
return msg;
|
|
17815
|
+
}
|
|
17816
|
+
function incidentAttributes(build) {
|
|
17817
|
+
const attrs = {};
|
|
17818
|
+
const err = build.error ?? {};
|
|
17819
|
+
const put = (k, v) => {
|
|
17820
|
+
if (typeof v === "string" && v.length === 0) return;
|
|
17821
|
+
if (v !== void 0 && v !== null) attrs[k] = v;
|
|
17822
|
+
};
|
|
17823
|
+
put("eas.buildId", build.id);
|
|
17824
|
+
put("eas.platform", build.platform ?? void 0);
|
|
17825
|
+
put("eas.buildProfile", build.buildProfile ?? void 0);
|
|
17826
|
+
put("eas.buildPhase", err.buildPhase ?? void 0);
|
|
17827
|
+
put("eas.errorCode", err.errorCode ?? void 0);
|
|
17828
|
+
put("eas.docsUrl", err.docsUrl ?? void 0);
|
|
17829
|
+
put("eas.gitCommitHash", build.gitCommitHash ?? void 0);
|
|
17830
|
+
put("eas.gitRef", build.gitRef ?? void 0);
|
|
17831
|
+
put("eas.gitCommitMessage", build.gitCommitMessage ?? void 0);
|
|
17832
|
+
if (typeof build.isGitWorkingTreeDirty === "boolean") {
|
|
17833
|
+
attrs["eas.gitWorkingTreeDirty"] = build.isGitWorkingTreeDirty;
|
|
17834
|
+
if (build.isGitWorkingTreeDirty) attrs["eas.confidence"] = "low";
|
|
17835
|
+
}
|
|
17836
|
+
put("eas.createdAt", build.createdAt ?? void 0);
|
|
17837
|
+
put("eas.completedAt", build.completedAt ?? void 0);
|
|
17838
|
+
if (typeof build.logsText === "string" && build.logsText.length > 0) {
|
|
17839
|
+
attrs["eas.logs"] = build.logsText;
|
|
17840
|
+
}
|
|
17841
|
+
return attrs;
|
|
17842
|
+
}
|
|
17843
|
+
function mapBuildToSignal(build, serviceName) {
|
|
17844
|
+
if (!build || typeof build !== "object") return null;
|
|
17845
|
+
if (build.status !== EAS_STATUS_ERRORED) return null;
|
|
17846
|
+
if (!build.error) return null;
|
|
17847
|
+
if (isTransientFailure(build.error)) return null;
|
|
17848
|
+
const timestamp = buildEventTime(build);
|
|
17849
|
+
const phase = typeof build.error.buildPhase === "string" ? build.error.buildPhase : "";
|
|
17850
|
+
return {
|
|
17851
|
+
targetKind: EAS_TARGET_KIND,
|
|
17852
|
+
targetName: packEasTargetName({ serviceName, phase }),
|
|
17853
|
+
// Incident-only — no edge, so no call/error count to replay.
|
|
17854
|
+
callCount: 0,
|
|
17855
|
+
errorCount: 0,
|
|
17856
|
+
lastObservedIso: timestamp,
|
|
17857
|
+
incident: {
|
|
17858
|
+
id: `eas:build:${build.id}`,
|
|
17859
|
+
timestamp,
|
|
17860
|
+
service: serviceName,
|
|
17861
|
+
errorType: "eas-build-failure",
|
|
17862
|
+
errorMessage: incidentMessage2(build),
|
|
17863
|
+
attributes: incidentAttributes(build)
|
|
17864
|
+
}
|
|
17865
|
+
};
|
|
17866
|
+
}
|
|
17867
|
+
function mapBuildsToSignals(builds, serviceName) {
|
|
17868
|
+
const out = [];
|
|
17869
|
+
for (const build of builds) {
|
|
17870
|
+
const signal = mapBuildToSignal(build, serviceName);
|
|
17871
|
+
if (signal) out.push(signal);
|
|
17872
|
+
}
|
|
17873
|
+
return out;
|
|
17874
|
+
}
|
|
17875
|
+
|
|
17876
|
+
// src/connectors/eas/resolve.ts
|
|
17877
|
+
init_cjs_shims();
|
|
17878
|
+
var import_types83 = require("@neat.is/types");
|
|
17879
|
+
var NO_ENV2 = "unknown";
|
|
17880
|
+
var EAS_JSON_PHASES = /* @__PURE__ */ new Set(["READ_EAS_JSON"]);
|
|
17881
|
+
var APP_CONFIG_PHASES = /* @__PURE__ */ new Set([
|
|
17882
|
+
"READ_APP_CONFIG",
|
|
17883
|
+
"CONFIGURE_EXPO_UPDATES",
|
|
17884
|
+
"CALCULATE_EXPO_UPDATES_RUNTIME_VERSION"
|
|
17885
|
+
]);
|
|
17886
|
+
function configBasenamesForPhase(phase) {
|
|
17887
|
+
if (EAS_JSON_PHASES.has(phase)) return ["eas.json"];
|
|
17888
|
+
if (APP_CONFIG_PHASES.has(phase)) return ["app.json", "app.config.json"];
|
|
17889
|
+
return [];
|
|
17890
|
+
}
|
|
17891
|
+
function configNodeService(graph, configNodeId) {
|
|
17892
|
+
for (const edgeId of graph.inboundEdges(configNodeId)) {
|
|
17893
|
+
const edge = graph.getEdgeAttributes(edgeId);
|
|
17894
|
+
if (edge.type !== import_types83.EdgeType.CONFIGURED_BY) continue;
|
|
17895
|
+
const parsed = (0, import_types83.parseFileId)(edge.source);
|
|
17896
|
+
if (parsed) return parsed.service;
|
|
17897
|
+
}
|
|
17898
|
+
return null;
|
|
17899
|
+
}
|
|
17900
|
+
function findConfigNode(graph, basenames, serviceName) {
|
|
17901
|
+
let scoped = null;
|
|
17902
|
+
let anyMatch = null;
|
|
17903
|
+
graph.forEachNode((id, attrs) => {
|
|
17904
|
+
if (scoped) return;
|
|
17905
|
+
const node = attrs;
|
|
17906
|
+
if (node.type !== import_types83.NodeType.ConfigNode) return;
|
|
17907
|
+
if (typeof node.name !== "string" || !basenames.includes(node.name)) return;
|
|
17908
|
+
if (anyMatch === null) anyMatch = id;
|
|
17909
|
+
if (configNodeService(graph, id) === serviceName) scoped = id;
|
|
17910
|
+
});
|
|
17911
|
+
return scoped ?? anyMatch;
|
|
17912
|
+
}
|
|
17913
|
+
function createEasResolveTarget(graph) {
|
|
17914
|
+
return (signal) => {
|
|
17915
|
+
if (signal.targetKind !== EAS_TARGET_KIND) return null;
|
|
17916
|
+
const identity = parseEasTargetName(signal.targetName);
|
|
17917
|
+
if (!identity) return null;
|
|
17918
|
+
const { serviceName, phase } = identity;
|
|
17919
|
+
const basenames = configBasenamesForPhase(phase);
|
|
17920
|
+
if (basenames.length > 0) {
|
|
17921
|
+
const configNodeId = findConfigNode(graph, basenames, serviceName);
|
|
17922
|
+
if (configNodeId) {
|
|
17923
|
+
return { targetNodeId: configNodeId, serviceName, edgeType: import_types83.EdgeType.CALLS };
|
|
17924
|
+
}
|
|
17925
|
+
}
|
|
17926
|
+
return {
|
|
17927
|
+
targetNodeId: resolveFusedServiceId(graph, serviceName, NO_ENV2),
|
|
17928
|
+
serviceName,
|
|
17929
|
+
edgeType: import_types83.EdgeType.CALLS
|
|
17930
|
+
};
|
|
17931
|
+
};
|
|
17932
|
+
}
|
|
17933
|
+
|
|
17934
|
+
// src/connectors/eas/index.ts
|
|
17935
|
+
function isBuildSince(build, sinceIso) {
|
|
17936
|
+
const t = Date.parse(buildEventTime(build));
|
|
17937
|
+
const s = Date.parse(sinceIso);
|
|
17938
|
+
if (Number.isNaN(t) || Number.isNaN(s)) return true;
|
|
17939
|
+
return t > s;
|
|
17940
|
+
}
|
|
17941
|
+
function boundedSinceIso2(since, now, maxLookbackMs) {
|
|
17942
|
+
const floor = new Date(now.getTime() - maxLookbackMs);
|
|
17943
|
+
if (!since) return floor.toISOString();
|
|
17944
|
+
const sinceMs = new Date(since).getTime();
|
|
17945
|
+
if (Number.isNaN(sinceMs)) return floor.toISOString();
|
|
17946
|
+
return sinceMs < floor.getTime() ? floor.toISOString() : new Date(sinceMs).toISOString();
|
|
17947
|
+
}
|
|
17948
|
+
var EasConnector = class {
|
|
17949
|
+
constructor(config, fetchImpl) {
|
|
17950
|
+
this.config = config;
|
|
17951
|
+
this.fetchImpl = fetchImpl;
|
|
17952
|
+
}
|
|
17953
|
+
config;
|
|
17954
|
+
fetchImpl;
|
|
17955
|
+
provider = "eas";
|
|
17956
|
+
async poll(ctx) {
|
|
17957
|
+
const creds = readEasCredentials(ctx.credentials);
|
|
17958
|
+
const serviceName = this.config.serviceName ?? this.config.appId;
|
|
17959
|
+
const maxLookbackMs = this.config.maxLookbackMs ?? DEFAULT_MAX_LOOKBACK_MS6;
|
|
17960
|
+
const sinceIso = boundedSinceIso2(ctx.since, /* @__PURE__ */ new Date(), maxLookbackMs);
|
|
17961
|
+
const builds = await fetchErroredBuilds(creds.token, this.config, this.fetchImpl);
|
|
17962
|
+
const fresh = builds.filter((b) => isBuildSince(b, sinceIso));
|
|
17963
|
+
const maxLogBytes = this.config.maxLogBytes ?? DEFAULT_MAX_LOG_BYTES;
|
|
17964
|
+
for (const build of fresh) {
|
|
17965
|
+
build.logsText = await fetchBuildLogs(build.logFileUrls, maxLogBytes, this.fetchImpl);
|
|
17966
|
+
}
|
|
17967
|
+
return mapBuildsToSignals(fresh, serviceName);
|
|
17968
|
+
}
|
|
17969
|
+
};
|
|
17970
|
+
function createEasConnector(graph, config, fetchImpl) {
|
|
17971
|
+
return {
|
|
17972
|
+
connector: new EasConnector(config, fetchImpl),
|
|
17973
|
+
resolveTarget: createEasResolveTarget(graph)
|
|
17974
|
+
};
|
|
17975
|
+
}
|
|
17976
|
+
|
|
17507
17977
|
// src/connectors/registry.ts
|
|
17508
17978
|
var CLOUDFLARE_API_BASE_URL = "https://api.cloudflare.com/client/v4";
|
|
17509
17979
|
async function authProbe(input) {
|
|
@@ -17791,6 +18261,41 @@ var PROVIDER_DISPATCH = {
|
|
|
17791
18261
|
...fetchImpl ? { fetchImpl } : {}
|
|
17792
18262
|
});
|
|
17793
18263
|
}
|
|
18264
|
+
},
|
|
18265
|
+
eas: {
|
|
18266
|
+
provider: "eas",
|
|
18267
|
+
// The secret is a single robot-user EXPO_TOKEN; a single-string credential
|
|
18268
|
+
// maps to `token`. `appId` is non-secret config (connector-config.md §7.1).
|
|
18269
|
+
primaryCredentialKey: "token",
|
|
18270
|
+
requiredCredentialFields: ["token"],
|
|
18271
|
+
requiredOptionFields: ["appId"],
|
|
18272
|
+
build(graph, options) {
|
|
18273
|
+
return createEasConnector(graph, options);
|
|
18274
|
+
},
|
|
18275
|
+
// Runs the connector's real `builds` query at limit 1 — the exact read poll()
|
|
18276
|
+
// performs, minus the pages — so the probe checks both that the EXPO_TOKEN
|
|
18277
|
+
// authenticates and that this app id is reachable, the same probe-the-real-
|
|
18278
|
+
// query discipline Railway and Cloud Run use over a trivial `{ __typename }`.
|
|
18279
|
+
// A bad or wrong-scoped token comes back as an Expo GraphQL error, which
|
|
18280
|
+
// `fetchErroredBuilds` throws on, so it fails honestly here rather than
|
|
18281
|
+
// silently at the first poll.
|
|
18282
|
+
async validate({ credentials, options, fetchImpl }) {
|
|
18283
|
+
const cfg = options;
|
|
18284
|
+
const appId = String(cfg.appId ?? "");
|
|
18285
|
+
if (!appId) return { ok: false, reason: "eas: appId is required to validate" };
|
|
18286
|
+
const probeConfig = {
|
|
18287
|
+
appId,
|
|
18288
|
+
pageSize: 1,
|
|
18289
|
+
maxPages: 1,
|
|
18290
|
+
...cfg.apiUrl ? { apiUrl: cfg.apiUrl } : {}
|
|
18291
|
+
};
|
|
18292
|
+
try {
|
|
18293
|
+
await fetchErroredBuilds(String(credentials.token ?? ""), probeConfig, fetchImpl);
|
|
18294
|
+
return { ok: true };
|
|
18295
|
+
} catch (err) {
|
|
18296
|
+
return { ok: false, reason: `eas auth check failed: ${err.message}` };
|
|
18297
|
+
}
|
|
18298
|
+
}
|
|
17794
18299
|
}
|
|
17795
18300
|
};
|
|
17796
18301
|
function vercelCredsFrom(credentials) {
|
|
@@ -18003,7 +18508,11 @@ async function startConnectorPolling(input) {
|
|
|
18003
18508
|
const stopFns = all.map(
|
|
18004
18509
|
(registration) => startConnectorPollLoop(
|
|
18005
18510
|
registration.connector,
|
|
18006
|
-
{
|
|
18511
|
+
{
|
|
18512
|
+
projectDir: input.projectDir,
|
|
18513
|
+
credentials: registration.credentials,
|
|
18514
|
+
...input.errorsPath ? { errorsPath: input.errorsPath } : {}
|
|
18515
|
+
},
|
|
18007
18516
|
input.graph,
|
|
18008
18517
|
registration.resolveTarget,
|
|
18009
18518
|
{ intervalMs: registration.intervalMs, connectorId: registration.id }
|
|
@@ -18221,11 +18730,11 @@ function registerRoutes(scope, ctx) {
|
|
|
18221
18730
|
const candidates = req.query.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
18222
18731
|
const parsed = [];
|
|
18223
18732
|
for (const c of candidates) {
|
|
18224
|
-
const r =
|
|
18733
|
+
const r = import_types86.DivergenceTypeSchema.safeParse(c);
|
|
18225
18734
|
if (!r.success) {
|
|
18226
18735
|
return reply.code(400).send({
|
|
18227
18736
|
error: `unknown divergence type "${c}"`,
|
|
18228
|
-
allowed:
|
|
18737
|
+
allowed: import_types86.DivergenceTypeSchema.options
|
|
18229
18738
|
});
|
|
18230
18739
|
}
|
|
18231
18740
|
parsed.push(r.data);
|
|
@@ -18332,10 +18841,15 @@ function registerRoutes(scope, ctx) {
|
|
|
18332
18841
|
}
|
|
18333
18842
|
const reg = built.registration;
|
|
18334
18843
|
const at = (/* @__PURE__ */ new Date()).toISOString();
|
|
18844
|
+
const incidentsPath = errorsPathFor(proj);
|
|
18335
18845
|
try {
|
|
18336
18846
|
const result = await ctx.runPoll(
|
|
18337
18847
|
reg.connector,
|
|
18338
|
-
{
|
|
18848
|
+
{
|
|
18849
|
+
projectDir: proj.scanPath ?? "",
|
|
18850
|
+
credentials: reg.credentials,
|
|
18851
|
+
...incidentsPath ? { errorsPath: incidentsPath } : {}
|
|
18852
|
+
},
|
|
18339
18853
|
proj.graph,
|
|
18340
18854
|
reg.resolveTarget
|
|
18341
18855
|
);
|
|
@@ -18534,7 +19048,7 @@ function registerRoutes(scope, ctx) {
|
|
|
18534
19048
|
const log = new PolicyViolationsLog(proj.paths.policyViolationsPath);
|
|
18535
19049
|
let violations = await log.readAll();
|
|
18536
19050
|
if (req.query.severity) {
|
|
18537
|
-
const sev =
|
|
19051
|
+
const sev = import_types86.PolicySeveritySchema.safeParse(req.query.severity);
|
|
18538
19052
|
if (!sev.success) {
|
|
18539
19053
|
return reply.code(400).send({
|
|
18540
19054
|
error: "invalid severity",
|
|
@@ -18573,7 +19087,7 @@ function registerRoutes(scope, ctx) {
|
|
|
18573
19087
|
scope.post("/policies/check", async (req, reply) => {
|
|
18574
19088
|
const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
|
|
18575
19089
|
if (!proj) return;
|
|
18576
|
-
const parsed =
|
|
19090
|
+
const parsed = import_types86.PoliciesCheckBodySchema.safeParse(req.body ?? {});
|
|
18577
19091
|
if (!parsed.success) {
|
|
18578
19092
|
return reply.code(400).send({
|
|
18579
19093
|
error: "invalid /policies/check body",
|
|
@@ -18906,7 +19420,7 @@ var import_node_fs34 = require("fs");
|
|
|
18906
19420
|
var import_node_path68 = __toESM(require("path"), 1);
|
|
18907
19421
|
|
|
18908
19422
|
// src/daemon.ts
|
|
18909
|
-
var
|
|
19423
|
+
var import_types87 = require("@neat.is/types");
|
|
18910
19424
|
function daemonJsonPath(scanPath) {
|
|
18911
19425
|
return import_node_path69.default.join(scanPath, "neat-out", "daemon.json");
|
|
18912
19426
|
}
|
|
@@ -19275,12 +19789,46 @@ var SubstringIndex = class {
|
|
|
19275
19789
|
this.graph = graph;
|
|
19276
19790
|
}
|
|
19277
19791
|
};
|
|
19792
|
+
var DEFAULT_SEARCH_INIT_TIMEOUT_MS = 3e4;
|
|
19793
|
+
function searchInitTimeoutMs() {
|
|
19794
|
+
const env = process.env.NEAT_SEARCH_INIT_TIMEOUT_MS;
|
|
19795
|
+
if (env !== void 0 && env.length > 0) {
|
|
19796
|
+
const n = Number.parseInt(env, 10);
|
|
19797
|
+
if (Number.isFinite(n) && n >= 0) return n;
|
|
19798
|
+
}
|
|
19799
|
+
return DEFAULT_SEARCH_INIT_TIMEOUT_MS;
|
|
19800
|
+
}
|
|
19801
|
+
async function resolveEmbedderBounded(factory, timeoutMs) {
|
|
19802
|
+
if (timeoutMs <= 0) return factory();
|
|
19803
|
+
let timer;
|
|
19804
|
+
const TIMED_OUT = /* @__PURE__ */ Symbol("embedder-init-timeout");
|
|
19805
|
+
const timeout = new Promise((resolve) => {
|
|
19806
|
+
timer = setTimeout(() => resolve(TIMED_OUT), timeoutMs);
|
|
19807
|
+
timer.unref?.();
|
|
19808
|
+
});
|
|
19809
|
+
try {
|
|
19810
|
+
const result = await Promise.race([factory(), timeout]);
|
|
19811
|
+
if (result === TIMED_OUT) {
|
|
19812
|
+
console.warn(
|
|
19813
|
+
`semantic_search: embedder init exceeded ${timeoutMs}ms; falling back to substring search. Set NEAT_SEARCH_INIT_TIMEOUT_MS to raise the bound (or 0 to wait indefinitely).`
|
|
19814
|
+
);
|
|
19815
|
+
return null;
|
|
19816
|
+
}
|
|
19817
|
+
return result;
|
|
19818
|
+
} finally {
|
|
19819
|
+
if (timer) clearTimeout(timer);
|
|
19820
|
+
}
|
|
19821
|
+
}
|
|
19278
19822
|
async function buildSearchIndex(graph, options = {}) {
|
|
19279
19823
|
let embedder = null;
|
|
19280
19824
|
if (options.embedder) {
|
|
19281
19825
|
embedder = options.embedder;
|
|
19282
19826
|
} else if (options.forceProvider !== "substring") {
|
|
19283
|
-
|
|
19827
|
+
const factory = options.embedderFactory ?? pickEmbedder;
|
|
19828
|
+
embedder = await resolveEmbedderBounded(
|
|
19829
|
+
factory,
|
|
19830
|
+
options.initTimeoutMs ?? searchInitTimeoutMs()
|
|
19831
|
+
);
|
|
19284
19832
|
if (options.forceProvider === "ollama" && embedder?.provider !== "ollama") {
|
|
19285
19833
|
embedder = null;
|
|
19286
19834
|
}
|
|
@@ -19530,6 +20078,9 @@ async function startWatch(graph, opts) {
|
|
|
19530
20078
|
project: projectName,
|
|
19531
20079
|
graph,
|
|
19532
20080
|
projectDir: opts.scanPath,
|
|
20081
|
+
// Incident ledger for an incident-emitting connector (ADR-185), same path
|
|
20082
|
+
// `neat watch`'s own error-span writer already uses.
|
|
20083
|
+
errorsPath: opts.errorsPath,
|
|
19533
20084
|
...opts.neatHome ? { home: opts.neatHome } : {},
|
|
19534
20085
|
onSkip: (skipped, reason) => console.warn(
|
|
19535
20086
|
`neat watch: connector "${skipped.id}" (${skipped.provider}) skipped for project "${projectName}" \u2014 ${reason}`
|
|
@@ -19903,7 +20454,8 @@ var OTEL_ENDPOINT_RESOLVER_CJS = `;(function () {
|
|
|
19903
20454
|
try {
|
|
19904
20455
|
const __rec = JSON.parse(__neatFs.readFileSync(__neatPath.join(__neatDir, 'neat-out', 'daemon.json'), 'utf8'))
|
|
19905
20456
|
if (__rec && __rec.ports && typeof __rec.ports.otlp === 'number') {
|
|
19906
|
-
|
|
20457
|
+
const __neatProj = (typeof __rec.project === 'string' && __rec.project) || '__PROJECT__'
|
|
20458
|
+
process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT = 'http://localhost:' + __rec.ports.otlp + '/projects/' + __neatProj + '/v1/traces'
|
|
19907
20459
|
break
|
|
19908
20460
|
}
|
|
19909
20461
|
} catch (_e) {}
|
|
@@ -19912,7 +20464,7 @@ var OTEL_ENDPOINT_RESOLVER_CJS = `;(function () {
|
|
|
19912
20464
|
__neatDir = __parent
|
|
19913
20465
|
}
|
|
19914
20466
|
} catch (_e) {}
|
|
19915
|
-
process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT ||= 'http://localhost:4318/v1/traces'
|
|
20467
|
+
process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT ||= 'http://localhost:4318/projects/__PROJECT__/v1/traces'
|
|
19916
20468
|
})()`;
|
|
19917
20469
|
var OTEL_ESM_NODE_IMPORTS = "import { readFileSync as __neatReadFileSync } from 'node:fs'\nimport { join as __neatJoin, dirname as __neatDirname } from 'node:path'";
|
|
19918
20470
|
var OTEL_ENDPOINT_RESOLVER_ESM = `;(function () {
|
|
@@ -19923,7 +20475,8 @@ var OTEL_ENDPOINT_RESOLVER_ESM = `;(function () {
|
|
|
19923
20475
|
try {
|
|
19924
20476
|
const __rec = JSON.parse(__neatReadFileSync(__neatJoin(__neatDir, 'neat-out', 'daemon.json'), 'utf8'))
|
|
19925
20477
|
if (__rec && __rec.ports && typeof __rec.ports.otlp === 'number') {
|
|
19926
|
-
|
|
20478
|
+
const __neatProj = (typeof __rec.project === 'string' && __rec.project) || '__PROJECT__'
|
|
20479
|
+
process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT = 'http://localhost:' + __rec.ports.otlp + '/projects/' + __neatProj + '/v1/traces'
|
|
19927
20480
|
break
|
|
19928
20481
|
}
|
|
19929
20482
|
} catch (_e) {}
|
|
@@ -19932,7 +20485,7 @@ var OTEL_ENDPOINT_RESOLVER_ESM = `;(function () {
|
|
|
19932
20485
|
__neatDir = __parent
|
|
19933
20486
|
}
|
|
19934
20487
|
} catch (_e) {}
|
|
19935
|
-
process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT ||= 'http://localhost:4318/v1/traces'
|
|
20488
|
+
process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT ||= 'http://localhost:4318/projects/__PROJECT__/v1/traces'
|
|
19936
20489
|
})()`;
|
|
19937
20490
|
function neatCaptureSource(ts) {
|
|
19938
20491
|
const spanT = ts ? ": any" : "";
|
|
@@ -20334,14 +20887,15 @@ ${registrations.join("\n")}
|
|
|
20334
20887
|
`;
|
|
20335
20888
|
return template.replace(/__SERVICE_NAME__/g, serviceName).replace(/__PROJECT__/g, projectName).replace(/__INSTRUMENTATION_BLOCK__\n?/g, block);
|
|
20336
20889
|
}
|
|
20337
|
-
function renderEnvNeat(serviceName,
|
|
20890
|
+
function renderEnvNeat(serviceName, projectName) {
|
|
20338
20891
|
return [
|
|
20339
20892
|
"# Generated by `neat init --apply` (ADR-069).",
|
|
20340
20893
|
`OTEL_SERVICE_NAME=${serviceName}`,
|
|
20341
20894
|
"# Advisory only \u2014 the generated otel-init resolves the live endpoint from",
|
|
20342
|
-
"# <project>/neat-out/daemon.json (ports.otlp) at boot (ADR-096).
|
|
20343
|
-
"# canonical default the daemon takes when its first-choice OTLP
|
|
20344
|
-
"
|
|
20895
|
+
"# <project>/neat-out/daemon.json (ports.otlp + project) at boot (ADR-096).",
|
|
20896
|
+
"# This is the canonical default the daemon takes when its first-choice OTLP",
|
|
20897
|
+
"# port is free; the project scope routes the span without service.name guessing (#879).",
|
|
20898
|
+
`OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=http://localhost:4318/projects/${projectName}/v1/traces`,
|
|
20345
20899
|
"OTEL_EXPORTER_OTLP_PROTOCOL=http/json",
|
|
20346
20900
|
"# Set NEAT_OTEL_TOKEN to the daemon's OTLP secret to authenticate exported spans (#410).",
|
|
20347
20901
|
"# NEAT_OTEL_TOKEN=",
|
|
@@ -20407,7 +20961,7 @@ var NEXT_INSTRUMENTATION_EDGE_TS = `${NEXT_INSTRUMENTATION_EDGE_HEADER}
|
|
|
20407
20961
|
import { registerOTel } from '@vercel/otel'
|
|
20408
20962
|
|
|
20409
20963
|
process.env.OTEL_SERVICE_NAME ||= '__SERVICE_NAME__'
|
|
20410
|
-
process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT ||= 'http://localhost:4318/v1/traces'
|
|
20964
|
+
process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT ||= 'http://localhost:4318/projects/__PROJECT__/v1/traces'
|
|
20411
20965
|
${OTEL_OTLP_PROTOCOL_JS}
|
|
20412
20966
|
${OTEL_OTLP_HEADERS_JS}
|
|
20413
20967
|
|
|
@@ -22155,14 +22709,519 @@ async function apply3(installPlan) {
|
|
|
22155
22709
|
await import_node_fs41.promises.writeFile(generated.file, generated.contents, "utf8");
|
|
22156
22710
|
writtenFiles.push(generated.file);
|
|
22157
22711
|
}
|
|
22158
|
-
|
|
22712
|
+
const wroteManifest = writtenFiles.some((f) => f.split(/[\\/]/).pop() === "go.mod");
|
|
22713
|
+
return {
|
|
22714
|
+
serviceDir: installPlan.serviceDir,
|
|
22715
|
+
outcome: writtenFiles.length ? "instrumented" : "already-instrumented",
|
|
22716
|
+
writtenFiles,
|
|
22717
|
+
...wroteManifest ? { followUpInstall: "go mod download" } : {}
|
|
22718
|
+
};
|
|
22159
22719
|
}
|
|
22160
22720
|
var goInstaller = { name: "go", detect: detect3, plan: plan3, apply: apply3 };
|
|
22161
22721
|
|
|
22722
|
+
// src/installers/ruby.ts
|
|
22723
|
+
init_cjs_shims();
|
|
22724
|
+
var import_node_fs42 = require("fs");
|
|
22725
|
+
var import_node_path76 = __toESM(require("path"), 1);
|
|
22726
|
+
var RUBY_MARKERS = [
|
|
22727
|
+
"Gemfile",
|
|
22728
|
+
"Gemfile.lock"
|
|
22729
|
+
];
|
|
22730
|
+
var RUBY_GEMS = [
|
|
22731
|
+
{ name: "opentelemetry-sdk", version: "~> 1.5" },
|
|
22732
|
+
{ name: "opentelemetry-exporter-otlp", version: "~> 0.29" },
|
|
22733
|
+
{ name: "opentelemetry-instrumentation-all", version: "~> 0.62" }
|
|
22734
|
+
];
|
|
22735
|
+
var NEAT_OTEL_STAMP2 = "neat-otel-init v1";
|
|
22736
|
+
var INITIALIZER_REL = import_node_path76.default.join("config", "initializers", "neat_otel.rb");
|
|
22737
|
+
function neatOtelRb(opts = {}) {
|
|
22738
|
+
const service = opts.project ?? "ruby-service";
|
|
22739
|
+
const endpoint2 = opts.project ? `http://localhost:4318/projects/${opts.project}/v1/traces` : "http://localhost:4318/v1/traces";
|
|
22740
|
+
return `# ${NEAT_OTEL_STAMP2} \u2014 generated by NEAT. Safe to re-generate; do not edit.
|
|
22741
|
+
# Rails auto-loads this at boot (config/initializers/*). It points the
|
|
22742
|
+
# OpenTelemetry SDK at your NEAT daemon, enables the Ruby auto-instrumentation
|
|
22743
|
+
# set, and installs a span processor that stamps code.file.path /
|
|
22744
|
+
# code.line.number / code.function.name on the CLIENT/PRODUCER spans your app
|
|
22745
|
+
# issues, so NEAT fuses each runtime span onto the source file that made the
|
|
22746
|
+
# call (docs/contracts/file-awareness.md). Absolute paths are emitted here;
|
|
22747
|
+
# ingest anchors them against the service root. If the OpenTelemetry gems are
|
|
22748
|
+
# not installed this file degrades to a no-op rather than breaking boot.
|
|
22749
|
+
|
|
22750
|
+
begin
|
|
22751
|
+
require 'opentelemetry/sdk'
|
|
22752
|
+
require 'opentelemetry/exporter/otlp'
|
|
22753
|
+
require 'opentelemetry/instrumentation/all'
|
|
22754
|
+
_neat_otel_loaded = true
|
|
22755
|
+
rescue LoadError
|
|
22756
|
+
_neat_otel_loaded = false
|
|
22757
|
+
end
|
|
22758
|
+
|
|
22759
|
+
if _neat_otel_loaded && ENV['NEAT_CALLSITE_DISABLED'] != '1'
|
|
22760
|
+
# Walk the Ruby call stack to the first application frame and stamp the stable
|
|
22761
|
+
# OTel source attributes on CLIENT/PRODUCER spans. SERVER spans are created
|
|
22762
|
+
# before the handler runs, so they stay route/service-grained, honestly.
|
|
22763
|
+
class NeatCallSiteSpanProcessor
|
|
22764
|
+
def initialize(root)
|
|
22765
|
+
@root = root.to_s.end_with?(File::SEPARATOR) ? root.to_s : root.to_s + File::SEPARATOR
|
|
22766
|
+
end
|
|
22767
|
+
|
|
22768
|
+
def on_start(span, _parent_context)
|
|
22769
|
+
kind = span.kind
|
|
22770
|
+
return unless kind == OpenTelemetry::Trace::SpanKind::CLIENT ||
|
|
22771
|
+
kind == OpenTelemetry::Trace::SpanKind::PRODUCER
|
|
22772
|
+
caller_locations(1).each do |loc|
|
|
22773
|
+
file = loc.absolute_path || loc.path
|
|
22774
|
+
next if file.nil?
|
|
22775
|
+
next unless file.start_with?(@root)
|
|
22776
|
+
next if file.include?('/vendor/') || file.include?('/.bundle/')
|
|
22777
|
+
next if file.end_with?('neat_otel.rb')
|
|
22778
|
+
span.set_attribute('code.file.path', file)
|
|
22779
|
+
span.set_attribute('code.line.number', loc.lineno)
|
|
22780
|
+
span.set_attribute('code.function.name', loc.label.to_s)
|
|
22781
|
+
break
|
|
22782
|
+
end
|
|
22783
|
+
rescue StandardError
|
|
22784
|
+
# never break the host application
|
|
22785
|
+
end
|
|
22786
|
+
|
|
22787
|
+
def on_finish(_span); end
|
|
22788
|
+
|
|
22789
|
+
def force_flush(timeout: nil)
|
|
22790
|
+
OpenTelemetry::SDK::Trace::Export::SUCCESS
|
|
22791
|
+
end
|
|
22792
|
+
|
|
22793
|
+
def shutdown(timeout: nil)
|
|
22794
|
+
OpenTelemetry::SDK::Trace::Export::SUCCESS
|
|
22795
|
+
end
|
|
22796
|
+
end
|
|
22797
|
+
|
|
22798
|
+
_neat_root = defined?(Rails) ? Rails.root.to_s : Dir.pwd
|
|
22799
|
+
_neat_service = ENV.fetch('OTEL_SERVICE_NAME', '${service}')
|
|
22800
|
+
_neat_endpoint = ENV.fetch('OTEL_EXPORTER_OTLP_TRACES_ENDPOINT', '${endpoint2}')
|
|
22801
|
+
|
|
22802
|
+
OpenTelemetry::SDK.configure do |c|
|
|
22803
|
+
c.service_name = _neat_service
|
|
22804
|
+
c.use_all
|
|
22805
|
+
c.add_span_processor(
|
|
22806
|
+
OpenTelemetry::SDK::Trace::Export::BatchSpanProcessor.new(
|
|
22807
|
+
OpenTelemetry::Exporter::OTLP::Exporter.new(endpoint: _neat_endpoint)
|
|
22808
|
+
)
|
|
22809
|
+
)
|
|
22810
|
+
c.add_span_processor(NeatCallSiteSpanProcessor.new(_neat_root))
|
|
22811
|
+
end
|
|
22812
|
+
end
|
|
22813
|
+
`;
|
|
22814
|
+
}
|
|
22815
|
+
async function exists6(p) {
|
|
22816
|
+
try {
|
|
22817
|
+
await import_node_fs42.promises.stat(p);
|
|
22818
|
+
return true;
|
|
22819
|
+
} catch {
|
|
22820
|
+
return false;
|
|
22821
|
+
}
|
|
22822
|
+
}
|
|
22823
|
+
async function detect4(serviceDir) {
|
|
22824
|
+
for (const marker of RUBY_MARKERS) {
|
|
22825
|
+
if (await exists6(import_node_path76.default.join(serviceDir, marker))) return true;
|
|
22826
|
+
}
|
|
22827
|
+
return false;
|
|
22828
|
+
}
|
|
22829
|
+
async function isRailsApp(serviceDir, gemfile) {
|
|
22830
|
+
if (gemfile && /^\s*gem\s+['"]rails['"]/m.test(gemfile)) return true;
|
|
22831
|
+
for (const marker of ["config/application.rb", "config/environment.rb", "bin/rails"]) {
|
|
22832
|
+
if (await exists6(import_node_path76.default.join(serviceDir, marker))) return true;
|
|
22833
|
+
}
|
|
22834
|
+
return false;
|
|
22835
|
+
}
|
|
22836
|
+
function gemPresent(gemfile, name) {
|
|
22837
|
+
const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
22838
|
+
return new RegExp(`^\\s*gem\\s+['"]${escaped}['"]`, "m").test(gemfile);
|
|
22839
|
+
}
|
|
22840
|
+
async function readGemfile(serviceDir) {
|
|
22841
|
+
const file = import_node_path76.default.join(serviceDir, "Gemfile");
|
|
22842
|
+
if (!await exists6(file)) return null;
|
|
22843
|
+
return { file, body: await import_node_fs42.promises.readFile(file, "utf8") };
|
|
22844
|
+
}
|
|
22845
|
+
async function plan4(serviceDir, opts) {
|
|
22846
|
+
const empty = {
|
|
22847
|
+
language: "ruby",
|
|
22848
|
+
serviceDir,
|
|
22849
|
+
dependencyEdits: [],
|
|
22850
|
+
entrypointEdits: [],
|
|
22851
|
+
envEdits: []
|
|
22852
|
+
};
|
|
22853
|
+
const gemfile = await readGemfile(serviceDir);
|
|
22854
|
+
const dependencyEdits = [];
|
|
22855
|
+
if (gemfile) {
|
|
22856
|
+
for (const gem of RUBY_GEMS) {
|
|
22857
|
+
if (!gemPresent(gemfile.body, gem.name)) {
|
|
22858
|
+
dependencyEdits.push({ file: gemfile.file, kind: "add", name: gem.name, version: gem.version });
|
|
22859
|
+
}
|
|
22860
|
+
}
|
|
22861
|
+
}
|
|
22862
|
+
const rails = await isRailsApp(serviceDir, gemfile?.body ?? null);
|
|
22863
|
+
const initializer = import_node_path76.default.join(serviceDir, INITIALIZER_REL);
|
|
22864
|
+
const generatedFiles = [];
|
|
22865
|
+
if (rails && !await exists6(initializer)) {
|
|
22866
|
+
generatedFiles.push({
|
|
22867
|
+
file: initializer,
|
|
22868
|
+
contents: neatOtelRb({ project: opts?.project }),
|
|
22869
|
+
skipIfExists: true
|
|
22870
|
+
});
|
|
22871
|
+
}
|
|
22872
|
+
if (dependencyEdits.length === 0 && generatedFiles.length === 0) {
|
|
22873
|
+
return empty;
|
|
22874
|
+
}
|
|
22875
|
+
const envEdits = [
|
|
22876
|
+
{ file: null, key: "OTEL_EXPORTER_OTLP_ENDPOINT", value: "http://localhost:4318" }
|
|
22877
|
+
];
|
|
22878
|
+
return {
|
|
22879
|
+
language: "ruby",
|
|
22880
|
+
serviceDir,
|
|
22881
|
+
dependencyEdits,
|
|
22882
|
+
entrypointEdits: [],
|
|
22883
|
+
envEdits,
|
|
22884
|
+
...generatedFiles.length > 0 ? { generatedFiles } : {}
|
|
22885
|
+
};
|
|
22886
|
+
}
|
|
22887
|
+
async function writeFileAtomic2(file, contents) {
|
|
22888
|
+
const tmp = `${file}.${process.pid}.${Date.now()}.tmp`;
|
|
22889
|
+
await import_node_fs42.promises.writeFile(tmp, contents, "utf8");
|
|
22890
|
+
await import_node_fs42.promises.rename(tmp, file);
|
|
22891
|
+
}
|
|
22892
|
+
async function applyGemfile(file, edits, original) {
|
|
22893
|
+
const lines = edits.filter((e) => e.kind === "add").map((e) => `gem '${e.name}', '${e.version}'`);
|
|
22894
|
+
const banner = `
|
|
22895
|
+
# ${NEAT_OTEL_STAMP2} \u2014 OpenTelemetry gems added by NEAT
|
|
22896
|
+
`;
|
|
22897
|
+
const trailing = original.endsWith("\n") ? "" : "\n";
|
|
22898
|
+
await writeFileAtomic2(file, `${original}${trailing}${banner}${lines.join("\n")}
|
|
22899
|
+
`);
|
|
22900
|
+
}
|
|
22901
|
+
async function rollback3(serviceDir, language, originals, created) {
|
|
22902
|
+
const restored = [];
|
|
22903
|
+
for (const [file, raw] of originals.entries()) {
|
|
22904
|
+
try {
|
|
22905
|
+
await import_node_fs42.promises.writeFile(file, raw, "utf8");
|
|
22906
|
+
restored.push(file);
|
|
22907
|
+
} catch {
|
|
22908
|
+
}
|
|
22909
|
+
}
|
|
22910
|
+
const removed = [];
|
|
22911
|
+
for (const file of created) {
|
|
22912
|
+
try {
|
|
22913
|
+
await import_node_fs42.promises.rm(file, { force: true });
|
|
22914
|
+
removed.push(file);
|
|
22915
|
+
} catch {
|
|
22916
|
+
}
|
|
22917
|
+
}
|
|
22918
|
+
const body = [
|
|
22919
|
+
"# neat-rollback.patch",
|
|
22920
|
+
"",
|
|
22921
|
+
`# Generated after a partial apply failure in the ${language} installer.`,
|
|
22922
|
+
"# Files listed below were restored to their pre-apply contents.",
|
|
22923
|
+
"",
|
|
22924
|
+
...restored.map((f) => `restored: ${f}`),
|
|
22925
|
+
...removed.map((f) => `removed: ${f}`),
|
|
22926
|
+
""
|
|
22927
|
+
];
|
|
22928
|
+
await import_node_fs42.promises.writeFile(import_node_path76.default.join(serviceDir, "neat-rollback.patch"), body.join("\n"), "utf8");
|
|
22929
|
+
}
|
|
22930
|
+
async function apply4(installPlan) {
|
|
22931
|
+
const { serviceDir } = installPlan;
|
|
22932
|
+
const generatedFiles = installPlan.generatedFiles ?? [];
|
|
22933
|
+
const manifests = new Set(installPlan.dependencyEdits.map((e) => e.file));
|
|
22934
|
+
if (manifests.size === 0 && generatedFiles.length === 0) {
|
|
22935
|
+
return { serviceDir, outcome: "already-instrumented", writtenFiles: [] };
|
|
22936
|
+
}
|
|
22937
|
+
const originals = /* @__PURE__ */ new Map();
|
|
22938
|
+
for (const file of manifests) {
|
|
22939
|
+
try {
|
|
22940
|
+
originals.set(file, await import_node_fs42.promises.readFile(file, "utf8"));
|
|
22941
|
+
} catch {
|
|
22942
|
+
}
|
|
22943
|
+
}
|
|
22944
|
+
const writtenFiles = [];
|
|
22945
|
+
const created = [];
|
|
22946
|
+
try {
|
|
22947
|
+
for (const gf of generatedFiles) {
|
|
22948
|
+
if (await exists6(gf.file)) continue;
|
|
22949
|
+
await import_node_fs42.promises.mkdir(import_node_path76.default.dirname(gf.file), { recursive: true });
|
|
22950
|
+
await writeFileAtomic2(gf.file, gf.contents);
|
|
22951
|
+
writtenFiles.push(gf.file);
|
|
22952
|
+
created.push(gf.file);
|
|
22953
|
+
}
|
|
22954
|
+
for (const file of manifests) {
|
|
22955
|
+
const raw = originals.get(file);
|
|
22956
|
+
if (raw === void 0) throw new Error(`ruby installer: cannot read ${file} during apply`);
|
|
22957
|
+
const edits = installPlan.dependencyEdits.filter((e) => e.file === file);
|
|
22958
|
+
if (edits.length > 0) {
|
|
22959
|
+
await applyGemfile(file, edits, raw);
|
|
22960
|
+
writtenFiles.push(file);
|
|
22961
|
+
}
|
|
22962
|
+
}
|
|
22963
|
+
} catch (err) {
|
|
22964
|
+
await rollback3(serviceDir, installPlan.language, originals, created);
|
|
22965
|
+
throw err;
|
|
22966
|
+
}
|
|
22967
|
+
const wroteManifest = writtenFiles.some((f) => import_node_path76.default.basename(f) === "Gemfile");
|
|
22968
|
+
return {
|
|
22969
|
+
serviceDir,
|
|
22970
|
+
outcome: writtenFiles.length > 0 ? "instrumented" : "already-instrumented",
|
|
22971
|
+
writtenFiles,
|
|
22972
|
+
...wroteManifest ? { followUpInstall: "bundle install" } : {}
|
|
22973
|
+
};
|
|
22974
|
+
}
|
|
22975
|
+
var rubyInstaller = { name: "ruby", detect: detect4, plan: plan4, apply: apply4 };
|
|
22976
|
+
|
|
22977
|
+
// src/installers/php.ts
|
|
22978
|
+
init_cjs_shims();
|
|
22979
|
+
var import_node_fs43 = require("fs");
|
|
22980
|
+
var import_node_path77 = __toESM(require("path"), 1);
|
|
22981
|
+
var PHP_MARKERS = [
|
|
22982
|
+
"composer.json",
|
|
22983
|
+
"composer.lock"
|
|
22984
|
+
];
|
|
22985
|
+
var NEAT_OTEL_FILENAME2 = "neat_otel.php";
|
|
22986
|
+
var NEAT_OTEL_STAMP3 = "neat-otel-init v1";
|
|
22987
|
+
var LARAVEL_PACKAGE = "open-telemetry/opentelemetry-auto-laravel";
|
|
22988
|
+
var PHP_PACKAGES = [
|
|
22989
|
+
{ name: "open-telemetry/sdk", version: "^1.0" },
|
|
22990
|
+
{ name: "open-telemetry/exporter-otlp", version: "^1.0" },
|
|
22991
|
+
{ name: "php-http/guzzle7-adapter", version: "^1.0" },
|
|
22992
|
+
{ name: LARAVEL_PACKAGE, version: "^0.1" }
|
|
22993
|
+
];
|
|
22994
|
+
var PHP_PECL_CAVEAT = "PHP auto-instrumentation requires the `opentelemetry` PECL extension (`pecl install opentelemetry`, then `extension=opentelemetry.so` in php.ini). NEAT cannot install a PECL extension via composer \u2014 until it is loaded no spans are produced. See neat_otel.php and ADR-186.";
|
|
22995
|
+
function neatOtelPhp(opts = {}) {
|
|
22996
|
+
const service = opts.project ?? "php-service";
|
|
22997
|
+
const endpoint2 = opts.project ? `http://localhost:4318/projects/${opts.project}/v1/traces` : "http://localhost:4318/v1/traces";
|
|
22998
|
+
return `<?php
|
|
22999
|
+
// ${NEAT_OTEL_STAMP3} \u2014 generated by NEAT. Safe to re-generate; do not edit.
|
|
23000
|
+
//
|
|
23001
|
+
// REQUIRED SYSTEM STEP \u2014 NEAT CANNOT DO THIS FOR YOU:
|
|
23002
|
+
// PHP OpenTelemetry auto-instrumentation needs the \`opentelemetry\` PECL
|
|
23003
|
+
// extension, a system-level install composer cannot provide:
|
|
23004
|
+
// pecl install opentelemetry
|
|
23005
|
+
// then enable it in your php.ini:
|
|
23006
|
+
// extension=opentelemetry.so
|
|
23007
|
+
// Verify with \`php -m | grep opentelemetry\`. Until the extension is loaded
|
|
23008
|
+
// the Laravel auto-instrumentation hooks never fire and no spans are emitted.
|
|
23009
|
+
//
|
|
23010
|
+
// Wire this file so it runs before the framework boots \u2014 either set
|
|
23011
|
+
// auto_prepend_file = /absolute/path/to/neat_otel.php
|
|
23012
|
+
// in php.ini / .user.ini, or require it at the very top of public/index.php and
|
|
23013
|
+
// artisan. It points the exporter at your NEAT daemon and turns on the SDK
|
|
23014
|
+
// autoloader; the auto-laravel instrumentation then produces route, DB, cache,
|
|
23015
|
+
// and queue spans that fuse onto your extracted routes and Eloquent tables.
|
|
23016
|
+
//
|
|
23017
|
+
// FILE-GRAIN (code.file.path call-site attribution) is a documented follow-up
|
|
23018
|
+
// for PHP \u2014 see ADR-186. Route, table, and service grain land now.
|
|
23019
|
+
|
|
23020
|
+
declare(strict_types=1);
|
|
23021
|
+
|
|
23022
|
+
// Degrade to a no-op when the extension isn't present, so a bare app still
|
|
23023
|
+
// boots (never break the host application).
|
|
23024
|
+
if (!extension_loaded('opentelemetry')) {
|
|
23025
|
+
return;
|
|
23026
|
+
}
|
|
23027
|
+
|
|
23028
|
+
// Point the exporter at NEAT unless the operator already set these. The
|
|
23029
|
+
// endpoint is NEAT's project-scoped traces path (ADR-183).
|
|
23030
|
+
$neat_defaults = [
|
|
23031
|
+
'OTEL_PHP_AUTOLOAD_ENABLED' => 'true',
|
|
23032
|
+
'OTEL_SERVICE_NAME' => '${service}',
|
|
23033
|
+
'OTEL_TRACES_EXPORTER' => 'otlp',
|
|
23034
|
+
'OTEL_EXPORTER_OTLP_PROTOCOL' => 'http/json',
|
|
23035
|
+
'OTEL_EXPORTER_OTLP_TRACES_ENDPOINT' => '${endpoint2}',
|
|
23036
|
+
'OTEL_PROPAGATORS' => 'baggage,tracecontext',
|
|
23037
|
+
];
|
|
23038
|
+
foreach ($neat_defaults as $neat_key => $neat_value) {
|
|
23039
|
+
if (getenv($neat_key) === false && !isset($_SERVER[$neat_key]) && !isset($_ENV[$neat_key])) {
|
|
23040
|
+
putenv($neat_key . '=' . $neat_value);
|
|
23041
|
+
$_SERVER[$neat_key] = $neat_value;
|
|
23042
|
+
$_ENV[$neat_key] = $neat_value;
|
|
23043
|
+
}
|
|
23044
|
+
}
|
|
23045
|
+
`;
|
|
23046
|
+
}
|
|
23047
|
+
async function exists7(p) {
|
|
23048
|
+
try {
|
|
23049
|
+
await import_node_fs43.promises.stat(p);
|
|
23050
|
+
return true;
|
|
23051
|
+
} catch {
|
|
23052
|
+
return false;
|
|
23053
|
+
}
|
|
23054
|
+
}
|
|
23055
|
+
async function detect5(serviceDir) {
|
|
23056
|
+
for (const marker of PHP_MARKERS) {
|
|
23057
|
+
if (await exists7(import_node_path77.default.join(serviceDir, marker))) return true;
|
|
23058
|
+
}
|
|
23059
|
+
return false;
|
|
23060
|
+
}
|
|
23061
|
+
function readComposerObject(body) {
|
|
23062
|
+
let parsed = null;
|
|
23063
|
+
try {
|
|
23064
|
+
parsed = JSON.parse(body);
|
|
23065
|
+
} catch {
|
|
23066
|
+
parsed = null;
|
|
23067
|
+
}
|
|
23068
|
+
const obj = parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
|
|
23069
|
+
const require2 = obj.require && typeof obj.require === "object" && !Array.isArray(obj.require) ? obj.require : {};
|
|
23070
|
+
const requireDev = obj["require-dev"] && typeof obj["require-dev"] === "object" && !Array.isArray(obj["require-dev"]) ? obj["require-dev"] : {};
|
|
23071
|
+
return { require: require2, requireDev };
|
|
23072
|
+
}
|
|
23073
|
+
async function plan5(serviceDir, opts) {
|
|
23074
|
+
const empty = {
|
|
23075
|
+
language: "php",
|
|
23076
|
+
serviceDir,
|
|
23077
|
+
dependencyEdits: [],
|
|
23078
|
+
entrypointEdits: [],
|
|
23079
|
+
envEdits: []
|
|
23080
|
+
};
|
|
23081
|
+
const composerPath = import_node_path77.default.join(serviceDir, "composer.json");
|
|
23082
|
+
const hasComposer = await exists7(composerPath);
|
|
23083
|
+
const dependencyEdits = [];
|
|
23084
|
+
if (hasComposer) {
|
|
23085
|
+
const body = await import_node_fs43.promises.readFile(composerPath, "utf8");
|
|
23086
|
+
const { require: require2, requireDev } = readComposerObject(body);
|
|
23087
|
+
const laravel = "laravel/framework" in require2 || "laravel/framework" in requireDev || await exists7(import_node_path77.default.join(serviceDir, "artisan"));
|
|
23088
|
+
const wanted = laravel ? PHP_PACKAGES : PHP_PACKAGES.filter((p) => p.name !== LARAVEL_PACKAGE);
|
|
23089
|
+
for (const pkg of wanted) {
|
|
23090
|
+
if (!(pkg.name in require2)) {
|
|
23091
|
+
dependencyEdits.push({ file: composerPath, kind: "add", name: pkg.name, version: pkg.version });
|
|
23092
|
+
}
|
|
23093
|
+
}
|
|
23094
|
+
}
|
|
23095
|
+
const bootstrap = import_node_path77.default.join(serviceDir, NEAT_OTEL_FILENAME2);
|
|
23096
|
+
const generatedFiles = [];
|
|
23097
|
+
if (hasComposer && !await exists7(bootstrap)) {
|
|
23098
|
+
generatedFiles.push({ file: bootstrap, contents: neatOtelPhp({ project: opts?.project }), skipIfExists: true });
|
|
23099
|
+
}
|
|
23100
|
+
if (dependencyEdits.length === 0 && generatedFiles.length === 0) {
|
|
23101
|
+
return empty;
|
|
23102
|
+
}
|
|
23103
|
+
const envEdits = [
|
|
23104
|
+
{ file: null, key: "OTEL_PHP_AUTOLOAD_ENABLED", value: "true" },
|
|
23105
|
+
{ file: null, key: "OTEL_EXPORTER_OTLP_ENDPOINT", value: "http://localhost:4318" }
|
|
23106
|
+
];
|
|
23107
|
+
return {
|
|
23108
|
+
language: "php",
|
|
23109
|
+
serviceDir,
|
|
23110
|
+
dependencyEdits,
|
|
23111
|
+
entrypointEdits: [],
|
|
23112
|
+
envEdits,
|
|
23113
|
+
...generatedFiles.length > 0 ? { generatedFiles } : {}
|
|
23114
|
+
};
|
|
23115
|
+
}
|
|
23116
|
+
async function writeFileAtomic3(file, contents) {
|
|
23117
|
+
const tmp = `${file}.${process.pid}.${Date.now()}.tmp`;
|
|
23118
|
+
await import_node_fs43.promises.writeFile(tmp, contents, "utf8");
|
|
23119
|
+
await import_node_fs43.promises.rename(tmp, file);
|
|
23120
|
+
}
|
|
23121
|
+
async function applyComposerJson(file, edits, original) {
|
|
23122
|
+
let parsed;
|
|
23123
|
+
try {
|
|
23124
|
+
parsed = JSON.parse(original);
|
|
23125
|
+
} catch {
|
|
23126
|
+
throw new Error(`php installer: composer.json at ${file} is not valid JSON`);
|
|
23127
|
+
}
|
|
23128
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
23129
|
+
throw new Error(`php installer: composer.json at ${file} is not a JSON object`);
|
|
23130
|
+
}
|
|
23131
|
+
const obj = parsed;
|
|
23132
|
+
const require2 = obj.require && typeof obj.require === "object" && !Array.isArray(obj.require) ? obj.require : {};
|
|
23133
|
+
for (const e of edits) {
|
|
23134
|
+
if (e.kind !== "add") continue;
|
|
23135
|
+
if (!(e.name in require2)) require2[e.name] = e.version;
|
|
23136
|
+
}
|
|
23137
|
+
obj.require = require2;
|
|
23138
|
+
await writeFileAtomic3(file, JSON.stringify(obj, null, 2) + "\n");
|
|
23139
|
+
}
|
|
23140
|
+
async function rollback4(serviceDir, language, originals, created) {
|
|
23141
|
+
const restored = [];
|
|
23142
|
+
for (const [file, raw] of originals.entries()) {
|
|
23143
|
+
try {
|
|
23144
|
+
await import_node_fs43.promises.writeFile(file, raw, "utf8");
|
|
23145
|
+
restored.push(file);
|
|
23146
|
+
} catch {
|
|
23147
|
+
}
|
|
23148
|
+
}
|
|
23149
|
+
const removed = [];
|
|
23150
|
+
for (const file of created) {
|
|
23151
|
+
try {
|
|
23152
|
+
await import_node_fs43.promises.rm(file, { force: true });
|
|
23153
|
+
removed.push(file);
|
|
23154
|
+
} catch {
|
|
23155
|
+
}
|
|
23156
|
+
}
|
|
23157
|
+
const body = [
|
|
23158
|
+
"# neat-rollback.patch",
|
|
23159
|
+
"",
|
|
23160
|
+
`# Generated after a partial apply failure in the ${language} installer.`,
|
|
23161
|
+
"# Files listed below were restored to their pre-apply contents.",
|
|
23162
|
+
"",
|
|
23163
|
+
...restored.map((f) => `restored: ${f}`),
|
|
23164
|
+
...removed.map((f) => `removed: ${f}`),
|
|
23165
|
+
""
|
|
23166
|
+
];
|
|
23167
|
+
await import_node_fs43.promises.writeFile(import_node_path77.default.join(serviceDir, "neat-rollback.patch"), body.join("\n"), "utf8");
|
|
23168
|
+
}
|
|
23169
|
+
async function apply5(installPlan) {
|
|
23170
|
+
const { serviceDir } = installPlan;
|
|
23171
|
+
const generatedFiles = installPlan.generatedFiles ?? [];
|
|
23172
|
+
const manifests = new Set(installPlan.dependencyEdits.map((e) => e.file));
|
|
23173
|
+
if (manifests.size === 0 && generatedFiles.length === 0) {
|
|
23174
|
+
return { serviceDir, outcome: "already-instrumented", writtenFiles: [], reason: PHP_PECL_CAVEAT };
|
|
23175
|
+
}
|
|
23176
|
+
const originals = /* @__PURE__ */ new Map();
|
|
23177
|
+
for (const file of manifests) {
|
|
23178
|
+
try {
|
|
23179
|
+
originals.set(file, await import_node_fs43.promises.readFile(file, "utf8"));
|
|
23180
|
+
} catch {
|
|
23181
|
+
}
|
|
23182
|
+
}
|
|
23183
|
+
const writtenFiles = [];
|
|
23184
|
+
const created = [];
|
|
23185
|
+
try {
|
|
23186
|
+
for (const gf of generatedFiles) {
|
|
23187
|
+
if (await exists7(gf.file)) continue;
|
|
23188
|
+
await import_node_fs43.promises.mkdir(import_node_path77.default.dirname(gf.file), { recursive: true });
|
|
23189
|
+
await writeFileAtomic3(gf.file, gf.contents);
|
|
23190
|
+
writtenFiles.push(gf.file);
|
|
23191
|
+
created.push(gf.file);
|
|
23192
|
+
}
|
|
23193
|
+
for (const file of manifests) {
|
|
23194
|
+
const raw = originals.get(file);
|
|
23195
|
+
if (raw === void 0) throw new Error(`php installer: cannot read ${file} during apply`);
|
|
23196
|
+
const edits = installPlan.dependencyEdits.filter((e) => e.file === file);
|
|
23197
|
+
if (edits.length > 0) {
|
|
23198
|
+
await applyComposerJson(file, edits, raw);
|
|
23199
|
+
writtenFiles.push(file);
|
|
23200
|
+
}
|
|
23201
|
+
}
|
|
23202
|
+
} catch (err) {
|
|
23203
|
+
await rollback4(serviceDir, installPlan.language, originals, created);
|
|
23204
|
+
throw err;
|
|
23205
|
+
}
|
|
23206
|
+
if (writtenFiles.length > 0) {
|
|
23207
|
+
console.warn(`neat: PHP instrumentation staged in ${import_node_path77.default.basename(serviceDir)}, but a system step remains:
|
|
23208
|
+
${PHP_PECL_CAVEAT}`);
|
|
23209
|
+
}
|
|
23210
|
+
const wroteManifest = writtenFiles.some((f) => import_node_path77.default.basename(f) === "composer.json");
|
|
23211
|
+
return {
|
|
23212
|
+
serviceDir,
|
|
23213
|
+
outcome: writtenFiles.length > 0 ? "instrumented" : "already-instrumented",
|
|
23214
|
+
writtenFiles,
|
|
23215
|
+
reason: PHP_PECL_CAVEAT,
|
|
23216
|
+
...wroteManifest ? { followUpInstall: "composer install" } : {}
|
|
23217
|
+
};
|
|
23218
|
+
}
|
|
23219
|
+
var phpInstaller = { name: "php", detect: detect5, plan: plan5, apply: apply5 };
|
|
23220
|
+
|
|
22162
23221
|
// src/installers/shared.ts
|
|
22163
23222
|
init_cjs_shims();
|
|
22164
|
-
function isEmptyPlan(
|
|
22165
|
-
return
|
|
23223
|
+
function isEmptyPlan(plan6) {
|
|
23224
|
+
return plan6.dependencyEdits.length === 0 && plan6.entrypointEdits.length === 0 && plan6.envEdits.length === 0 && (plan6.generatedFiles?.length ?? 0) === 0 && plan6.nextConfigEdit === void 0;
|
|
22166
23225
|
}
|
|
22167
23226
|
|
|
22168
23227
|
// src/installers/index.ts
|
|
@@ -22174,9 +23233,16 @@ var FORBIDDEN_LOCKFILES = /* @__PURE__ */ new Set([
|
|
|
22174
23233
|
"Pipfile.lock",
|
|
22175
23234
|
"Gemfile.lock",
|
|
22176
23235
|
"Cargo.lock",
|
|
22177
|
-
"go.sum"
|
|
23236
|
+
"go.sum",
|
|
23237
|
+
"composer.lock"
|
|
22178
23238
|
]);
|
|
22179
|
-
var INSTALLERS = [
|
|
23239
|
+
var INSTALLERS = [
|
|
23240
|
+
javascriptInstaller,
|
|
23241
|
+
pythonInstaller,
|
|
23242
|
+
goInstaller,
|
|
23243
|
+
rubyInstaller,
|
|
23244
|
+
phpInstaller
|
|
23245
|
+
];
|
|
22180
23246
|
async function pickInstaller(serviceDir) {
|
|
22181
23247
|
for (const inst of INSTALLERS) {
|
|
22182
23248
|
if (await inst.detect(serviceDir)) return inst;
|
|
@@ -22191,7 +23257,7 @@ function renderPatch(sections) {
|
|
|
22191
23257
|
"No SDK installers matched the discovered services. Two reasons this",
|
|
22192
23258
|
"normally happens:",
|
|
22193
23259
|
" - the project uses a language NEAT does not yet instrument",
|
|
22194
|
-
" (Java /
|
|
23260
|
+
" (Java / .NET / Rust are out of scope per ADR-047);",
|
|
22195
23261
|
" - the SDK is already installed, so the installer returned an empty",
|
|
22196
23262
|
" plan.",
|
|
22197
23263
|
"",
|
|
@@ -22201,22 +23267,22 @@ function renderPatch(sections) {
|
|
|
22201
23267
|
}
|
|
22202
23268
|
const lines = ["# neat install plan", ""];
|
|
22203
23269
|
for (const section of sections) {
|
|
22204
|
-
const { installer, plan:
|
|
22205
|
-
lines.push(`## ${installer} (${
|
|
23270
|
+
const { installer, plan: plan6 } = section;
|
|
23271
|
+
lines.push(`## ${installer} (${plan6.language}) \u2014 ${plan6.serviceDir}`);
|
|
22206
23272
|
lines.push("");
|
|
22207
|
-
if (
|
|
23273
|
+
if (plan6.libOnly) {
|
|
22208
23274
|
lines.push("### skipped \u2014 no resolvable entry point (lib-only)");
|
|
22209
23275
|
lines.push("");
|
|
22210
23276
|
continue;
|
|
22211
23277
|
}
|
|
22212
|
-
if (
|
|
22213
|
-
lines.push(`entry: ${
|
|
23278
|
+
if (plan6.entryFile) {
|
|
23279
|
+
lines.push(`entry: ${plan6.entryFile}`);
|
|
22214
23280
|
lines.push("");
|
|
22215
23281
|
}
|
|
22216
|
-
if (
|
|
23282
|
+
if (plan6.dependencyEdits.length > 0) {
|
|
22217
23283
|
lines.push("### dependencies");
|
|
22218
23284
|
const byFile = /* @__PURE__ */ new Map();
|
|
22219
|
-
for (const dep of
|
|
23285
|
+
for (const dep of plan6.dependencyEdits) {
|
|
22220
23286
|
const base = dep.file.split(/[\\/]/).pop() ?? dep.file;
|
|
22221
23287
|
if (FORBIDDEN_LOCKFILES.has(base)) {
|
|
22222
23288
|
throw new Error(
|
|
@@ -22235,9 +23301,9 @@ function renderPatch(sections) {
|
|
|
22235
23301
|
}
|
|
22236
23302
|
lines.push("");
|
|
22237
23303
|
}
|
|
22238
|
-
if (
|
|
23304
|
+
if (plan6.generatedFiles && plan6.generatedFiles.length > 0) {
|
|
22239
23305
|
lines.push("### generated files");
|
|
22240
|
-
for (const gen of
|
|
23306
|
+
for (const gen of plan6.generatedFiles) {
|
|
22241
23307
|
lines.push(`--- (new file) ${gen.file}`);
|
|
22242
23308
|
for (const ln of gen.contents.split(/\r?\n/)) {
|
|
22243
23309
|
lines.push(`+ ${ln}`);
|
|
@@ -22245,26 +23311,26 @@ function renderPatch(sections) {
|
|
|
22245
23311
|
}
|
|
22246
23312
|
lines.push("");
|
|
22247
23313
|
}
|
|
22248
|
-
if (
|
|
23314
|
+
if (plan6.entrypointEdits.length > 0) {
|
|
22249
23315
|
lines.push("### entry-point injection");
|
|
22250
|
-
for (const e of
|
|
23316
|
+
for (const e of plan6.entrypointEdits) {
|
|
22251
23317
|
lines.push(`--- ${e.file}`);
|
|
22252
23318
|
lines.push(`+ ${e.after}`);
|
|
22253
23319
|
lines.push(` ${e.before}`);
|
|
22254
23320
|
}
|
|
22255
23321
|
lines.push("");
|
|
22256
23322
|
}
|
|
22257
|
-
if (
|
|
23323
|
+
if (plan6.envEdits.length > 0) {
|
|
22258
23324
|
lines.push("### env (written to <package-dir>/.env.neat)");
|
|
22259
|
-
for (const env of
|
|
23325
|
+
for (const env of plan6.envEdits) {
|
|
22260
23326
|
lines.push(`- ${env.key}=${env.value}`);
|
|
22261
23327
|
}
|
|
22262
23328
|
lines.push("");
|
|
22263
23329
|
}
|
|
22264
|
-
if (
|
|
23330
|
+
if (plan6.nextConfigEdit) {
|
|
22265
23331
|
lines.push("### next.config (framework flag)");
|
|
22266
|
-
lines.push(`--- ${
|
|
22267
|
-
lines.push(`+ experimental: { instrumentationHook: true }, // ${
|
|
23332
|
+
lines.push(`--- ${plan6.nextConfigEdit.file}`);
|
|
23333
|
+
lines.push(`+ experimental: { instrumentationHook: true }, // ${plan6.nextConfigEdit.reason}`);
|
|
22268
23334
|
lines.push("");
|
|
22269
23335
|
}
|
|
22270
23336
|
}
|
|
@@ -22273,10 +23339,10 @@ function renderPatch(sections) {
|
|
|
22273
23339
|
|
|
22274
23340
|
// src/orchestrator.ts
|
|
22275
23341
|
init_cjs_shims();
|
|
22276
|
-
var
|
|
23342
|
+
var import_node_fs44 = require("fs");
|
|
22277
23343
|
var import_node_http = __toESM(require("http"), 1);
|
|
22278
23344
|
var import_node_net = __toESM(require("net"), 1);
|
|
22279
|
-
var
|
|
23345
|
+
var import_node_path78 = __toESM(require("path"), 1);
|
|
22280
23346
|
var import_node_url4 = require("url");
|
|
22281
23347
|
var import_node_child_process3 = require("child_process");
|
|
22282
23348
|
var import_node_readline = __toESM(require("readline"), 1);
|
|
@@ -22286,7 +23352,7 @@ async function extractAndPersist(opts) {
|
|
|
22286
23352
|
const graphKey = opts.projectExplicit ? opts.project : DEFAULT_PROJECT;
|
|
22287
23353
|
resetGraph(graphKey);
|
|
22288
23354
|
const graph = getGraph(graphKey);
|
|
22289
|
-
const projectPaths = pathsForProject(graphKey,
|
|
23355
|
+
const projectPaths = pathsForProject(graphKey, import_node_path78.default.join(opts.scanPath, "neat-out"));
|
|
22290
23356
|
const extraction = await extractFromDirectory(graph, opts.scanPath, {
|
|
22291
23357
|
errorsPath: projectPaths.errorsPath
|
|
22292
23358
|
});
|
|
@@ -22317,28 +23383,34 @@ async function applyInstallersOver(services, project, options = {}) {
|
|
|
22317
23383
|
let cloudflareWorkers = 0;
|
|
22318
23384
|
let electron = 0;
|
|
22319
23385
|
const installPlans = /* @__PURE__ */ new Map();
|
|
23386
|
+
const dependencyInstructions = /* @__PURE__ */ new Map();
|
|
22320
23387
|
for (const svc of services) {
|
|
22321
23388
|
const installer = await pickInstaller(svc.dir);
|
|
22322
23389
|
if (!installer) continue;
|
|
22323
|
-
const
|
|
22324
|
-
if (isEmptyPlan(
|
|
23390
|
+
const plan6 = await installer.plan(svc.dir, { project });
|
|
23391
|
+
if (isEmptyPlan(plan6) && !plan6.libOnly && plan6.runtimeKind === void 0) {
|
|
22325
23392
|
already++;
|
|
22326
23393
|
continue;
|
|
22327
23394
|
}
|
|
22328
|
-
const outcome = await installer.apply(
|
|
23395
|
+
const outcome = await installer.apply(plan6);
|
|
22329
23396
|
if (outcome.outcome === "instrumented") {
|
|
22330
23397
|
instrumented++;
|
|
22331
|
-
if (
|
|
22332
|
-
const
|
|
22333
|
-
|
|
22334
|
-
|
|
23398
|
+
if (plan6.dependencyEdits.length > 0) {
|
|
23399
|
+
const manifest = import_node_path78.default.basename(plan6.dependencyEdits[0].file);
|
|
23400
|
+
if (manifest === "package.json") {
|
|
23401
|
+
const cmd = await resolveManager(svc.dir);
|
|
23402
|
+
const key = `${cmd.pm}:${cmd.cwd}`;
|
|
23403
|
+
if (!installPlans.has(key)) installPlans.set(key, cmd);
|
|
23404
|
+
} else if (outcome.followUpInstall) {
|
|
23405
|
+
dependencyInstructions.set(svc.dir, outcome.followUpInstall);
|
|
23406
|
+
}
|
|
22335
23407
|
}
|
|
22336
23408
|
} else if (outcome.outcome === "already-instrumented") already++;
|
|
22337
23409
|
else if (outcome.outcome === "lib-only") {
|
|
22338
23410
|
libOnly++;
|
|
22339
23411
|
const appDeps = svc.pkg ? appFrameworkDependencies(svc.pkg) : [];
|
|
22340
23412
|
if (appDeps.length > 0) {
|
|
22341
|
-
const svcName =
|
|
23413
|
+
const svcName = import_node_path78.default.basename(svc.dir);
|
|
22342
23414
|
const list = appDeps.join(", ");
|
|
22343
23415
|
console.warn(
|
|
22344
23416
|
`neat: runtime layer won't engage for ${svcName}: no entry point found.
|
|
@@ -22351,7 +23423,7 @@ async function applyInstallersOver(services, project, options = {}) {
|
|
|
22351
23423
|
console.log(`skipping ${svc.dir}: browser bundle; browser-OTel support lands in a future release.`);
|
|
22352
23424
|
} else if (outcome.outcome === "react-native") {
|
|
22353
23425
|
reactNative++;
|
|
22354
|
-
const svcName =
|
|
23426
|
+
const svcName = import_node_path78.default.basename(svc.dir);
|
|
22355
23427
|
console.log(
|
|
22356
23428
|
`neat: ${svc.dir} detected as React Native / Expo
|
|
22357
23429
|
The installer doesn't cover this runtime deterministically.
|
|
@@ -22362,7 +23434,7 @@ async function applyInstallersOver(services, project, options = {}) {
|
|
|
22362
23434
|
);
|
|
22363
23435
|
} else if (outcome.outcome === "bun") {
|
|
22364
23436
|
bun++;
|
|
22365
|
-
const svcName =
|
|
23437
|
+
const svcName = import_node_path78.default.basename(svc.dir);
|
|
22366
23438
|
console.log(
|
|
22367
23439
|
`neat: ${svc.dir} detected as Bun
|
|
22368
23440
|
The installer doesn't cover this runtime deterministically.
|
|
@@ -22373,7 +23445,7 @@ async function applyInstallersOver(services, project, options = {}) {
|
|
|
22373
23445
|
);
|
|
22374
23446
|
} else if (outcome.outcome === "deno") {
|
|
22375
23447
|
deno++;
|
|
22376
|
-
const svcName =
|
|
23448
|
+
const svcName = import_node_path78.default.basename(svc.dir);
|
|
22377
23449
|
console.log(
|
|
22378
23450
|
`neat: ${svc.dir} detected as Deno
|
|
22379
23451
|
The installer doesn't cover this runtime deterministically.
|
|
@@ -22384,7 +23456,7 @@ async function applyInstallersOver(services, project, options = {}) {
|
|
|
22384
23456
|
);
|
|
22385
23457
|
} else if (outcome.outcome === "cloudflare-workers") {
|
|
22386
23458
|
cloudflareWorkers++;
|
|
22387
|
-
const svcName =
|
|
23459
|
+
const svcName = import_node_path78.default.basename(svc.dir);
|
|
22388
23460
|
console.log(
|
|
22389
23461
|
`neat: ${svc.dir} detected as Cloudflare Workers
|
|
22390
23462
|
The installer doesn't cover this runtime deterministically.
|
|
@@ -22395,7 +23467,7 @@ async function applyInstallersOver(services, project, options = {}) {
|
|
|
22395
23467
|
);
|
|
22396
23468
|
} else if (outcome.outcome === "electron") {
|
|
22397
23469
|
electron++;
|
|
22398
|
-
const svcName =
|
|
23470
|
+
const svcName = import_node_path78.default.basename(svc.dir);
|
|
22399
23471
|
console.log(
|
|
22400
23472
|
`neat: ${svc.dir} detected as Electron
|
|
22401
23473
|
The installer doesn't cover this runtime deterministically.
|
|
@@ -22408,7 +23480,7 @@ async function applyInstallersOver(services, project, options = {}) {
|
|
|
22408
23480
|
if (svc.pkg && (outcome.outcome === "instrumented" || outcome.outcome === "already-instrumented")) {
|
|
22409
23481
|
const gaps = uninstrumentedLibraries(svc.pkg);
|
|
22410
23482
|
if (gaps.length > 0) {
|
|
22411
|
-
const svcName =
|
|
23483
|
+
const svcName = import_node_path78.default.basename(svc.dir);
|
|
22412
23484
|
const list = gaps.join(", ");
|
|
22413
23485
|
const subject = gaps.length === 1 ? "this library" : "these libraries";
|
|
22414
23486
|
const aux = gaps.length === 1 ? "isn't" : "aren't";
|
|
@@ -22436,6 +23508,11 @@ async function applyInstallersOver(services, project, options = {}) {
|
|
|
22436
23508
|
}
|
|
22437
23509
|
}
|
|
22438
23510
|
}
|
|
23511
|
+
for (const [dir, command] of dependencyInstructions) {
|
|
23512
|
+
console.log(
|
|
23513
|
+
`neat: dependencies staged in ${dir}; run \`${command}\` to install them \u2014 NEAT does not run it for you.`
|
|
23514
|
+
);
|
|
23515
|
+
}
|
|
22439
23516
|
return {
|
|
22440
23517
|
instrumented,
|
|
22441
23518
|
alreadyInstrumented: already,
|
|
@@ -22446,7 +23523,8 @@ async function applyInstallersOver(services, project, options = {}) {
|
|
|
22446
23523
|
deno,
|
|
22447
23524
|
cloudflareWorkers,
|
|
22448
23525
|
electron,
|
|
22449
|
-
packageManagerInstalls
|
|
23526
|
+
packageManagerInstalls,
|
|
23527
|
+
dependencyInstructions: [...dependencyInstructions].map(([dir, command]) => ({ dir, command }))
|
|
22450
23528
|
};
|
|
22451
23529
|
}
|
|
22452
23530
|
async function promptYesNo(question) {
|
|
@@ -22614,24 +23692,24 @@ async function persistedPortsFor(scanPath) {
|
|
|
22614
23692
|
return { rest: record.ports.rest, otlp: record.ports.otlp, web: record.ports.web };
|
|
22615
23693
|
}
|
|
22616
23694
|
async function acquireSpawnLock(scanPath) {
|
|
22617
|
-
const lockPath =
|
|
22618
|
-
await
|
|
23695
|
+
const lockPath = import_node_path78.default.join(scanPath, "neat-out", "daemon.spawn.lock");
|
|
23696
|
+
await import_node_fs44.promises.mkdir(import_node_path78.default.dirname(lockPath), { recursive: true });
|
|
22619
23697
|
const STALE_LOCK_MS = 6e4;
|
|
22620
23698
|
try {
|
|
22621
|
-
const fd = await
|
|
23699
|
+
const fd = await import_node_fs44.promises.open(lockPath, "wx");
|
|
22622
23700
|
await fd.writeFile(`${process.pid}
|
|
22623
23701
|
`, "utf8");
|
|
22624
23702
|
await fd.close();
|
|
22625
23703
|
return async () => {
|
|
22626
|
-
await
|
|
23704
|
+
await import_node_fs44.promises.unlink(lockPath).catch(() => {
|
|
22627
23705
|
});
|
|
22628
23706
|
};
|
|
22629
23707
|
} catch (err) {
|
|
22630
23708
|
if (err.code !== "EEXIST") return null;
|
|
22631
23709
|
try {
|
|
22632
|
-
const stat = await
|
|
23710
|
+
const stat = await import_node_fs44.promises.stat(lockPath);
|
|
22633
23711
|
if (Date.now() - stat.mtimeMs > STALE_LOCK_MS) {
|
|
22634
|
-
await
|
|
23712
|
+
await import_node_fs44.promises.unlink(lockPath).catch(() => {
|
|
22635
23713
|
});
|
|
22636
23714
|
return acquireSpawnLock(scanPath);
|
|
22637
23715
|
}
|
|
@@ -22660,13 +23738,13 @@ async function healthIsForProject(restPort, project) {
|
|
|
22660
23738
|
return false;
|
|
22661
23739
|
}
|
|
22662
23740
|
function daemonLogPath(projectPath3) {
|
|
22663
|
-
return
|
|
23741
|
+
return import_node_path78.default.join(projectPath3, "neat-out", "daemon.log");
|
|
22664
23742
|
}
|
|
22665
23743
|
function spawnDaemonDetached(spec) {
|
|
22666
|
-
const here =
|
|
23744
|
+
const here = import_node_path78.default.dirname((0, import_node_url4.fileURLToPath)(importMetaUrl));
|
|
22667
23745
|
const candidates = [
|
|
22668
|
-
|
|
22669
|
-
|
|
23746
|
+
import_node_path78.default.join(here, "neatd.cjs"),
|
|
23747
|
+
import_node_path78.default.join(here, "neatd.js")
|
|
22670
23748
|
];
|
|
22671
23749
|
let entry2 = null;
|
|
22672
23750
|
const fsSync = require("fs");
|
|
@@ -22696,7 +23774,7 @@ function spawnDaemonDetached(spec) {
|
|
|
22696
23774
|
let logFd = null;
|
|
22697
23775
|
if (spec) {
|
|
22698
23776
|
const logPath = daemonLogPath(spec.projectPath);
|
|
22699
|
-
fsSync.mkdirSync(
|
|
23777
|
+
fsSync.mkdirSync(import_node_path78.default.dirname(logPath), { recursive: true });
|
|
22700
23778
|
logFd = fsSync.openSync(logPath, "a");
|
|
22701
23779
|
}
|
|
22702
23780
|
const child = (0, import_node_child_process3.spawn)(process.execPath, [entry2, "start"], {
|
|
@@ -22735,7 +23813,7 @@ async function runOrchestrator(opts) {
|
|
|
22735
23813
|
browser: "skipped"
|
|
22736
23814
|
}
|
|
22737
23815
|
};
|
|
22738
|
-
const stat = await
|
|
23816
|
+
const stat = await import_node_fs44.promises.stat(opts.scanPath).catch(() => null);
|
|
22739
23817
|
if (!stat || !stat.isDirectory()) {
|
|
22740
23818
|
console.error(`neat: ${opts.scanPath} is not a directory`);
|
|
22741
23819
|
result.exitCode = 2;
|
|
@@ -22895,7 +23973,7 @@ async function runOrchestrator(opts) {
|
|
|
22895
23973
|
result.steps.browser = openBrowser(dashboardUrl);
|
|
22896
23974
|
}
|
|
22897
23975
|
const daemonRunning = result.steps.daemon === "spawned" || result.steps.daemon === "already-running";
|
|
22898
|
-
const daemonLog = daemonRunning ?
|
|
23976
|
+
const daemonLog = daemonRunning ? import_node_path78.default.relative(opts.scanPath, daemonLogPath(opts.scanPath)) : null;
|
|
22899
23977
|
printSummary(result, graph, dashboardUrl, daemonLog);
|
|
22900
23978
|
return result;
|
|
22901
23979
|
}
|
|
@@ -23354,27 +24432,27 @@ async function runConnectorCommand(rawArgs, deps = {}) {
|
|
|
23354
24432
|
|
|
23355
24433
|
// src/hooks-cli.ts
|
|
23356
24434
|
init_cjs_shims();
|
|
23357
|
-
var
|
|
24435
|
+
var import_node_path79 = __toESM(require("path"), 1);
|
|
23358
24436
|
var import_node_os5 = __toESM(require("os"), 1);
|
|
23359
|
-
var
|
|
24437
|
+
var import_node_fs45 = require("fs");
|
|
23360
24438
|
var import_node_url5 = require("url");
|
|
23361
24439
|
var HOOK_FILENAME = "neat-search-nudge.mjs";
|
|
23362
24440
|
var GUIDE_FILENAME = "GRAPH_FIRST.md";
|
|
23363
24441
|
var GUIDE_INSTALL_NAME = "neat-graph-first.md";
|
|
23364
24442
|
var HOOK_MATCHER = "Grep|Glob|Bash";
|
|
23365
24443
|
function moduleDir() {
|
|
23366
|
-
return typeof __dirname !== "undefined" ? __dirname :
|
|
24444
|
+
return typeof __dirname !== "undefined" ? __dirname : import_node_path79.default.dirname((0, import_node_url5.fileURLToPath)(importMetaUrl));
|
|
23367
24445
|
}
|
|
23368
24446
|
async function readSkillAsset(rel) {
|
|
23369
24447
|
const here = moduleDir();
|
|
23370
24448
|
const candidates = [
|
|
23371
|
-
|
|
23372
|
-
|
|
23373
|
-
|
|
24449
|
+
import_node_path79.default.resolve(here, "../../claude-skill", rel),
|
|
24450
|
+
import_node_path79.default.resolve(here, "../../../claude-skill", rel),
|
|
24451
|
+
import_node_path79.default.resolve(here, "../claude-skill", rel)
|
|
23374
24452
|
];
|
|
23375
24453
|
for (const candidate of candidates) {
|
|
23376
24454
|
try {
|
|
23377
|
-
return await
|
|
24455
|
+
return await import_node_fs45.promises.readFile(candidate, "utf8");
|
|
23378
24456
|
} catch {
|
|
23379
24457
|
}
|
|
23380
24458
|
}
|
|
@@ -23384,17 +24462,17 @@ async function readSkillAsset(rel) {
|
|
|
23384
24462
|
}
|
|
23385
24463
|
function neatHome3() {
|
|
23386
24464
|
const override = process.env.NEAT_HOME;
|
|
23387
|
-
if (override && override.length > 0) return
|
|
23388
|
-
return
|
|
24465
|
+
if (override && override.length > 0) return import_node_path79.default.resolve(override);
|
|
24466
|
+
return import_node_path79.default.join(import_node_os5.default.homedir(), ".neat");
|
|
23389
24467
|
}
|
|
23390
24468
|
function claudeSettingsPath() {
|
|
23391
24469
|
const override = process.env.NEAT_CLAUDE_SETTINGS;
|
|
23392
|
-
if (override && override.length > 0) return
|
|
24470
|
+
if (override && override.length > 0) return import_node_path79.default.resolve(override);
|
|
23393
24471
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? import_node_os5.default.homedir();
|
|
23394
|
-
return
|
|
24472
|
+
return import_node_path79.default.join(home, ".claude", "settings.json");
|
|
23395
24473
|
}
|
|
23396
24474
|
function installedHookPath() {
|
|
23397
|
-
return
|
|
24475
|
+
return import_node_path79.default.join(neatHome3(), "hooks", HOOK_FILENAME);
|
|
23398
24476
|
}
|
|
23399
24477
|
function isNeatSearchEntry(entry2) {
|
|
23400
24478
|
return (entry2.hooks ?? []).some(
|
|
@@ -23427,14 +24505,14 @@ async function runHooks(opts) {
|
|
|
23427
24505
|
const hookScript = await readSkillAsset(`hooks/${HOOK_FILENAME}`);
|
|
23428
24506
|
const guide = await readSkillAsset(GUIDE_FILENAME);
|
|
23429
24507
|
const scriptPath = installedHookPath();
|
|
23430
|
-
await
|
|
23431
|
-
await
|
|
23432
|
-
const guidePath =
|
|
23433
|
-
await
|
|
24508
|
+
await import_node_fs45.promises.mkdir(import_node_path79.default.dirname(scriptPath), { recursive: true });
|
|
24509
|
+
await import_node_fs45.promises.writeFile(scriptPath, hookScript, { mode: 493 });
|
|
24510
|
+
const guidePath = import_node_path79.default.join(neatHome3(), GUIDE_INSTALL_NAME);
|
|
24511
|
+
await import_node_fs45.promises.writeFile(guidePath, guide, "utf8");
|
|
23434
24512
|
const settingsFile = claudeSettingsPath();
|
|
23435
24513
|
let settings = {};
|
|
23436
24514
|
try {
|
|
23437
|
-
settings = JSON.parse(await
|
|
24515
|
+
settings = JSON.parse(await import_node_fs45.promises.readFile(settingsFile, "utf8"));
|
|
23438
24516
|
} catch (err) {
|
|
23439
24517
|
if (err.code !== "ENOENT") {
|
|
23440
24518
|
console.error(
|
|
@@ -23456,8 +24534,8 @@ async function runHooks(opts) {
|
|
|
23456
24534
|
...settings,
|
|
23457
24535
|
hooks: { ...hooks, PreToolUse: preToolUse }
|
|
23458
24536
|
};
|
|
23459
|
-
await
|
|
23460
|
-
await
|
|
24537
|
+
await import_node_fs45.promises.mkdir(import_node_path79.default.dirname(settingsFile), { recursive: true });
|
|
24538
|
+
await import_node_fs45.promises.writeFile(settingsFile, JSON.stringify(merged, null, 2) + "\n", "utf8");
|
|
23461
24539
|
console.log(`neat hooks: installed the search-nudge hook`);
|
|
23462
24540
|
console.log(` script: ${scriptPath}`);
|
|
23463
24541
|
console.log(` settings: ${settingsFile} (PreToolUse \u2192 ${HOOK_MATCHER})`);
|
|
@@ -23528,9 +24606,9 @@ async function runHooksCommand(args) {
|
|
|
23528
24606
|
|
|
23529
24607
|
// src/codex-cli.ts
|
|
23530
24608
|
init_cjs_shims();
|
|
23531
|
-
var
|
|
24609
|
+
var import_node_path80 = __toESM(require("path"), 1);
|
|
23532
24610
|
var import_node_os6 = __toESM(require("os"), 1);
|
|
23533
|
-
var
|
|
24611
|
+
var import_node_fs46 = require("fs");
|
|
23534
24612
|
var import_node_util = require("util");
|
|
23535
24613
|
var import_smol_toml5 = require("smol-toml");
|
|
23536
24614
|
var CODEX_MCP_SERVER = {
|
|
@@ -23548,14 +24626,14 @@ var NEAT_GRAPH_FIRST_START = "<!-- neat:graph-first -->";
|
|
|
23548
24626
|
var NEAT_GRAPH_FIRST_END = "<!-- /neat:graph-first -->";
|
|
23549
24627
|
function codexConfigPath() {
|
|
23550
24628
|
const override = process.env.NEAT_CODEX_CONFIG;
|
|
23551
|
-
if (override && override.length > 0) return
|
|
24629
|
+
if (override && override.length > 0) return import_node_path80.default.resolve(override);
|
|
23552
24630
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? import_node_os6.default.homedir();
|
|
23553
|
-
return
|
|
24631
|
+
return import_node_path80.default.join(home, ".codex", "config.toml");
|
|
23554
24632
|
}
|
|
23555
24633
|
function agentsFilePath() {
|
|
23556
24634
|
const override = process.env.NEAT_CODEX_AGENTS;
|
|
23557
|
-
if (override && override.length > 0) return
|
|
23558
|
-
return
|
|
24635
|
+
if (override && override.length > 0) return import_node_path80.default.resolve(override);
|
|
24636
|
+
return import_node_path80.default.join(process.cwd(), "AGENTS.md");
|
|
23559
24637
|
}
|
|
23560
24638
|
function isTableHeader(line) {
|
|
23561
24639
|
return /^\s*\[\[?[^\]]+\]\]?\s*$/.test(line);
|
|
@@ -23689,7 +24767,7 @@ async function runCodex(opts) {
|
|
|
23689
24767
|
const agentsPath = agentsFilePath();
|
|
23690
24768
|
let configRaw = "";
|
|
23691
24769
|
try {
|
|
23692
|
-
configRaw = await
|
|
24770
|
+
configRaw = await import_node_fs46.promises.readFile(configPath, "utf8");
|
|
23693
24771
|
} catch (err) {
|
|
23694
24772
|
if (err.code !== "ENOENT") {
|
|
23695
24773
|
console.error(`neat codex: failed to read ${configPath} \u2014 ${err.message}`);
|
|
@@ -23698,7 +24776,7 @@ async function runCodex(opts) {
|
|
|
23698
24776
|
}
|
|
23699
24777
|
let agentsRaw = "";
|
|
23700
24778
|
try {
|
|
23701
|
-
agentsRaw = await
|
|
24779
|
+
agentsRaw = await import_node_fs46.promises.readFile(agentsPath, "utf8");
|
|
23702
24780
|
} catch (err) {
|
|
23703
24781
|
if (err.code !== "ENOENT") {
|
|
23704
24782
|
console.error(`neat codex: failed to read ${agentsPath} \u2014 ${err.message}`);
|
|
@@ -23738,15 +24816,15 @@ async function runCodex(opts) {
|
|
|
23738
24816
|
return { exitCode: 0 };
|
|
23739
24817
|
}
|
|
23740
24818
|
if (config.changed) {
|
|
23741
|
-
await
|
|
23742
|
-
await
|
|
24819
|
+
await import_node_fs46.promises.mkdir(import_node_path80.default.dirname(configPath), { recursive: true });
|
|
24820
|
+
await import_node_fs46.promises.writeFile(configPath, config.text, "utf8");
|
|
23743
24821
|
console.log(`neat codex: wrote [mcp_servers.neat] to ${configPath}`);
|
|
23744
24822
|
} else {
|
|
23745
24823
|
console.log(`neat codex: ${configPath} already has NEAT's MCP server`);
|
|
23746
24824
|
}
|
|
23747
24825
|
if (agents.changed) {
|
|
23748
|
-
await
|
|
23749
|
-
await
|
|
24826
|
+
await import_node_fs46.promises.mkdir(import_node_path80.default.dirname(agentsPath), { recursive: true });
|
|
24827
|
+
await import_node_fs46.promises.writeFile(agentsPath, agents.text, "utf8");
|
|
23750
24828
|
console.log(`neat codex: wrote the graph-first block to ${agentsPath}`);
|
|
23751
24829
|
} else {
|
|
23752
24830
|
console.log(`neat codex: ${agentsPath} already has the graph-first block`);
|
|
@@ -23803,9 +24881,9 @@ async function runCodexCommand(args) {
|
|
|
23803
24881
|
|
|
23804
24882
|
// src/editors-cli.ts
|
|
23805
24883
|
init_cjs_shims();
|
|
23806
|
-
var
|
|
24884
|
+
var import_node_path81 = __toESM(require("path"), 1);
|
|
23807
24885
|
var import_node_os7 = __toESM(require("os"), 1);
|
|
23808
|
-
var
|
|
24886
|
+
var import_node_fs47 = require("fs");
|
|
23809
24887
|
var import_node_util2 = require("util");
|
|
23810
24888
|
var jsonc = __toESM(require("jsonc-parser"), 1);
|
|
23811
24889
|
var NEAT_MCP_SERVER = {
|
|
@@ -23829,17 +24907,17 @@ function homeDir() {
|
|
|
23829
24907
|
}
|
|
23830
24908
|
function xdgConfigDir() {
|
|
23831
24909
|
const xdg = process.env.XDG_CONFIG_HOME;
|
|
23832
|
-
return xdg && xdg.length > 0 ?
|
|
24910
|
+
return xdg && xdg.length > 0 ? import_node_path81.default.resolve(xdg) : import_node_path81.default.join(homeDir(), ".config");
|
|
23833
24911
|
}
|
|
23834
24912
|
function envOverride(name) {
|
|
23835
24913
|
const v = process.env[name];
|
|
23836
|
-
return v && v.length > 0 ?
|
|
24914
|
+
return v && v.length > 0 ? import_node_path81.default.resolve(v) : void 0;
|
|
23837
24915
|
}
|
|
23838
24916
|
var CURSOR_CLIENT = {
|
|
23839
24917
|
id: "cursor",
|
|
23840
24918
|
label: "Cursor",
|
|
23841
24919
|
docsUrl: "https://docs.cursor.com/context/mcp",
|
|
23842
|
-
mcpConfigPath: () => envOverride("NEAT_CURSOR_CONFIG") ??
|
|
24920
|
+
mcpConfigPath: () => envOverride("NEAT_CURSOR_CONFIG") ?? import_node_path81.default.join(homeDir(), ".cursor", "mcp.json"),
|
|
23843
24921
|
mcpContainerKey: "mcpServers",
|
|
23844
24922
|
format: "json",
|
|
23845
24923
|
// Cursor still reads a single `.cursorrules` at the project root (the modern
|
|
@@ -23851,7 +24929,7 @@ var DEVIN_CLIENT = {
|
|
|
23851
24929
|
id: "devin",
|
|
23852
24930
|
label: "Devin Desktop (Cascade)",
|
|
23853
24931
|
docsUrl: "https://docs.devin.ai/desktop/cascade/mcp",
|
|
23854
|
-
mcpConfigPath: () => envOverride("NEAT_DEVIN_CONFIG") ??
|
|
24932
|
+
mcpConfigPath: () => envOverride("NEAT_DEVIN_CONFIG") ?? import_node_path81.default.join(homeDir(), ".codeium", "windsurf", "mcp_config.json"),
|
|
23855
24933
|
mcpContainerKey: "mcpServers",
|
|
23856
24934
|
format: "json",
|
|
23857
24935
|
rulesFileName: ".windsurfrules"
|
|
@@ -23860,7 +24938,7 @@ var GEMINI_CLIENT = {
|
|
|
23860
24938
|
id: "gemini",
|
|
23861
24939
|
label: "Gemini CLI",
|
|
23862
24940
|
docsUrl: "https://github.com/google-gemini/gemini-cli/blob/main/docs/tools/mcp-server.md",
|
|
23863
|
-
mcpConfigPath: () => envOverride("NEAT_GEMINI_CONFIG") ??
|
|
24941
|
+
mcpConfigPath: () => envOverride("NEAT_GEMINI_CONFIG") ?? import_node_path81.default.join(homeDir(), ".gemini", "settings.json"),
|
|
23864
24942
|
mcpContainerKey: "mcpServers",
|
|
23865
24943
|
format: "json",
|
|
23866
24944
|
rulesFileName: "GEMINI.md"
|
|
@@ -23869,7 +24947,7 @@ var QWEN_CLIENT = {
|
|
|
23869
24947
|
id: "qwen",
|
|
23870
24948
|
label: "Qwen Code",
|
|
23871
24949
|
docsUrl: "https://qwenlm.github.io/qwen-code-docs/en/users/features/mcp/",
|
|
23872
|
-
mcpConfigPath: () => envOverride("NEAT_QWEN_CONFIG") ??
|
|
24950
|
+
mcpConfigPath: () => envOverride("NEAT_QWEN_CONFIG") ?? import_node_path81.default.join(homeDir(), ".qwen", "settings.json"),
|
|
23873
24951
|
mcpContainerKey: "mcpServers",
|
|
23874
24952
|
format: "json",
|
|
23875
24953
|
rulesFileName: "QWEN.md"
|
|
@@ -23878,7 +24956,7 @@ var AMAZONQ_CLIENT = {
|
|
|
23878
24956
|
id: "amazonq",
|
|
23879
24957
|
label: "Amazon Q Developer CLI",
|
|
23880
24958
|
docsUrl: "https://docs.aws.amazon.com/amazonq/latest/qdeveloper-ug/command-line-mcp-configuration.html",
|
|
23881
|
-
mcpConfigPath: () => envOverride("NEAT_AMAZONQ_CONFIG") ??
|
|
24959
|
+
mcpConfigPath: () => envOverride("NEAT_AMAZONQ_CONFIG") ?? import_node_path81.default.join(homeDir(), ".aws", "amazonq", "mcp.json"),
|
|
23882
24960
|
mcpContainerKey: "mcpServers",
|
|
23883
24961
|
format: "json"
|
|
23884
24962
|
};
|
|
@@ -23886,7 +24964,7 @@ var ROOCODE_CLIENT = {
|
|
|
23886
24964
|
id: "roocode",
|
|
23887
24965
|
label: "Roo Code",
|
|
23888
24966
|
docsUrl: "https://roocodeinc.github.io/Roo-Code/features/mcp/using-mcp-in-roo",
|
|
23889
|
-
mcpConfigPath: () => envOverride("NEAT_ROOCODE_CONFIG") ??
|
|
24967
|
+
mcpConfigPath: () => envOverride("NEAT_ROOCODE_CONFIG") ?? import_node_path81.default.join(process.cwd(), ".roo", "mcp.json"),
|
|
23890
24968
|
mcpContainerKey: "mcpServers",
|
|
23891
24969
|
format: "json"
|
|
23892
24970
|
};
|
|
@@ -23899,9 +24977,9 @@ var ZED_CLIENT = {
|
|
|
23899
24977
|
if (override) return override;
|
|
23900
24978
|
if (process.platform === "win32") {
|
|
23901
24979
|
const appData = process.env.APPDATA;
|
|
23902
|
-
if (appData && appData.length > 0) return
|
|
24980
|
+
if (appData && appData.length > 0) return import_node_path81.default.join(appData, "Zed", "settings.json");
|
|
23903
24981
|
}
|
|
23904
|
-
return
|
|
24982
|
+
return import_node_path81.default.join(homeDir(), ".config", "zed", "settings.json");
|
|
23905
24983
|
},
|
|
23906
24984
|
mcpContainerKey: "context_servers",
|
|
23907
24985
|
format: "jsonc",
|
|
@@ -23911,7 +24989,7 @@ var OPENCODE_CLIENT = {
|
|
|
23911
24989
|
id: "opencode",
|
|
23912
24990
|
label: "OpenCode",
|
|
23913
24991
|
docsUrl: "https://opencode.ai/docs/mcp-servers/",
|
|
23914
|
-
mcpConfigPath: () => envOverride("NEAT_OPENCODE_CONFIG") ??
|
|
24992
|
+
mcpConfigPath: () => envOverride("NEAT_OPENCODE_CONFIG") ?? import_node_path81.default.join(xdgConfigDir(), "opencode", "opencode.json"),
|
|
23915
24993
|
mcpContainerKey: "mcp",
|
|
23916
24994
|
format: "json",
|
|
23917
24995
|
serverEntry: NEAT_OPENCODE_SERVER,
|
|
@@ -23921,7 +24999,7 @@ var CRUSH_CLIENT = {
|
|
|
23921
24999
|
id: "crush",
|
|
23922
25000
|
label: "Crush",
|
|
23923
25001
|
docsUrl: "https://charmbracelet-crush.mintlify.app/configuration/mcp",
|
|
23924
|
-
mcpConfigPath: () => envOverride("NEAT_CRUSH_CONFIG") ??
|
|
25002
|
+
mcpConfigPath: () => envOverride("NEAT_CRUSH_CONFIG") ?? import_node_path81.default.join(xdgConfigDir(), "crush", "crush.json"),
|
|
23925
25003
|
mcpContainerKey: "mcp",
|
|
23926
25004
|
format: "json",
|
|
23927
25005
|
serverEntry: NEAT_CRUSH_SERVER,
|
|
@@ -23984,7 +25062,7 @@ async function planMcp(client, mcpPath) {
|
|
|
23984
25062
|
const serverEntry = client.serverEntry ?? NEAT_MCP_SERVER;
|
|
23985
25063
|
let raw = "";
|
|
23986
25064
|
try {
|
|
23987
|
-
raw = await
|
|
25065
|
+
raw = await import_node_fs47.promises.readFile(mcpPath, "utf8");
|
|
23988
25066
|
} catch (err) {
|
|
23989
25067
|
const e = err;
|
|
23990
25068
|
if (e.code === "ENOENT") {
|
|
@@ -24026,7 +25104,7 @@ async function runEditorInstall(client, opts) {
|
|
|
24026
25104
|
const mcpPath = client.mcpConfigPath();
|
|
24027
25105
|
const serverEntry = client.serverEntry ?? NEAT_MCP_SERVER;
|
|
24028
25106
|
const hasRules = typeof client.rulesFileName === "string";
|
|
24029
|
-
const rulesPath = hasRules ?
|
|
25107
|
+
const rulesPath = hasRules ? import_node_path81.default.join(opts.projectDir, client.rulesFileName) : "";
|
|
24030
25108
|
const mcp = await planMcp(client, mcpPath);
|
|
24031
25109
|
if (mcp === null) return { exitCode: 1 };
|
|
24032
25110
|
let existingRules = "";
|
|
@@ -24035,7 +25113,7 @@ async function runEditorInstall(client, opts) {
|
|
|
24035
25113
|
let block = "";
|
|
24036
25114
|
if (hasRules) {
|
|
24037
25115
|
try {
|
|
24038
|
-
existingRules = await
|
|
25116
|
+
existingRules = await import_node_fs47.promises.readFile(rulesPath, "utf8");
|
|
24039
25117
|
} catch (err) {
|
|
24040
25118
|
if (err.code !== "ENOENT") {
|
|
24041
25119
|
console.error(`neat ${client.id}: failed to read ${rulesPath} \u2014 ${err.message}`);
|
|
@@ -24069,11 +25147,11 @@ async function runEditorInstall(client, opts) {
|
|
|
24069
25147
|
);
|
|
24070
25148
|
return { exitCode: 0 };
|
|
24071
25149
|
}
|
|
24072
|
-
await
|
|
24073
|
-
await
|
|
25150
|
+
await import_node_fs47.promises.mkdir(import_node_path81.default.dirname(mcpPath), { recursive: true });
|
|
25151
|
+
await import_node_fs47.promises.writeFile(mcpPath, mcp.text, "utf8");
|
|
24074
25152
|
if (hasRules) {
|
|
24075
|
-
await
|
|
24076
|
-
await
|
|
25153
|
+
await import_node_fs47.promises.mkdir(import_node_path81.default.dirname(rulesPath), { recursive: true });
|
|
25154
|
+
await import_node_fs47.promises.writeFile(rulesPath, newRules, "utf8");
|
|
24077
25155
|
}
|
|
24078
25156
|
console.log(`neat ${client.id}: wired NEAT into ${client.label}`);
|
|
24079
25157
|
console.log(` MCP server: ${mcpPath} (${client.mcpContainerKey}.neat \u2192 npx -y @neat.is/mcp)`);
|
|
@@ -24109,11 +25187,11 @@ function usage3(client) {
|
|
|
24109
25187
|
}
|
|
24110
25188
|
async function runEditorCommand(clientId, args, projectDir = process.cwd()) {
|
|
24111
25189
|
const client = CLIENTS[clientId];
|
|
24112
|
-
let
|
|
25190
|
+
let apply6 = false;
|
|
24113
25191
|
for (const arg of args) {
|
|
24114
25192
|
switch (arg) {
|
|
24115
25193
|
case "--apply":
|
|
24116
|
-
|
|
25194
|
+
apply6 = true;
|
|
24117
25195
|
break;
|
|
24118
25196
|
case "-h":
|
|
24119
25197
|
case "--help":
|
|
@@ -24126,7 +25204,7 @@ async function runEditorCommand(clientId, args, projectDir = process.cwd()) {
|
|
|
24126
25204
|
}
|
|
24127
25205
|
}
|
|
24128
25206
|
try {
|
|
24129
|
-
const { exitCode } = await runEditorInstall(client, { apply:
|
|
25207
|
+
const { exitCode } = await runEditorInstall(client, { apply: apply6, projectDir });
|
|
24130
25208
|
return exitCode;
|
|
24131
25209
|
} catch (err) {
|
|
24132
25210
|
console.error(err.message);
|
|
@@ -24136,11 +25214,11 @@ async function runEditorCommand(clientId, args, projectDir = process.cwd()) {
|
|
|
24136
25214
|
|
|
24137
25215
|
// src/monitor.ts
|
|
24138
25216
|
init_cjs_shims();
|
|
24139
|
-
var
|
|
25217
|
+
var import_types89 = require("@neat.is/types");
|
|
24140
25218
|
|
|
24141
25219
|
// src/cli-client.ts
|
|
24142
25220
|
init_cjs_shims();
|
|
24143
|
-
var
|
|
25221
|
+
var import_types88 = require("@neat.is/types");
|
|
24144
25222
|
var HttpError = class extends Error {
|
|
24145
25223
|
constructor(status2, message, responseBody = "") {
|
|
24146
25224
|
super(message);
|
|
@@ -24165,10 +25243,10 @@ function createHttpClient(baseUrl, bearerToken) {
|
|
|
24165
25243
|
const root = baseUrl.replace(/\/$/, "");
|
|
24166
25244
|
const authHeader = bearerToken && bearerToken.length > 0 ? { authorization: `Bearer ${bearerToken}` } : {};
|
|
24167
25245
|
return {
|
|
24168
|
-
async get(
|
|
25246
|
+
async get(path84) {
|
|
24169
25247
|
let res;
|
|
24170
25248
|
try {
|
|
24171
|
-
res = await fetch(`${root}${
|
|
25249
|
+
res = await fetch(`${root}${path84}`, {
|
|
24172
25250
|
headers: { ...authHeader }
|
|
24173
25251
|
});
|
|
24174
25252
|
} catch (err) {
|
|
@@ -24180,16 +25258,16 @@ function createHttpClient(baseUrl, bearerToken) {
|
|
|
24180
25258
|
const body = await res.text().catch(() => "");
|
|
24181
25259
|
throw new HttpError(
|
|
24182
25260
|
res.status,
|
|
24183
|
-
`${res.status} ${res.statusText} on GET ${
|
|
25261
|
+
`${res.status} ${res.statusText} on GET ${path84}: ${body}`,
|
|
24184
25262
|
body
|
|
24185
25263
|
);
|
|
24186
25264
|
}
|
|
24187
25265
|
return await res.json();
|
|
24188
25266
|
},
|
|
24189
|
-
async post(
|
|
25267
|
+
async post(path84, body) {
|
|
24190
25268
|
let res;
|
|
24191
25269
|
try {
|
|
24192
|
-
res = await fetch(`${root}${
|
|
25270
|
+
res = await fetch(`${root}${path84}`, {
|
|
24193
25271
|
method: "POST",
|
|
24194
25272
|
headers: { "content-type": "application/json", ...authHeader },
|
|
24195
25273
|
body: JSON.stringify(body)
|
|
@@ -24203,7 +25281,7 @@ function createHttpClient(baseUrl, bearerToken) {
|
|
|
24203
25281
|
const text = await res.text().catch(() => "");
|
|
24204
25282
|
throw new HttpError(
|
|
24205
25283
|
res.status,
|
|
24206
|
-
`${res.status} ${res.statusText} on POST ${
|
|
25284
|
+
`${res.status} ${res.statusText} on POST ${path84}: ${text}`,
|
|
24207
25285
|
text
|
|
24208
25286
|
);
|
|
24209
25287
|
}
|
|
@@ -24217,12 +25295,12 @@ function projectPath(project, suffix) {
|
|
|
24217
25295
|
}
|
|
24218
25296
|
async function runRootCause(client, input) {
|
|
24219
25297
|
const qs = input.errorId ? `?errorId=${encodeURIComponent(input.errorId)}` : "";
|
|
24220
|
-
const
|
|
25298
|
+
const path84 = projectPath(
|
|
24221
25299
|
input.project,
|
|
24222
25300
|
`/graph/root-cause/${encodeURIComponent(input.errorNode)}${qs}`
|
|
24223
25301
|
);
|
|
24224
25302
|
try {
|
|
24225
|
-
const result = await client.get(
|
|
25303
|
+
const result = await client.get(path84);
|
|
24226
25304
|
const arrowPath = result.traversalPath.join(" \u2190 ");
|
|
24227
25305
|
const provenances = result.edgeProvenances.length ? result.edgeProvenances.join(", ") : "(direct, no edges traversed)";
|
|
24228
25306
|
const summary = `Root cause for ${input.errorNode} is ${result.rootCauseNode}. ` + result.rootCauseReason + (result.fixRecommendation ? ` Recommended fix: ${result.fixRecommendation}.` : "");
|
|
@@ -24248,12 +25326,12 @@ async function runRootCause(client, input) {
|
|
|
24248
25326
|
}
|
|
24249
25327
|
async function runBlastRadius(client, input) {
|
|
24250
25328
|
const qs = input.depth !== void 0 ? `?depth=${input.depth}` : "";
|
|
24251
|
-
const
|
|
25329
|
+
const path84 = projectPath(
|
|
24252
25330
|
input.project,
|
|
24253
25331
|
`/graph/blast-radius/${encodeURIComponent(input.nodeId)}${qs}`
|
|
24254
25332
|
);
|
|
24255
25333
|
try {
|
|
24256
|
-
const result = await client.get(
|
|
25334
|
+
const result = await client.get(path84);
|
|
24257
25335
|
if (result.totalAffected === 0) {
|
|
24258
25336
|
return {
|
|
24259
25337
|
summary: `${result.origin} has no dependents. Nothing else would break if it failed.`
|
|
@@ -24282,17 +25360,17 @@ async function runBlastRadius(client, input) {
|
|
|
24282
25360
|
}
|
|
24283
25361
|
}
|
|
24284
25362
|
function formatBlastEntry(n) {
|
|
24285
|
-
const tag = n.edgeProvenance ===
|
|
25363
|
+
const tag = n.edgeProvenance === import_types88.Provenance.STALE ? " [STALE \u2014 last seen too long ago]" : "";
|
|
24286
25364
|
return ` \u2022 ${n.nodeId} (distance ${n.distance}, ${n.edgeProvenance})${tag}`;
|
|
24287
25365
|
}
|
|
24288
25366
|
async function runDependencies(client, input) {
|
|
24289
25367
|
const depth = input.depth ?? 3;
|
|
24290
|
-
const
|
|
25368
|
+
const path84 = projectPath(
|
|
24291
25369
|
input.project,
|
|
24292
25370
|
`/graph/dependencies/${encodeURIComponent(input.nodeId)}?depth=${depth}`
|
|
24293
25371
|
);
|
|
24294
25372
|
try {
|
|
24295
|
-
const result = await client.get(
|
|
25373
|
+
const result = await client.get(path84);
|
|
24296
25374
|
if (result.total === 0) {
|
|
24297
25375
|
return {
|
|
24298
25376
|
summary: depth === 1 ? `${input.nodeId} has no direct dependencies in the graph.` : `${input.nodeId} has no dependencies (BFS to depth ${depth}).`
|
|
@@ -24339,7 +25417,7 @@ async function runObservedDependencies(client, input) {
|
|
|
24339
25417
|
if (result.observed) {
|
|
24340
25418
|
return {
|
|
24341
25419
|
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.`,
|
|
24342
|
-
provenance:
|
|
25420
|
+
provenance: import_types88.Provenance.OBSERVED
|
|
24343
25421
|
};
|
|
24344
25422
|
}
|
|
24345
25423
|
const note = result.hasExtractedOutbound ? " Static (EXTRACTED) dependencies exist but no runtime traffic has been seen \u2014 is OTel running?" : "";
|
|
@@ -24349,7 +25427,7 @@ async function runObservedDependencies(client, input) {
|
|
|
24349
25427
|
return {
|
|
24350
25428
|
summary: `${input.nodeId} has ${result.dependencies.length} runtime dependenc${result.dependencies.length === 1 ? "y" : "ies"} confirmed by OTel.`,
|
|
24351
25429
|
block: blockLines.join("\n"),
|
|
24352
|
-
provenance:
|
|
25430
|
+
provenance: import_types88.Provenance.OBSERVED
|
|
24353
25431
|
};
|
|
24354
25432
|
} catch (err) {
|
|
24355
25433
|
if (err instanceof HttpError && err.status === 404) {
|
|
@@ -24384,9 +25462,9 @@ function formatDuration(ms) {
|
|
|
24384
25462
|
return `${Math.round(h / 24)}d`;
|
|
24385
25463
|
}
|
|
24386
25464
|
async function runIncidents(client, input) {
|
|
24387
|
-
const
|
|
25465
|
+
const path84 = input.nodeId ? projectPath(input.project, `/incidents/${encodeURIComponent(input.nodeId)}`) : projectPath(input.project, "/incidents");
|
|
24388
25466
|
try {
|
|
24389
|
-
const body = await client.get(
|
|
25467
|
+
const body = await client.get(path84);
|
|
24390
25468
|
const events = body.events;
|
|
24391
25469
|
if (events.length === 0) {
|
|
24392
25470
|
return {
|
|
@@ -24403,7 +25481,7 @@ async function runIncidents(client, input) {
|
|
|
24403
25481
|
return {
|
|
24404
25482
|
summary: `${target} has ${body.total} recorded incident${body.total === 1 ? "" : "s"}; showing the ${ordered.length} most recent.`,
|
|
24405
25483
|
block: blockLines.join("\n"),
|
|
24406
|
-
provenance:
|
|
25484
|
+
provenance: import_types88.Provenance.OBSERVED
|
|
24407
25485
|
};
|
|
24408
25486
|
} catch (err) {
|
|
24409
25487
|
if (err instanceof HttpError && err.status === 404) {
|
|
@@ -24512,7 +25590,7 @@ async function runStaleEdges(client, input) {
|
|
|
24512
25590
|
return {
|
|
24513
25591
|
summary: `${events.length} stale-edge transition${events.length === 1 ? "" : "s"} recorded${input.edgeType ? ` for ${input.edgeType}` : ""}.`,
|
|
24514
25592
|
block: blockLines.join("\n"),
|
|
24515
|
-
provenance:
|
|
25593
|
+
provenance: import_types88.Provenance.STALE
|
|
24516
25594
|
};
|
|
24517
25595
|
}
|
|
24518
25596
|
async function runPolicies(client, input) {
|
|
@@ -24671,10 +25749,10 @@ async function pushSnapshotToRemote(input) {
|
|
|
24671
25749
|
|
|
24672
25750
|
// src/monitor.ts
|
|
24673
25751
|
var OBSERVED_DEP_EDGE_TYPES = /* @__PURE__ */ new Set([
|
|
24674
|
-
|
|
24675
|
-
|
|
24676
|
-
|
|
24677
|
-
|
|
25752
|
+
import_types89.EdgeType.CALLS,
|
|
25753
|
+
import_types89.EdgeType.CONNECTS_TO,
|
|
25754
|
+
import_types89.EdgeType.PUBLISHES_TO,
|
|
25755
|
+
import_types89.EdgeType.CONSUMES_FROM
|
|
24678
25756
|
]);
|
|
24679
25757
|
function divergenceKey(d) {
|
|
24680
25758
|
const column = "column" in d && d.column ? d.column : "";
|
|
@@ -24719,7 +25797,7 @@ function formatDivergenceLine2(d) {
|
|
|
24719
25797
|
}
|
|
24720
25798
|
}
|
|
24721
25799
|
function formatStaleLine(edgeId) {
|
|
24722
|
-
const parsed = (0,
|
|
25800
|
+
const parsed = (0, import_types89.parseEdgeId)(edgeId);
|
|
24723
25801
|
if (parsed) {
|
|
24724
25802
|
return `\u22EF stale ${parsed.source} \u2192 ${parsed.target} (observed edge went quiet)`;
|
|
24725
25803
|
}
|
|
@@ -24732,7 +25810,7 @@ function divergenceJson(d) {
|
|
|
24732
25810
|
return JSON.stringify({ kind: "divergence", ...d });
|
|
24733
25811
|
}
|
|
24734
25812
|
function staleJson(edgeId) {
|
|
24735
|
-
const parsed = (0,
|
|
25813
|
+
const parsed = (0, import_types89.parseEdgeId)(edgeId);
|
|
24736
25814
|
return JSON.stringify({
|
|
24737
25815
|
kind: "stale",
|
|
24738
25816
|
edgeId,
|
|
@@ -24802,7 +25880,7 @@ var MonitorEmitter = class {
|
|
|
24802
25880
|
// ignores non-OBSERVED edges and non-dependency edge types (structural
|
|
24803
25881
|
// ownership), so only real runtime dependencies reach stdout.
|
|
24804
25882
|
emitObservedEdge(edge) {
|
|
24805
|
-
if (edge.provenance !==
|
|
25883
|
+
if (edge.provenance !== import_types89.Provenance.OBSERVED) return false;
|
|
24806
25884
|
if (!OBSERVED_DEP_EDGE_TYPES.has(edge.type)) return false;
|
|
24807
25885
|
const key = `edge|${edge.id}`;
|
|
24808
25886
|
if (this.seen.has(key)) return false;
|
|
@@ -24960,7 +26038,7 @@ async function runMonitor(opts) {
|
|
|
24960
26038
|
case "edge-added": {
|
|
24961
26039
|
const payload = safeParse(frame.data);
|
|
24962
26040
|
const edge = payload?.edge;
|
|
24963
|
-
if (edge && edge.provenance ===
|
|
26041
|
+
if (edge && edge.provenance === import_types89.Provenance.OBSERVED) {
|
|
24964
26042
|
emitter.emitObservedEdge(edge);
|
|
24965
26043
|
divergences.schedule();
|
|
24966
26044
|
}
|
|
@@ -25040,7 +26118,7 @@ function sleep(ms, signal) {
|
|
|
25040
26118
|
|
|
25041
26119
|
// src/cli-verbs.ts
|
|
25042
26120
|
init_cjs_shims();
|
|
25043
|
-
var
|
|
26121
|
+
var import_node_path82 = __toESM(require("path"), 1);
|
|
25044
26122
|
async function resolveProjectEntry(opts) {
|
|
25045
26123
|
const entries = await listProjects();
|
|
25046
26124
|
if (opts.project) {
|
|
@@ -25050,7 +26128,7 @@ async function resolveProjectEntry(opts) {
|
|
|
25050
26128
|
const cwd = opts.cwd ?? process.cwd();
|
|
25051
26129
|
const resolvedCwd = await normalizeProjectPath(cwd);
|
|
25052
26130
|
for (const entry2 of entries) {
|
|
25053
|
-
if (resolvedCwd === entry2.path || resolvedCwd.startsWith(`${entry2.path}${
|
|
26131
|
+
if (resolvedCwd === entry2.path || resolvedCwd.startsWith(`${entry2.path}${import_node_path82.default.sep}`)) {
|
|
25054
26132
|
return entry2;
|
|
25055
26133
|
}
|
|
25056
26134
|
}
|
|
@@ -25203,7 +26281,7 @@ async function runSync(opts) {
|
|
|
25203
26281
|
}
|
|
25204
26282
|
|
|
25205
26283
|
// src/cli.ts
|
|
25206
|
-
var
|
|
26284
|
+
var import_types90 = require("@neat.is/types");
|
|
25207
26285
|
function isNpxInvocation() {
|
|
25208
26286
|
if (process.env.npm_command === "exec") return true;
|
|
25209
26287
|
const execpath = process.env.npm_execpath ?? "";
|
|
@@ -25557,15 +26635,15 @@ async function buildPatchSections(services, project) {
|
|
|
25557
26635
|
for (const svc of services) {
|
|
25558
26636
|
const installer = await pickInstaller(svc.dir);
|
|
25559
26637
|
if (!installer) continue;
|
|
25560
|
-
const
|
|
25561
|
-
if (isEmptyPlan(
|
|
25562
|
-
sections.push({ installer: installer.name, plan:
|
|
26638
|
+
const plan6 = await installer.plan(svc.dir, { project });
|
|
26639
|
+
if (isEmptyPlan(plan6) && !plan6.libOnly && plan6.runtimeKind === void 0) continue;
|
|
26640
|
+
sections.push({ installer: installer.name, plan: plan6 });
|
|
25563
26641
|
}
|
|
25564
26642
|
return sections;
|
|
25565
26643
|
}
|
|
25566
26644
|
async function runInit(opts) {
|
|
25567
26645
|
const written = [];
|
|
25568
|
-
const stat = await
|
|
26646
|
+
const stat = await import_node_fs48.promises.stat(opts.scanPath).catch(() => null);
|
|
25569
26647
|
if (!stat || !stat.isDirectory()) {
|
|
25570
26648
|
console.error(`neat init: ${opts.scanPath} is not a directory`);
|
|
25571
26649
|
return { exitCode: 2, writtenFiles: written };
|
|
@@ -25574,13 +26652,13 @@ async function runInit(opts) {
|
|
|
25574
26652
|
printDiscoveryReport(opts, services);
|
|
25575
26653
|
const sections = opts.noInstall ? [] : await buildPatchSections(services, opts.project);
|
|
25576
26654
|
const patch = renderPatch(sections);
|
|
25577
|
-
const patchPath =
|
|
26655
|
+
const patchPath = import_node_path83.default.join(opts.scanPath, "neat.patch");
|
|
25578
26656
|
if (opts.dryRun) {
|
|
25579
|
-
await
|
|
26657
|
+
await import_node_fs48.promises.writeFile(patchPath, patch, "utf8");
|
|
25580
26658
|
written.push(patchPath);
|
|
25581
26659
|
console.log(`dry-run: patch written to ${patchPath}`);
|
|
25582
|
-
const gitignorePath =
|
|
25583
|
-
const gitignoreExists = await
|
|
26660
|
+
const gitignorePath = import_node_path83.default.join(opts.scanPath, ".gitignore");
|
|
26661
|
+
const gitignoreExists = await import_node_fs48.promises.stat(gitignorePath).then(() => true).catch(() => false);
|
|
25584
26662
|
const verb = gitignoreExists ? "append" : "create";
|
|
25585
26663
|
console.log(`dry-run: would ${verb} ${gitignorePath} (add neat-out/)`);
|
|
25586
26664
|
console.log("rerun without --dry-run to register and snapshot.");
|
|
@@ -25591,9 +26669,9 @@ async function runInit(opts) {
|
|
|
25591
26669
|
const graph = getGraph(graphKey);
|
|
25592
26670
|
const projectPaths = pathsForProject(
|
|
25593
26671
|
graphKey,
|
|
25594
|
-
|
|
26672
|
+
import_node_path83.default.join(opts.scanPath, "neat-out")
|
|
25595
26673
|
);
|
|
25596
|
-
const errorsPath =
|
|
26674
|
+
const errorsPath = import_node_path83.default.join(import_node_path83.default.dirname(opts.outPath), import_node_path83.default.basename(projectPaths.errorsPath));
|
|
25597
26675
|
const result = await extractFromDirectory(graph, opts.scanPath, { errorsPath });
|
|
25598
26676
|
await saveGraphToDisk(graph, opts.outPath);
|
|
25599
26677
|
written.push(opts.outPath);
|
|
@@ -25672,7 +26750,7 @@ async function runInit(opts) {
|
|
|
25672
26750
|
console.log("Run `npm install` (or your language equivalent) to refresh lockfiles.");
|
|
25673
26751
|
}
|
|
25674
26752
|
} else {
|
|
25675
|
-
await
|
|
26753
|
+
await import_node_fs48.promises.writeFile(patchPath, patch, "utf8");
|
|
25676
26754
|
written.push(patchPath);
|
|
25677
26755
|
}
|
|
25678
26756
|
}
|
|
@@ -25712,9 +26790,9 @@ var CLAUDE_SKILL_CONFIG = {
|
|
|
25712
26790
|
};
|
|
25713
26791
|
function claudeConfigPath() {
|
|
25714
26792
|
const override = process.env.NEAT_CLAUDE_CONFIG;
|
|
25715
|
-
if (override && override.length > 0) return
|
|
26793
|
+
if (override && override.length > 0) return import_node_path83.default.resolve(override);
|
|
25716
26794
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
25717
|
-
return
|
|
26795
|
+
return import_node_path83.default.join(home, ".claude.json");
|
|
25718
26796
|
}
|
|
25719
26797
|
async function runSkill(opts) {
|
|
25720
26798
|
const snippet2 = JSON.stringify(CLAUDE_SKILL_CONFIG, null, 2) + "\n";
|
|
@@ -25726,7 +26804,7 @@ async function runSkill(opts) {
|
|
|
25726
26804
|
const target = claudeConfigPath();
|
|
25727
26805
|
let existing = {};
|
|
25728
26806
|
try {
|
|
25729
|
-
existing = JSON.parse(await
|
|
26807
|
+
existing = JSON.parse(await import_node_fs48.promises.readFile(target, "utf8"));
|
|
25730
26808
|
} catch (err) {
|
|
25731
26809
|
if (err.code !== "ENOENT") {
|
|
25732
26810
|
console.error(`neat skill: failed to read ${target} \u2014 ${err.message}`);
|
|
@@ -25738,8 +26816,8 @@ async function runSkill(opts) {
|
|
|
25738
26816
|
...existing,
|
|
25739
26817
|
mcpServers: { ...mcp, neat: CLAUDE_SKILL_CONFIG.mcpServers.neat }
|
|
25740
26818
|
};
|
|
25741
|
-
await
|
|
25742
|
-
await
|
|
26819
|
+
await import_node_fs48.promises.mkdir(import_node_path83.default.dirname(target), { recursive: true });
|
|
26820
|
+
await import_node_fs48.promises.writeFile(target, JSON.stringify(merged, null, 2) + "\n", "utf8");
|
|
25743
26821
|
console.log(`neat skill: wrote mcpServers.neat to ${target}`);
|
|
25744
26822
|
console.log("restart Claude Code to pick up the new MCP server.");
|
|
25745
26823
|
console.log("");
|
|
@@ -25812,7 +26890,7 @@ async function main() {
|
|
|
25812
26890
|
}
|
|
25813
26891
|
const cmd = argvParsed.positional[0];
|
|
25814
26892
|
const parsed = { ...argvParsed, positional: argvParsed.positional.slice(1) };
|
|
25815
|
-
const { positional, apply:
|
|
26893
|
+
const { positional, apply: apply6, dryRun, noInstall } = parsed;
|
|
25816
26894
|
const project = parsed.project ?? DEFAULT_PROJECT;
|
|
25817
26895
|
if (cmd === "init") {
|
|
25818
26896
|
const target = positional[0];
|
|
@@ -25821,22 +26899,22 @@ async function main() {
|
|
|
25821
26899
|
usage4();
|
|
25822
26900
|
process.exit(2);
|
|
25823
26901
|
}
|
|
25824
|
-
if (
|
|
26902
|
+
if (apply6 && dryRun) {
|
|
25825
26903
|
console.error("neat init: --apply and --dry-run are mutually exclusive");
|
|
25826
26904
|
process.exit(2);
|
|
25827
26905
|
}
|
|
25828
|
-
const scanPath =
|
|
26906
|
+
const scanPath = import_node_path83.default.resolve(target);
|
|
25829
26907
|
const projectExplicit = parsed.project !== null;
|
|
25830
|
-
const projectName = projectExplicit ? project :
|
|
26908
|
+
const projectName = projectExplicit ? project : import_node_path83.default.basename(scanPath);
|
|
25831
26909
|
const projectKey = projectExplicit ? project : DEFAULT_PROJECT;
|
|
25832
|
-
const fallback = pathsForProject(projectKey,
|
|
25833
|
-
const outPath =
|
|
26910
|
+
const fallback = pathsForProject(projectKey, import_node_path83.default.join(scanPath, "neat-out")).snapshotPath;
|
|
26911
|
+
const outPath = import_node_path83.default.resolve(process.env.NEAT_OUT_PATH ?? fallback);
|
|
25834
26912
|
const result = await runInit({
|
|
25835
26913
|
scanPath,
|
|
25836
26914
|
outPath,
|
|
25837
26915
|
project: projectName,
|
|
25838
26916
|
projectExplicit,
|
|
25839
|
-
apply:
|
|
26917
|
+
apply: apply6,
|
|
25840
26918
|
dryRun,
|
|
25841
26919
|
noInstall,
|
|
25842
26920
|
verbose: parsed.verbose
|
|
@@ -25851,21 +26929,21 @@ async function main() {
|
|
|
25851
26929
|
usage4();
|
|
25852
26930
|
process.exit(2);
|
|
25853
26931
|
}
|
|
25854
|
-
const scanPath =
|
|
25855
|
-
const stat = await
|
|
26932
|
+
const scanPath = import_node_path83.default.resolve(target);
|
|
26933
|
+
const stat = await import_node_fs48.promises.stat(scanPath).catch(() => null);
|
|
25856
26934
|
if (!stat || !stat.isDirectory()) {
|
|
25857
26935
|
console.error(`neat watch: ${scanPath} is not a directory`);
|
|
25858
26936
|
process.exit(2);
|
|
25859
26937
|
}
|
|
25860
|
-
const projectPaths = pathsForProject(project,
|
|
25861
|
-
const outPath =
|
|
25862
|
-
const errorsPath =
|
|
25863
|
-
process.env.NEAT_ERRORS_PATH ??
|
|
26938
|
+
const projectPaths = pathsForProject(project, import_node_path83.default.join(scanPath, "neat-out"));
|
|
26939
|
+
const outPath = import_node_path83.default.resolve(process.env.NEAT_OUT_PATH ?? projectPaths.snapshotPath);
|
|
26940
|
+
const errorsPath = import_node_path83.default.resolve(
|
|
26941
|
+
process.env.NEAT_ERRORS_PATH ?? import_node_path83.default.join(import_node_path83.default.dirname(outPath), import_node_path83.default.basename(projectPaths.errorsPath))
|
|
25864
26942
|
);
|
|
25865
|
-
const staleEventsPath =
|
|
25866
|
-
process.env.NEAT_STALE_EVENTS_PATH ??
|
|
26943
|
+
const staleEventsPath = import_node_path83.default.resolve(
|
|
26944
|
+
process.env.NEAT_STALE_EVENTS_PATH ?? import_node_path83.default.join(import_node_path83.default.dirname(outPath), import_node_path83.default.basename(projectPaths.staleEventsPath))
|
|
25867
26945
|
);
|
|
25868
|
-
const embeddingsCachePath = process.env.NEAT_EMBEDDINGS_CACHE_PATH ?
|
|
26946
|
+
const embeddingsCachePath = process.env.NEAT_EMBEDDINGS_CACHE_PATH ? import_node_path83.default.resolve(process.env.NEAT_EMBEDDINGS_CACHE_PATH) : void 0;
|
|
25869
26947
|
const handle = await startWatch(getGraph(project), {
|
|
25870
26948
|
scanPath,
|
|
25871
26949
|
outPath,
|
|
@@ -25874,7 +26952,7 @@ async function main() {
|
|
|
25874
26952
|
project,
|
|
25875
26953
|
// Resolve NEAT_HOME so a `neat watch` picks up connectors added to
|
|
25876
26954
|
// ~/.neat/connectors.json (#871). Same resolution the rest of the CLI uses.
|
|
25877
|
-
neatHome: process.env.NEAT_HOME ?
|
|
26955
|
+
neatHome: process.env.NEAT_HOME ? import_node_path83.default.resolve(process.env.NEAT_HOME) : import_node_path83.default.join(import_node_os8.default.homedir(), ".neat"),
|
|
25878
26956
|
...embeddingsCachePath ? { embeddingsCachePath } : {},
|
|
25879
26957
|
host: process.env.HOST ?? "0.0.0.0",
|
|
25880
26958
|
port: Number(process.env.PORT ?? 8080),
|
|
@@ -26056,11 +27134,11 @@ async function main() {
|
|
|
26056
27134
|
process.exit(1);
|
|
26057
27135
|
}
|
|
26058
27136
|
async function tryOrchestrator(cmd, parsed) {
|
|
26059
|
-
const scanPath =
|
|
26060
|
-
const stat = await
|
|
27137
|
+
const scanPath = import_node_path83.default.resolve(cmd);
|
|
27138
|
+
const stat = await import_node_fs48.promises.stat(scanPath).catch(() => null);
|
|
26061
27139
|
if (!stat || !stat.isDirectory()) return null;
|
|
26062
27140
|
const projectExplicit = parsed.project !== null;
|
|
26063
|
-
const projectName = projectExplicit ? parsed.project :
|
|
27141
|
+
const projectName = projectExplicit ? parsed.project : import_node_path83.default.basename(scanPath);
|
|
26064
27142
|
const result = await runOrchestrator({
|
|
26065
27143
|
scanPath,
|
|
26066
27144
|
project: projectName,
|
|
@@ -26249,10 +27327,10 @@ async function runQueryVerb(cmd, parsed) {
|
|
|
26249
27327
|
const parts = parsed.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
26250
27328
|
const out = [];
|
|
26251
27329
|
for (const p of parts) {
|
|
26252
|
-
const r =
|
|
27330
|
+
const r = import_types90.DivergenceTypeSchema.safeParse(p);
|
|
26253
27331
|
if (!r.success) {
|
|
26254
27332
|
console.error(
|
|
26255
|
-
`neat divergences: unknown --type "${p}". allowed: ${
|
|
27333
|
+
`neat divergences: unknown --type "${p}". allowed: ${import_types90.DivergenceTypeSchema.options.join(", ")}`
|
|
26256
27334
|
);
|
|
26257
27335
|
return 2;
|
|
26258
27336
|
}
|