@neat.is/core 0.7.9 → 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-N5TPODCX.js → chunk-6T7ZHODF.js} +486 -18
- package/dist/chunk-6T7ZHODF.js.map +1 -0
- package/dist/{chunk-ILG3SMD5.js → chunk-D4OX6MUX.js} +6 -2
- package/dist/chunk-D4OX6MUX.js.map +1 -0
- package/dist/cli.cjs +1256 -252
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +689 -164
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +505 -24
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +24 -1
- package/dist/index.d.ts +24 -1
- package/dist/index.js +2 -2
- package/dist/neatd.cjs +505 -24
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +2 -2
- package/dist/server.cjs +494 -21
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +1 -1
- package/package.json +2 -2
- package/dist/chunk-ILG3SMD5.js.map +0 -1
- package/dist/chunk-N5TPODCX.js.map +0 -1
package/dist/cli.cjs
CHANGED
|
@@ -61,8 +61,8 @@ function mountBearerAuth(app, opts) {
|
|
|
61
61
|
]);
|
|
62
62
|
const publicRead = opts.publicRead === true;
|
|
63
63
|
app.addHook("preHandler", (req, reply, done) => {
|
|
64
|
-
const
|
|
65
|
-
if (exactUnauthPaths.has(
|
|
64
|
+
const 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;
|
|
@@ -771,9 +771,9 @@ __export(cli_exports, {
|
|
|
771
771
|
});
|
|
772
772
|
module.exports = __toCommonJS(cli_exports);
|
|
773
773
|
init_cjs_shims();
|
|
774
|
-
var
|
|
774
|
+
var import_node_path83 = __toESM(require("path"), 1);
|
|
775
775
|
var import_node_os8 = __toESM(require("os"), 1);
|
|
776
|
-
var
|
|
776
|
+
var import_node_fs48 = require("fs");
|
|
777
777
|
|
|
778
778
|
// src/banner.ts
|
|
779
779
|
init_cjs_shims();
|
|
@@ -1341,19 +1341,19 @@ function confidenceFromMix(edges, now = Date.now()) {
|
|
|
1341
1341
|
function longestIncomingWalk(graph, start, maxDepth) {
|
|
1342
1342
|
let best = { path: [start], edges: [] };
|
|
1343
1343
|
const visited = /* @__PURE__ */ new Set([start]);
|
|
1344
|
-
function step(node,
|
|
1345
|
-
if (
|
|
1346
|
-
best = { path: [...
|
|
1344
|
+
function step(node, path84, edges) {
|
|
1345
|
+
if (path84.length > best.path.length) {
|
|
1346
|
+
best = { path: [...path84], edges: [...edges] };
|
|
1347
1347
|
}
|
|
1348
|
-
if (
|
|
1348
|
+
if (path84.length - 1 >= maxDepth) return;
|
|
1349
1349
|
const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
|
|
1350
1350
|
for (const [srcId, edge] of incoming) {
|
|
1351
1351
|
if (visited.has(srcId)) continue;
|
|
1352
1352
|
visited.add(srcId);
|
|
1353
|
-
|
|
1353
|
+
path84.push(srcId);
|
|
1354
1354
|
edges.push(edge);
|
|
1355
|
-
step(srcId,
|
|
1356
|
-
|
|
1355
|
+
step(srcId, path84, edges);
|
|
1356
|
+
path84.pop();
|
|
1357
1357
|
edges.pop();
|
|
1358
1358
|
visited.delete(srcId);
|
|
1359
1359
|
}
|
|
@@ -1560,26 +1560,26 @@ function dominantFailingCall(graph, serviceId9, visited) {
|
|
|
1560
1560
|
return best;
|
|
1561
1561
|
}
|
|
1562
1562
|
function followFailingCallChain(graph, originServiceId, maxDepth) {
|
|
1563
|
-
const
|
|
1563
|
+
const path84 = [originServiceId];
|
|
1564
1564
|
const edges = [];
|
|
1565
1565
|
const visited = /* @__PURE__ */ new Set([originServiceId]);
|
|
1566
1566
|
let current = originServiceId;
|
|
1567
1567
|
for (let depth = 0; depth < maxDepth; depth++) {
|
|
1568
1568
|
const hop = dominantFailingCall(graph, current, visited);
|
|
1569
1569
|
if (!hop) break;
|
|
1570
|
-
|
|
1570
|
+
path84.push(hop.nextService);
|
|
1571
1571
|
edges.push(hop.edge);
|
|
1572
1572
|
visited.add(hop.nextService);
|
|
1573
1573
|
current = hop.nextService;
|
|
1574
1574
|
}
|
|
1575
1575
|
if (edges.length === 0) return null;
|
|
1576
|
-
return { path:
|
|
1576
|
+
return { path: path84, edges, culprit: current };
|
|
1577
1577
|
}
|
|
1578
1578
|
function crossServiceRootCause(graph, originId, incidents, errorEvent) {
|
|
1579
1579
|
const chain = followFailingCallChain(graph, originId, ROOT_CAUSE_MAX_DEPTH);
|
|
1580
1580
|
if (!chain) return null;
|
|
1581
1581
|
const culprit = chain.culprit;
|
|
1582
|
-
const
|
|
1582
|
+
const path84 = [...chain.path];
|
|
1583
1583
|
const edgeProvenances = chain.edges.map((e) => e.provenance);
|
|
1584
1584
|
const baseConfidence = confidenceFromMix(chain.edges);
|
|
1585
1585
|
const confidence = Math.max(0, Math.min(1, baseConfidence * INCIDENT_ROOT_CAUSE_CONFIDENCE));
|
|
@@ -1587,14 +1587,14 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
|
|
|
1587
1587
|
if (loc) {
|
|
1588
1588
|
let rootCauseNode = culprit;
|
|
1589
1589
|
if (loc.fileNode) {
|
|
1590
|
-
|
|
1590
|
+
path84.push(loc.fileNode);
|
|
1591
1591
|
edgeProvenances.push(import_types.Provenance.OBSERVED);
|
|
1592
1592
|
rootCauseNode = loc.fileNode;
|
|
1593
1593
|
}
|
|
1594
1594
|
return import_types.RootCauseResultSchema.parse({
|
|
1595
1595
|
rootCauseNode,
|
|
1596
1596
|
rootCauseReason: loc.rootCauseReason,
|
|
1597
|
-
traversalPath:
|
|
1597
|
+
traversalPath: path84,
|
|
1598
1598
|
edgeProvenances,
|
|
1599
1599
|
confidence,
|
|
1600
1600
|
...loc.fixRecommendation ? { fixRecommendation: loc.fixRecommendation } : {}
|
|
@@ -1606,7 +1606,7 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
|
|
|
1606
1606
|
return import_types.RootCauseResultSchema.parse({
|
|
1607
1607
|
rootCauseNode: culprit,
|
|
1608
1608
|
rootCauseReason: `${culpritName} is failing downstream calls (${errs} observed error${errs === 1 ? "" : "s"})`,
|
|
1609
|
-
traversalPath:
|
|
1609
|
+
traversalPath: path84,
|
|
1610
1610
|
edgeProvenances,
|
|
1611
1611
|
confidence,
|
|
1612
1612
|
fixRecommendation: `Inspect ${culpritName}'s failing handler`
|
|
@@ -2274,6 +2274,7 @@ var import_yaml = require("yaml");
|
|
|
2274
2274
|
var import_types3 = require("@neat.is/types");
|
|
2275
2275
|
var SERVICE_FILE_EXTENSIONS = /* @__PURE__ */ new Set([".js", ".mjs", ".cjs", ".ts", ".tsx", ".py", ".go", ".rb", ".php"]);
|
|
2276
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"]);
|
|
2277
2278
|
var IGNORED_DIRS = /* @__PURE__ */ new Set([
|
|
2278
2279
|
"node_modules",
|
|
2279
2280
|
".git",
|
|
@@ -2313,6 +2314,7 @@ async function isPythonVenvDir(dir) {
|
|
|
2313
2314
|
function isConfigFile(name) {
|
|
2314
2315
|
const ext = import_node_path4.default.extname(name);
|
|
2315
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" };
|
|
2316
2318
|
if (name === ".env" || name.startsWith(".env.")) {
|
|
2317
2319
|
if (isEnvTemplateFile(name)) return { match: false, fileType: "" };
|
|
2318
2320
|
if (isNeatAuthoredEnvFile(name)) return { match: false, fileType: "" };
|
|
@@ -3141,8 +3143,8 @@ function chiRoutesFromSource(source, parser) {
|
|
|
3141
3143
|
chiWalk(tree.rootNode, "", out);
|
|
3142
3144
|
return out;
|
|
3143
3145
|
}
|
|
3144
|
-
function stripChiRegex(
|
|
3145
|
-
return
|
|
3146
|
+
function stripChiRegex(path84) {
|
|
3147
|
+
return path84.replace(/\{([^{}:]+):[^{}]*\}/g, "{$1}");
|
|
3146
3148
|
}
|
|
3147
3149
|
function chiWalk(node, prefix, out) {
|
|
3148
3150
|
for (let i = 0; i < node.namedChildCount; i++) {
|
|
@@ -3814,9 +3816,9 @@ function rubyRocketRoute(args) {
|
|
|
3814
3816
|
if (!pair || pair.type !== "pair") continue;
|
|
3815
3817
|
const k = pair.childForFieldName("key");
|
|
3816
3818
|
if (k?.type !== "string") continue;
|
|
3817
|
-
const
|
|
3818
|
-
if (
|
|
3819
|
-
return { path:
|
|
3819
|
+
const path84 = rubyLiteral(k);
|
|
3820
|
+
if (path84 === null) continue;
|
|
3821
|
+
return { path: path84, target: rubyLiteral(pair.childForFieldName("value")) };
|
|
3820
3822
|
}
|
|
3821
3823
|
return null;
|
|
3822
3824
|
}
|
|
@@ -4436,7 +4438,7 @@ async function expressMountPrefixes(files, serviceDir, tsPaths) {
|
|
|
4436
4438
|
};
|
|
4437
4439
|
const filePrefix = /* @__PURE__ */ new Map();
|
|
4438
4440
|
const conflicted = /* @__PURE__ */ new Set();
|
|
4439
|
-
const
|
|
4441
|
+
const apply6 = (file, prefix) => {
|
|
4440
4442
|
if (conflicted.has(file)) return;
|
|
4441
4443
|
const existing = filePrefix.get(file);
|
|
4442
4444
|
if (existing === void 0) filePrefix.set(file, prefix);
|
|
@@ -4453,7 +4455,7 @@ async function expressMountPrefixes(files, serviceDir, tsPaths) {
|
|
|
4453
4455
|
const info = fileInfo.get(file);
|
|
4454
4456
|
const rv = info?.routerVars.get(name);
|
|
4455
4457
|
if (!info || !rv) return;
|
|
4456
|
-
if (rv.declares && info.appVars.size === 0)
|
|
4458
|
+
if (rv.declares && info.appVars.size === 0) apply6(file, accPrefix);
|
|
4457
4459
|
for (const m of rv.mounts) {
|
|
4458
4460
|
if (!m.target) continue;
|
|
4459
4461
|
const t = resolveTarget(m.target, file);
|
|
@@ -5429,6 +5431,21 @@ async function appendErrorEvent(ctx, ev) {
|
|
|
5429
5431
|
await import_node_fs8.promises.mkdir(import_node_path9.default.dirname(ctx.errorsPath), { recursive: true });
|
|
5430
5432
|
await import_node_fs8.promises.appendFile(ctx.errorsPath, JSON.stringify(ev) + "\n", "utf8");
|
|
5431
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
|
+
}
|
|
5432
5449
|
function incidentAffectedNode(span, graph, scanPath) {
|
|
5433
5450
|
const sid = graph ? resolveFusedServiceId(graph, span.service, span.env) : (0, import_types8.serviceId)(span.service, span.env);
|
|
5434
5451
|
const serviceNode = graph && graph.hasNode(sid) ? graph.getNodeAttributes(sid) : void 0;
|
|
@@ -11242,8 +11259,41 @@ var import_tree_sitter14 = __toESM(require("tree-sitter"), 1);
|
|
|
11242
11259
|
var import_tree_sitter_go3 = __toESM(require("tree-sitter-go"), 1);
|
|
11243
11260
|
var import_types35 = require("@neat.is/types");
|
|
11244
11261
|
init_otel();
|
|
11245
|
-
var SQL_METHODS = /* @__PURE__ */ new Set(["Exec", "ExecContext", "Query", "QueryContext", "QueryRow", "QueryRowContext"]);
|
|
11246
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
|
+
}
|
|
11247
11297
|
function walk7(node, visit) {
|
|
11248
11298
|
visit(node);
|
|
11249
11299
|
for (let i = 0; i < node.namedChildCount; i++) {
|
|
@@ -11251,25 +11301,54 @@ function walk7(node, visit) {
|
|
|
11251
11301
|
if (child) walk7(child, visit);
|
|
11252
11302
|
}
|
|
11253
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
|
+
}
|
|
11254
11331
|
function goSqlEndpointsFromFile(file, serviceDir) {
|
|
11255
11332
|
if (import_node_path48.default.extname(file.path) !== ".go") return [];
|
|
11256
|
-
|
|
11257
|
-
|
|
11258
|
-
const
|
|
11259
|
-
|
|
11260
|
-
);
|
|
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 [];
|
|
11261
11338
|
const out = [];
|
|
11262
11339
|
walk7(tree.rootNode, (node) => {
|
|
11263
11340
|
if (node.type !== "call_expression") return;
|
|
11264
11341
|
const fn = node.childForFieldName("function");
|
|
11265
11342
|
if (fn?.type !== "selector_expression") return;
|
|
11266
11343
|
const method = fn.childForFieldName("field")?.text;
|
|
11267
|
-
if (!method
|
|
11268
|
-
const
|
|
11269
|
-
if (!
|
|
11270
|
-
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;
|
|
11271
11349
|
const table = tableFromSqlStatement(sql);
|
|
11272
11350
|
if (!table) return;
|
|
11351
|
+
const columns = columnsFromSqlStatement(sql);
|
|
11273
11352
|
const line = node.startPosition.row + 1;
|
|
11274
11353
|
out.push({
|
|
11275
11354
|
infraId: (0, import_types35.infraId)("sql-table", table),
|
|
@@ -11277,7 +11356,12 @@ function goSqlEndpointsFromFile(file, serviceDir) {
|
|
|
11277
11356
|
kind: "sql-table",
|
|
11278
11357
|
edgeType: "CALLS",
|
|
11279
11358
|
confidenceKind: "verified-call-site",
|
|
11280
|
-
|
|
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
|
+
}
|
|
11281
11365
|
});
|
|
11282
11366
|
});
|
|
11283
11367
|
return out;
|
|
@@ -11291,12 +11375,12 @@ var import_tree_sitter_go4 = __toESM(require("tree-sitter-go"), 1);
|
|
|
11291
11375
|
var import_types36 = require("@neat.is/types");
|
|
11292
11376
|
var GORM_IMPORT_RE = /gorm\.io\/gorm/;
|
|
11293
11377
|
var PARSE_CHUNK11 = 16384;
|
|
11294
|
-
function
|
|
11378
|
+
function makeGoParser4() {
|
|
11295
11379
|
const p = new import_tree_sitter15.default();
|
|
11296
11380
|
p.setLanguage(import_tree_sitter_go4.default);
|
|
11297
11381
|
return p;
|
|
11298
11382
|
}
|
|
11299
|
-
function
|
|
11383
|
+
function parseSource11(parser, source) {
|
|
11300
11384
|
return parser.parse(
|
|
11301
11385
|
(index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK11)
|
|
11302
11386
|
);
|
|
@@ -11720,7 +11804,7 @@ function collectColumns(struct, structs, seen, prefix, out, emitted) {
|
|
|
11720
11804
|
function gormEndpointsFromFile(file, serviceDir) {
|
|
11721
11805
|
if (import_node_path49.default.extname(file.path) !== ".go") return [];
|
|
11722
11806
|
if (!GORM_IMPORT_RE.test(file.content)) return [];
|
|
11723
|
-
const tree =
|
|
11807
|
+
const tree = parseSource11(makeGoParser4(), file.content);
|
|
11724
11808
|
const { structs, models, tableFor } = analyze(tree);
|
|
11725
11809
|
const out = [];
|
|
11726
11810
|
const seenTables = /* @__PURE__ */ new Set();
|
|
@@ -11751,7 +11835,7 @@ function gormEndpointsFromFile(file, serviceDir) {
|
|
|
11751
11835
|
function gormForeignKeys(file, serviceDir) {
|
|
11752
11836
|
if (import_node_path49.default.extname(file.path) !== ".go") return [];
|
|
11753
11837
|
if (!GORM_IMPORT_RE.test(file.content)) return [];
|
|
11754
|
-
const tree =
|
|
11838
|
+
const tree = parseSource11(makeGoParser4(), file.content);
|
|
11755
11839
|
const { structs, models, tableFor } = analyze(tree);
|
|
11756
11840
|
const out = [];
|
|
11757
11841
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -13978,7 +14062,7 @@ var import_chokidar = __toESM(require("chokidar"), 1);
|
|
|
13978
14062
|
init_cjs_shims();
|
|
13979
14063
|
var import_fastify2 = __toESM(require("fastify"), 1);
|
|
13980
14064
|
var import_cors = __toESM(require("@fastify/cors"), 1);
|
|
13981
|
-
var
|
|
14065
|
+
var import_types86 = require("@neat.is/types");
|
|
13982
14066
|
|
|
13983
14067
|
// src/extend/index.ts
|
|
13984
14068
|
init_cjs_shims();
|
|
@@ -15307,6 +15391,23 @@ async function runConnectorPoll(connector, ctx, graph, resolveTarget) {
|
|
|
15307
15391
|
unresolved++;
|
|
15308
15392
|
continue;
|
|
15309
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
|
+
}
|
|
15310
15411
|
if (resolved.ensureInfraNode) {
|
|
15311
15412
|
const { kind, name, provider } = resolved.ensureInfraNode;
|
|
15312
15413
|
ensureInfraNode(graph, kind, name, provider);
|
|
@@ -15772,10 +15873,10 @@ var SUPABASE_RPC_TARGET_KIND = "supabase-rpc";
|
|
|
15772
15873
|
// src/connectors/supabase/map.ts
|
|
15773
15874
|
var REST_RPC_PATH_RE = /^\/rest\/v1\/rpc\/([^/?]+)/;
|
|
15774
15875
|
var REST_TABLE_PATH_RE = /^\/rest\/v1\/([^/?]+)/;
|
|
15775
|
-
function targetFromRestPath(
|
|
15776
|
-
const rpcMatch = REST_RPC_PATH_RE.exec(
|
|
15876
|
+
function targetFromRestPath(path84) {
|
|
15877
|
+
const rpcMatch = REST_RPC_PATH_RE.exec(path84);
|
|
15777
15878
|
if (rpcMatch) return { targetKind: SUPABASE_RPC_TARGET_KIND, name: rpcMatch[1] };
|
|
15778
|
-
const tableMatch = REST_TABLE_PATH_RE.exec(
|
|
15879
|
+
const tableMatch = REST_TABLE_PATH_RE.exec(path84);
|
|
15779
15880
|
if (tableMatch) return { targetKind: SUPABASE_TABLE_TARGET_KIND, name: tableMatch[1] };
|
|
15780
15881
|
return null;
|
|
15781
15882
|
}
|
|
@@ -16379,9 +16480,9 @@ function parseFirebaseTargetName(targetName) {
|
|
|
16379
16480
|
const secondSep = rest.indexOf(FIELD_SEP);
|
|
16380
16481
|
if (secondSep === -1) return null;
|
|
16381
16482
|
const method = rest.slice(0, secondSep);
|
|
16382
|
-
const
|
|
16383
|
-
if (!resourceName || !method || !
|
|
16384
|
-
return { resourceName, method, path:
|
|
16483
|
+
const path84 = rest.slice(secondSep + 1);
|
|
16484
|
+
if (!resourceName || !method || !path84) return null;
|
|
16485
|
+
return { resourceName, method, path: path84 };
|
|
16385
16486
|
}
|
|
16386
16487
|
function resourceNameFor(type, labels) {
|
|
16387
16488
|
if (!labels) return null;
|
|
@@ -16419,14 +16520,14 @@ function mapLogEntryToSignal(entry2) {
|
|
|
16419
16520
|
if (!req) return null;
|
|
16420
16521
|
if (typeof req.requestMethod !== "string" || req.requestMethod.length === 0) return null;
|
|
16421
16522
|
const method = req.requestMethod.toUpperCase();
|
|
16422
|
-
const
|
|
16423
|
-
if (
|
|
16523
|
+
const path84 = pathFromRequestUrl(req.requestUrl);
|
|
16524
|
+
if (path84 === null) return null;
|
|
16424
16525
|
const timestamp = entry2.timestamp;
|
|
16425
16526
|
if (typeof timestamp !== "string" || timestamp.length === 0) return null;
|
|
16426
16527
|
const isError = typeof req.status === "number" && req.status >= ERROR_STATUS_THRESHOLD2;
|
|
16427
16528
|
return {
|
|
16428
16529
|
targetKind: resourceType,
|
|
16429
|
-
targetName: packFirebaseTargetName({ resourceName, method, path:
|
|
16530
|
+
targetName: packFirebaseTargetName({ resourceName, method, path: path84 }),
|
|
16430
16531
|
callCount: 1,
|
|
16431
16532
|
errorCount: isError ? 1 : 0,
|
|
16432
16533
|
lastObservedIso: timestamp
|
|
@@ -16633,7 +16734,7 @@ function mapEventToSignal(event) {
|
|
|
16633
16734
|
if (Number.isNaN(observedAt.getTime())) return null;
|
|
16634
16735
|
const statusCode = metadata?.statusCode;
|
|
16635
16736
|
const isError = typeof statusCode === "number" && statusCode >= ERROR_STATUS_THRESHOLD3;
|
|
16636
|
-
const
|
|
16737
|
+
const path84 = metadata?.trigger ? parsePathFromTrigger(metadata.trigger) : void 0;
|
|
16637
16738
|
return {
|
|
16638
16739
|
targetKind: CLOUDFLARE_TARGET_KIND,
|
|
16639
16740
|
targetName: scriptName,
|
|
@@ -16641,7 +16742,7 @@ function mapEventToSignal(event) {
|
|
|
16641
16742
|
errorCount: isError ? 1 : 0,
|
|
16642
16743
|
lastObservedIso: observedAt.toISOString(),
|
|
16643
16744
|
method,
|
|
16644
|
-
...
|
|
16745
|
+
...path84 ? { path: path84 } : {},
|
|
16645
16746
|
...typeof statusCode === "number" ? { statusCode } : {},
|
|
16646
16747
|
...typeof metadata?.duration === "number" ? { duration: metadata.duration } : {}
|
|
16647
16748
|
};
|
|
@@ -16687,8 +16788,8 @@ function findTaggedWorkerFileNode(graph, workerName) {
|
|
|
16687
16788
|
});
|
|
16688
16789
|
return found;
|
|
16689
16790
|
}
|
|
16690
|
-
function findMatchingRouteNode(graph, serviceName, method,
|
|
16691
|
-
const normalizedPath = normalizePathTemplate(
|
|
16791
|
+
function findMatchingRouteNode(graph, serviceName, method, path84) {
|
|
16792
|
+
const normalizedPath = normalizePathTemplate(path84);
|
|
16692
16793
|
let found = null;
|
|
16693
16794
|
graph.forEachNode((id, attrs) => {
|
|
16694
16795
|
if (found) return;
|
|
@@ -16705,10 +16806,10 @@ function createCloudflareResolveTarget(config, graph) {
|
|
|
16705
16806
|
return (signal) => {
|
|
16706
16807
|
if (signal.targetKind !== CLOUDFLARE_TARGET_KIND) return null;
|
|
16707
16808
|
const scriptName = signal.targetName;
|
|
16708
|
-
const { method, path:
|
|
16809
|
+
const { method, path: path84 } = signal;
|
|
16709
16810
|
const resolveRouteGrain = (serviceName, wholeFileId) => {
|
|
16710
|
-
if (!method || !
|
|
16711
|
-
return findMatchingRouteNode(graph, serviceName, method,
|
|
16811
|
+
if (!method || !path84) return wholeFileId;
|
|
16812
|
+
return findMatchingRouteNode(graph, serviceName, method, path84) ?? wholeFileId;
|
|
16712
16813
|
};
|
|
16713
16814
|
const mapping = config.workers?.[scriptName];
|
|
16714
16815
|
if (mapping) {
|
|
@@ -17060,9 +17161,9 @@ function parseCloudRunTargetName(targetName) {
|
|
|
17060
17161
|
const secondSep = rest.indexOf(FIELD_SEP2);
|
|
17061
17162
|
if (secondSep === -1) return null;
|
|
17062
17163
|
const method = rest.slice(0, secondSep);
|
|
17063
|
-
const
|
|
17064
|
-
if (!serviceName || !method || !
|
|
17065
|
-
return { serviceName, method, path:
|
|
17164
|
+
const path84 = rest.slice(secondSep + 1);
|
|
17165
|
+
if (!serviceName || !method || !path84) return null;
|
|
17166
|
+
return { serviceName, method, path: path84 };
|
|
17066
17167
|
}
|
|
17067
17168
|
|
|
17068
17169
|
// src/connectors/cloud-run/map.ts
|
|
@@ -17091,14 +17192,14 @@ function mapLogEntryToSignal2(entry2) {
|
|
|
17091
17192
|
if (!req) return null;
|
|
17092
17193
|
if (typeof req.requestMethod !== "string" || req.requestMethod.length === 0) return null;
|
|
17093
17194
|
const method = req.requestMethod.toUpperCase();
|
|
17094
|
-
const
|
|
17095
|
-
if (
|
|
17195
|
+
const path84 = pathFromRequestUrl2(req.requestUrl);
|
|
17196
|
+
if (path84 === null) return null;
|
|
17096
17197
|
const timestamp = entry2.timestamp;
|
|
17097
17198
|
if (typeof timestamp !== "string" || timestamp.length === 0) return null;
|
|
17098
17199
|
const isError = typeof req.status === "number" && req.status >= ERROR_STATUS_THRESHOLD4;
|
|
17099
17200
|
return {
|
|
17100
17201
|
targetKind: CLOUD_RUN_TARGET_KIND,
|
|
17101
|
-
targetName: packCloudRunTargetName({ serviceName, method, path:
|
|
17202
|
+
targetName: packCloudRunTargetName({ serviceName, method, path: path84 }),
|
|
17102
17203
|
callCount: 1,
|
|
17103
17204
|
errorCount: isError ? 1 : 0,
|
|
17104
17205
|
lastObservedIso: timestamp
|
|
@@ -17137,14 +17238,14 @@ function createCloudRunResolveTarget(graph, config) {
|
|
|
17137
17238
|
if (signal.targetKind !== CLOUD_RUN_TARGET_KIND) return null;
|
|
17138
17239
|
const identity = parseCloudRunTargetName(signal.targetName);
|
|
17139
17240
|
if (!identity) return null;
|
|
17140
|
-
const { serviceName: gcpServiceName, method, path:
|
|
17241
|
+
const { serviceName: gcpServiceName, method, path: path84 } = identity;
|
|
17141
17242
|
const mappedService = config.serviceMap?.[gcpServiceName];
|
|
17142
17243
|
if (mappedService) {
|
|
17143
17244
|
const routeNodeId = findMatchingRouteNode2(
|
|
17144
17245
|
graph,
|
|
17145
17246
|
mappedService,
|
|
17146
17247
|
method,
|
|
17147
|
-
normalizePathTemplate(
|
|
17248
|
+
normalizePathTemplate(path84)
|
|
17148
17249
|
);
|
|
17149
17250
|
if (routeNodeId) {
|
|
17150
17251
|
return { targetNodeId: routeNodeId, serviceName: mappedService, edgeType: import_types71.EdgeType.CALLS };
|
|
@@ -17541,6 +17642,338 @@ function createPlanetscaleConnector(graph, config, deps = {}) {
|
|
|
17541
17642
|
};
|
|
17542
17643
|
}
|
|
17543
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
|
+
|
|
17544
17977
|
// src/connectors/registry.ts
|
|
17545
17978
|
var CLOUDFLARE_API_BASE_URL = "https://api.cloudflare.com/client/v4";
|
|
17546
17979
|
async function authProbe(input) {
|
|
@@ -17828,6 +18261,41 @@ var PROVIDER_DISPATCH = {
|
|
|
17828
18261
|
...fetchImpl ? { fetchImpl } : {}
|
|
17829
18262
|
});
|
|
17830
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
|
+
}
|
|
17831
18299
|
}
|
|
17832
18300
|
};
|
|
17833
18301
|
function vercelCredsFrom(credentials) {
|
|
@@ -18040,7 +18508,11 @@ async function startConnectorPolling(input) {
|
|
|
18040
18508
|
const stopFns = all.map(
|
|
18041
18509
|
(registration) => startConnectorPollLoop(
|
|
18042
18510
|
registration.connector,
|
|
18043
|
-
{
|
|
18511
|
+
{
|
|
18512
|
+
projectDir: input.projectDir,
|
|
18513
|
+
credentials: registration.credentials,
|
|
18514
|
+
...input.errorsPath ? { errorsPath: input.errorsPath } : {}
|
|
18515
|
+
},
|
|
18044
18516
|
input.graph,
|
|
18045
18517
|
registration.resolveTarget,
|
|
18046
18518
|
{ intervalMs: registration.intervalMs, connectorId: registration.id }
|
|
@@ -18258,11 +18730,11 @@ function registerRoutes(scope, ctx) {
|
|
|
18258
18730
|
const candidates = req.query.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
18259
18731
|
const parsed = [];
|
|
18260
18732
|
for (const c of candidates) {
|
|
18261
|
-
const r =
|
|
18733
|
+
const r = import_types86.DivergenceTypeSchema.safeParse(c);
|
|
18262
18734
|
if (!r.success) {
|
|
18263
18735
|
return reply.code(400).send({
|
|
18264
18736
|
error: `unknown divergence type "${c}"`,
|
|
18265
|
-
allowed:
|
|
18737
|
+
allowed: import_types86.DivergenceTypeSchema.options
|
|
18266
18738
|
});
|
|
18267
18739
|
}
|
|
18268
18740
|
parsed.push(r.data);
|
|
@@ -18369,10 +18841,15 @@ function registerRoutes(scope, ctx) {
|
|
|
18369
18841
|
}
|
|
18370
18842
|
const reg = built.registration;
|
|
18371
18843
|
const at = (/* @__PURE__ */ new Date()).toISOString();
|
|
18844
|
+
const incidentsPath = errorsPathFor(proj);
|
|
18372
18845
|
try {
|
|
18373
18846
|
const result = await ctx.runPoll(
|
|
18374
18847
|
reg.connector,
|
|
18375
|
-
{
|
|
18848
|
+
{
|
|
18849
|
+
projectDir: proj.scanPath ?? "",
|
|
18850
|
+
credentials: reg.credentials,
|
|
18851
|
+
...incidentsPath ? { errorsPath: incidentsPath } : {}
|
|
18852
|
+
},
|
|
18376
18853
|
proj.graph,
|
|
18377
18854
|
reg.resolveTarget
|
|
18378
18855
|
);
|
|
@@ -18571,7 +19048,7 @@ function registerRoutes(scope, ctx) {
|
|
|
18571
19048
|
const log = new PolicyViolationsLog(proj.paths.policyViolationsPath);
|
|
18572
19049
|
let violations = await log.readAll();
|
|
18573
19050
|
if (req.query.severity) {
|
|
18574
|
-
const sev =
|
|
19051
|
+
const sev = import_types86.PolicySeveritySchema.safeParse(req.query.severity);
|
|
18575
19052
|
if (!sev.success) {
|
|
18576
19053
|
return reply.code(400).send({
|
|
18577
19054
|
error: "invalid severity",
|
|
@@ -18610,7 +19087,7 @@ function registerRoutes(scope, ctx) {
|
|
|
18610
19087
|
scope.post("/policies/check", async (req, reply) => {
|
|
18611
19088
|
const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
|
|
18612
19089
|
if (!proj) return;
|
|
18613
|
-
const parsed =
|
|
19090
|
+
const parsed = import_types86.PoliciesCheckBodySchema.safeParse(req.body ?? {});
|
|
18614
19091
|
if (!parsed.success) {
|
|
18615
19092
|
return reply.code(400).send({
|
|
18616
19093
|
error: "invalid /policies/check body",
|
|
@@ -18943,7 +19420,7 @@ var import_node_fs34 = require("fs");
|
|
|
18943
19420
|
var import_node_path68 = __toESM(require("path"), 1);
|
|
18944
19421
|
|
|
18945
19422
|
// src/daemon.ts
|
|
18946
|
-
var
|
|
19423
|
+
var import_types87 = require("@neat.is/types");
|
|
18947
19424
|
function daemonJsonPath(scanPath) {
|
|
18948
19425
|
return import_node_path69.default.join(scanPath, "neat-out", "daemon.json");
|
|
18949
19426
|
}
|
|
@@ -19601,6 +20078,9 @@ async function startWatch(graph, opts) {
|
|
|
19601
20078
|
project: projectName,
|
|
19602
20079
|
graph,
|
|
19603
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,
|
|
19604
20084
|
...opts.neatHome ? { home: opts.neatHome } : {},
|
|
19605
20085
|
onSkip: (skipped, reason) => console.warn(
|
|
19606
20086
|
`neat watch: connector "${skipped.id}" (${skipped.provider}) skipped for project "${projectName}" \u2014 ${reason}`
|
|
@@ -22229,14 +22709,519 @@ async function apply3(installPlan) {
|
|
|
22229
22709
|
await import_node_fs41.promises.writeFile(generated.file, generated.contents, "utf8");
|
|
22230
22710
|
writtenFiles.push(generated.file);
|
|
22231
22711
|
}
|
|
22232
|
-
|
|
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
|
+
};
|
|
22233
22719
|
}
|
|
22234
22720
|
var goInstaller = { name: "go", detect: detect3, plan: plan3, apply: apply3 };
|
|
22235
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
|
+
|
|
22236
23221
|
// src/installers/shared.ts
|
|
22237
23222
|
init_cjs_shims();
|
|
22238
|
-
function isEmptyPlan(
|
|
22239
|
-
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;
|
|
22240
23225
|
}
|
|
22241
23226
|
|
|
22242
23227
|
// src/installers/index.ts
|
|
@@ -22248,9 +23233,16 @@ var FORBIDDEN_LOCKFILES = /* @__PURE__ */ new Set([
|
|
|
22248
23233
|
"Pipfile.lock",
|
|
22249
23234
|
"Gemfile.lock",
|
|
22250
23235
|
"Cargo.lock",
|
|
22251
|
-
"go.sum"
|
|
23236
|
+
"go.sum",
|
|
23237
|
+
"composer.lock"
|
|
22252
23238
|
]);
|
|
22253
|
-
var INSTALLERS = [
|
|
23239
|
+
var INSTALLERS = [
|
|
23240
|
+
javascriptInstaller,
|
|
23241
|
+
pythonInstaller,
|
|
23242
|
+
goInstaller,
|
|
23243
|
+
rubyInstaller,
|
|
23244
|
+
phpInstaller
|
|
23245
|
+
];
|
|
22254
23246
|
async function pickInstaller(serviceDir) {
|
|
22255
23247
|
for (const inst of INSTALLERS) {
|
|
22256
23248
|
if (await inst.detect(serviceDir)) return inst;
|
|
@@ -22265,7 +23257,7 @@ function renderPatch(sections) {
|
|
|
22265
23257
|
"No SDK installers matched the discovered services. Two reasons this",
|
|
22266
23258
|
"normally happens:",
|
|
22267
23259
|
" - the project uses a language NEAT does not yet instrument",
|
|
22268
|
-
" (Java /
|
|
23260
|
+
" (Java / .NET / Rust are out of scope per ADR-047);",
|
|
22269
23261
|
" - the SDK is already installed, so the installer returned an empty",
|
|
22270
23262
|
" plan.",
|
|
22271
23263
|
"",
|
|
@@ -22275,22 +23267,22 @@ function renderPatch(sections) {
|
|
|
22275
23267
|
}
|
|
22276
23268
|
const lines = ["# neat install plan", ""];
|
|
22277
23269
|
for (const section of sections) {
|
|
22278
|
-
const { installer, plan:
|
|
22279
|
-
lines.push(`## ${installer} (${
|
|
23270
|
+
const { installer, plan: plan6 } = section;
|
|
23271
|
+
lines.push(`## ${installer} (${plan6.language}) \u2014 ${plan6.serviceDir}`);
|
|
22280
23272
|
lines.push("");
|
|
22281
|
-
if (
|
|
23273
|
+
if (plan6.libOnly) {
|
|
22282
23274
|
lines.push("### skipped \u2014 no resolvable entry point (lib-only)");
|
|
22283
23275
|
lines.push("");
|
|
22284
23276
|
continue;
|
|
22285
23277
|
}
|
|
22286
|
-
if (
|
|
22287
|
-
lines.push(`entry: ${
|
|
23278
|
+
if (plan6.entryFile) {
|
|
23279
|
+
lines.push(`entry: ${plan6.entryFile}`);
|
|
22288
23280
|
lines.push("");
|
|
22289
23281
|
}
|
|
22290
|
-
if (
|
|
23282
|
+
if (plan6.dependencyEdits.length > 0) {
|
|
22291
23283
|
lines.push("### dependencies");
|
|
22292
23284
|
const byFile = /* @__PURE__ */ new Map();
|
|
22293
|
-
for (const dep of
|
|
23285
|
+
for (const dep of plan6.dependencyEdits) {
|
|
22294
23286
|
const base = dep.file.split(/[\\/]/).pop() ?? dep.file;
|
|
22295
23287
|
if (FORBIDDEN_LOCKFILES.has(base)) {
|
|
22296
23288
|
throw new Error(
|
|
@@ -22309,9 +23301,9 @@ function renderPatch(sections) {
|
|
|
22309
23301
|
}
|
|
22310
23302
|
lines.push("");
|
|
22311
23303
|
}
|
|
22312
|
-
if (
|
|
23304
|
+
if (plan6.generatedFiles && plan6.generatedFiles.length > 0) {
|
|
22313
23305
|
lines.push("### generated files");
|
|
22314
|
-
for (const gen of
|
|
23306
|
+
for (const gen of plan6.generatedFiles) {
|
|
22315
23307
|
lines.push(`--- (new file) ${gen.file}`);
|
|
22316
23308
|
for (const ln of gen.contents.split(/\r?\n/)) {
|
|
22317
23309
|
lines.push(`+ ${ln}`);
|
|
@@ -22319,26 +23311,26 @@ function renderPatch(sections) {
|
|
|
22319
23311
|
}
|
|
22320
23312
|
lines.push("");
|
|
22321
23313
|
}
|
|
22322
|
-
if (
|
|
23314
|
+
if (plan6.entrypointEdits.length > 0) {
|
|
22323
23315
|
lines.push("### entry-point injection");
|
|
22324
|
-
for (const e of
|
|
23316
|
+
for (const e of plan6.entrypointEdits) {
|
|
22325
23317
|
lines.push(`--- ${e.file}`);
|
|
22326
23318
|
lines.push(`+ ${e.after}`);
|
|
22327
23319
|
lines.push(` ${e.before}`);
|
|
22328
23320
|
}
|
|
22329
23321
|
lines.push("");
|
|
22330
23322
|
}
|
|
22331
|
-
if (
|
|
23323
|
+
if (plan6.envEdits.length > 0) {
|
|
22332
23324
|
lines.push("### env (written to <package-dir>/.env.neat)");
|
|
22333
|
-
for (const env of
|
|
23325
|
+
for (const env of plan6.envEdits) {
|
|
22334
23326
|
lines.push(`- ${env.key}=${env.value}`);
|
|
22335
23327
|
}
|
|
22336
23328
|
lines.push("");
|
|
22337
23329
|
}
|
|
22338
|
-
if (
|
|
23330
|
+
if (plan6.nextConfigEdit) {
|
|
22339
23331
|
lines.push("### next.config (framework flag)");
|
|
22340
|
-
lines.push(`--- ${
|
|
22341
|
-
lines.push(`+ experimental: { instrumentationHook: true }, // ${
|
|
23332
|
+
lines.push(`--- ${plan6.nextConfigEdit.file}`);
|
|
23333
|
+
lines.push(`+ experimental: { instrumentationHook: true }, // ${plan6.nextConfigEdit.reason}`);
|
|
22342
23334
|
lines.push("");
|
|
22343
23335
|
}
|
|
22344
23336
|
}
|
|
@@ -22347,10 +23339,10 @@ function renderPatch(sections) {
|
|
|
22347
23339
|
|
|
22348
23340
|
// src/orchestrator.ts
|
|
22349
23341
|
init_cjs_shims();
|
|
22350
|
-
var
|
|
23342
|
+
var import_node_fs44 = require("fs");
|
|
22351
23343
|
var import_node_http = __toESM(require("http"), 1);
|
|
22352
23344
|
var import_node_net = __toESM(require("net"), 1);
|
|
22353
|
-
var
|
|
23345
|
+
var import_node_path78 = __toESM(require("path"), 1);
|
|
22354
23346
|
var import_node_url4 = require("url");
|
|
22355
23347
|
var import_node_child_process3 = require("child_process");
|
|
22356
23348
|
var import_node_readline = __toESM(require("readline"), 1);
|
|
@@ -22360,7 +23352,7 @@ async function extractAndPersist(opts) {
|
|
|
22360
23352
|
const graphKey = opts.projectExplicit ? opts.project : DEFAULT_PROJECT;
|
|
22361
23353
|
resetGraph(graphKey);
|
|
22362
23354
|
const graph = getGraph(graphKey);
|
|
22363
|
-
const projectPaths = pathsForProject(graphKey,
|
|
23355
|
+
const projectPaths = pathsForProject(graphKey, import_node_path78.default.join(opts.scanPath, "neat-out"));
|
|
22364
23356
|
const extraction = await extractFromDirectory(graph, opts.scanPath, {
|
|
22365
23357
|
errorsPath: projectPaths.errorsPath
|
|
22366
23358
|
});
|
|
@@ -22391,28 +23383,34 @@ async function applyInstallersOver(services, project, options = {}) {
|
|
|
22391
23383
|
let cloudflareWorkers = 0;
|
|
22392
23384
|
let electron = 0;
|
|
22393
23385
|
const installPlans = /* @__PURE__ */ new Map();
|
|
23386
|
+
const dependencyInstructions = /* @__PURE__ */ new Map();
|
|
22394
23387
|
for (const svc of services) {
|
|
22395
23388
|
const installer = await pickInstaller(svc.dir);
|
|
22396
23389
|
if (!installer) continue;
|
|
22397
|
-
const
|
|
22398
|
-
if (isEmptyPlan(
|
|
23390
|
+
const plan6 = await installer.plan(svc.dir, { project });
|
|
23391
|
+
if (isEmptyPlan(plan6) && !plan6.libOnly && plan6.runtimeKind === void 0) {
|
|
22399
23392
|
already++;
|
|
22400
23393
|
continue;
|
|
22401
23394
|
}
|
|
22402
|
-
const outcome = await installer.apply(
|
|
23395
|
+
const outcome = await installer.apply(plan6);
|
|
22403
23396
|
if (outcome.outcome === "instrumented") {
|
|
22404
23397
|
instrumented++;
|
|
22405
|
-
if (
|
|
22406
|
-
const
|
|
22407
|
-
|
|
22408
|
-
|
|
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
|
+
}
|
|
22409
23407
|
}
|
|
22410
23408
|
} else if (outcome.outcome === "already-instrumented") already++;
|
|
22411
23409
|
else if (outcome.outcome === "lib-only") {
|
|
22412
23410
|
libOnly++;
|
|
22413
23411
|
const appDeps = svc.pkg ? appFrameworkDependencies(svc.pkg) : [];
|
|
22414
23412
|
if (appDeps.length > 0) {
|
|
22415
|
-
const svcName =
|
|
23413
|
+
const svcName = import_node_path78.default.basename(svc.dir);
|
|
22416
23414
|
const list = appDeps.join(", ");
|
|
22417
23415
|
console.warn(
|
|
22418
23416
|
`neat: runtime layer won't engage for ${svcName}: no entry point found.
|
|
@@ -22425,7 +23423,7 @@ async function applyInstallersOver(services, project, options = {}) {
|
|
|
22425
23423
|
console.log(`skipping ${svc.dir}: browser bundle; browser-OTel support lands in a future release.`);
|
|
22426
23424
|
} else if (outcome.outcome === "react-native") {
|
|
22427
23425
|
reactNative++;
|
|
22428
|
-
const svcName =
|
|
23426
|
+
const svcName = import_node_path78.default.basename(svc.dir);
|
|
22429
23427
|
console.log(
|
|
22430
23428
|
`neat: ${svc.dir} detected as React Native / Expo
|
|
22431
23429
|
The installer doesn't cover this runtime deterministically.
|
|
@@ -22436,7 +23434,7 @@ async function applyInstallersOver(services, project, options = {}) {
|
|
|
22436
23434
|
);
|
|
22437
23435
|
} else if (outcome.outcome === "bun") {
|
|
22438
23436
|
bun++;
|
|
22439
|
-
const svcName =
|
|
23437
|
+
const svcName = import_node_path78.default.basename(svc.dir);
|
|
22440
23438
|
console.log(
|
|
22441
23439
|
`neat: ${svc.dir} detected as Bun
|
|
22442
23440
|
The installer doesn't cover this runtime deterministically.
|
|
@@ -22447,7 +23445,7 @@ async function applyInstallersOver(services, project, options = {}) {
|
|
|
22447
23445
|
);
|
|
22448
23446
|
} else if (outcome.outcome === "deno") {
|
|
22449
23447
|
deno++;
|
|
22450
|
-
const svcName =
|
|
23448
|
+
const svcName = import_node_path78.default.basename(svc.dir);
|
|
22451
23449
|
console.log(
|
|
22452
23450
|
`neat: ${svc.dir} detected as Deno
|
|
22453
23451
|
The installer doesn't cover this runtime deterministically.
|
|
@@ -22458,7 +23456,7 @@ async function applyInstallersOver(services, project, options = {}) {
|
|
|
22458
23456
|
);
|
|
22459
23457
|
} else if (outcome.outcome === "cloudflare-workers") {
|
|
22460
23458
|
cloudflareWorkers++;
|
|
22461
|
-
const svcName =
|
|
23459
|
+
const svcName = import_node_path78.default.basename(svc.dir);
|
|
22462
23460
|
console.log(
|
|
22463
23461
|
`neat: ${svc.dir} detected as Cloudflare Workers
|
|
22464
23462
|
The installer doesn't cover this runtime deterministically.
|
|
@@ -22469,7 +23467,7 @@ async function applyInstallersOver(services, project, options = {}) {
|
|
|
22469
23467
|
);
|
|
22470
23468
|
} else if (outcome.outcome === "electron") {
|
|
22471
23469
|
electron++;
|
|
22472
|
-
const svcName =
|
|
23470
|
+
const svcName = import_node_path78.default.basename(svc.dir);
|
|
22473
23471
|
console.log(
|
|
22474
23472
|
`neat: ${svc.dir} detected as Electron
|
|
22475
23473
|
The installer doesn't cover this runtime deterministically.
|
|
@@ -22482,7 +23480,7 @@ async function applyInstallersOver(services, project, options = {}) {
|
|
|
22482
23480
|
if (svc.pkg && (outcome.outcome === "instrumented" || outcome.outcome === "already-instrumented")) {
|
|
22483
23481
|
const gaps = uninstrumentedLibraries(svc.pkg);
|
|
22484
23482
|
if (gaps.length > 0) {
|
|
22485
|
-
const svcName =
|
|
23483
|
+
const svcName = import_node_path78.default.basename(svc.dir);
|
|
22486
23484
|
const list = gaps.join(", ");
|
|
22487
23485
|
const subject = gaps.length === 1 ? "this library" : "these libraries";
|
|
22488
23486
|
const aux = gaps.length === 1 ? "isn't" : "aren't";
|
|
@@ -22510,6 +23508,11 @@ async function applyInstallersOver(services, project, options = {}) {
|
|
|
22510
23508
|
}
|
|
22511
23509
|
}
|
|
22512
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
|
+
}
|
|
22513
23516
|
return {
|
|
22514
23517
|
instrumented,
|
|
22515
23518
|
alreadyInstrumented: already,
|
|
@@ -22520,7 +23523,8 @@ async function applyInstallersOver(services, project, options = {}) {
|
|
|
22520
23523
|
deno,
|
|
22521
23524
|
cloudflareWorkers,
|
|
22522
23525
|
electron,
|
|
22523
|
-
packageManagerInstalls
|
|
23526
|
+
packageManagerInstalls,
|
|
23527
|
+
dependencyInstructions: [...dependencyInstructions].map(([dir, command]) => ({ dir, command }))
|
|
22524
23528
|
};
|
|
22525
23529
|
}
|
|
22526
23530
|
async function promptYesNo(question) {
|
|
@@ -22688,24 +23692,24 @@ async function persistedPortsFor(scanPath) {
|
|
|
22688
23692
|
return { rest: record.ports.rest, otlp: record.ports.otlp, web: record.ports.web };
|
|
22689
23693
|
}
|
|
22690
23694
|
async function acquireSpawnLock(scanPath) {
|
|
22691
|
-
const lockPath =
|
|
22692
|
-
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 });
|
|
22693
23697
|
const STALE_LOCK_MS = 6e4;
|
|
22694
23698
|
try {
|
|
22695
|
-
const fd = await
|
|
23699
|
+
const fd = await import_node_fs44.promises.open(lockPath, "wx");
|
|
22696
23700
|
await fd.writeFile(`${process.pid}
|
|
22697
23701
|
`, "utf8");
|
|
22698
23702
|
await fd.close();
|
|
22699
23703
|
return async () => {
|
|
22700
|
-
await
|
|
23704
|
+
await import_node_fs44.promises.unlink(lockPath).catch(() => {
|
|
22701
23705
|
});
|
|
22702
23706
|
};
|
|
22703
23707
|
} catch (err) {
|
|
22704
23708
|
if (err.code !== "EEXIST") return null;
|
|
22705
23709
|
try {
|
|
22706
|
-
const stat = await
|
|
23710
|
+
const stat = await import_node_fs44.promises.stat(lockPath);
|
|
22707
23711
|
if (Date.now() - stat.mtimeMs > STALE_LOCK_MS) {
|
|
22708
|
-
await
|
|
23712
|
+
await import_node_fs44.promises.unlink(lockPath).catch(() => {
|
|
22709
23713
|
});
|
|
22710
23714
|
return acquireSpawnLock(scanPath);
|
|
22711
23715
|
}
|
|
@@ -22734,13 +23738,13 @@ async function healthIsForProject(restPort, project) {
|
|
|
22734
23738
|
return false;
|
|
22735
23739
|
}
|
|
22736
23740
|
function daemonLogPath(projectPath3) {
|
|
22737
|
-
return
|
|
23741
|
+
return import_node_path78.default.join(projectPath3, "neat-out", "daemon.log");
|
|
22738
23742
|
}
|
|
22739
23743
|
function spawnDaemonDetached(spec) {
|
|
22740
|
-
const here =
|
|
23744
|
+
const here = import_node_path78.default.dirname((0, import_node_url4.fileURLToPath)(importMetaUrl));
|
|
22741
23745
|
const candidates = [
|
|
22742
|
-
|
|
22743
|
-
|
|
23746
|
+
import_node_path78.default.join(here, "neatd.cjs"),
|
|
23747
|
+
import_node_path78.default.join(here, "neatd.js")
|
|
22744
23748
|
];
|
|
22745
23749
|
let entry2 = null;
|
|
22746
23750
|
const fsSync = require("fs");
|
|
@@ -22770,7 +23774,7 @@ function spawnDaemonDetached(spec) {
|
|
|
22770
23774
|
let logFd = null;
|
|
22771
23775
|
if (spec) {
|
|
22772
23776
|
const logPath = daemonLogPath(spec.projectPath);
|
|
22773
|
-
fsSync.mkdirSync(
|
|
23777
|
+
fsSync.mkdirSync(import_node_path78.default.dirname(logPath), { recursive: true });
|
|
22774
23778
|
logFd = fsSync.openSync(logPath, "a");
|
|
22775
23779
|
}
|
|
22776
23780
|
const child = (0, import_node_child_process3.spawn)(process.execPath, [entry2, "start"], {
|
|
@@ -22809,7 +23813,7 @@ async function runOrchestrator(opts) {
|
|
|
22809
23813
|
browser: "skipped"
|
|
22810
23814
|
}
|
|
22811
23815
|
};
|
|
22812
|
-
const stat = await
|
|
23816
|
+
const stat = await import_node_fs44.promises.stat(opts.scanPath).catch(() => null);
|
|
22813
23817
|
if (!stat || !stat.isDirectory()) {
|
|
22814
23818
|
console.error(`neat: ${opts.scanPath} is not a directory`);
|
|
22815
23819
|
result.exitCode = 2;
|
|
@@ -22969,7 +23973,7 @@ async function runOrchestrator(opts) {
|
|
|
22969
23973
|
result.steps.browser = openBrowser(dashboardUrl);
|
|
22970
23974
|
}
|
|
22971
23975
|
const daemonRunning = result.steps.daemon === "spawned" || result.steps.daemon === "already-running";
|
|
22972
|
-
const daemonLog = daemonRunning ?
|
|
23976
|
+
const daemonLog = daemonRunning ? import_node_path78.default.relative(opts.scanPath, daemonLogPath(opts.scanPath)) : null;
|
|
22973
23977
|
printSummary(result, graph, dashboardUrl, daemonLog);
|
|
22974
23978
|
return result;
|
|
22975
23979
|
}
|
|
@@ -23428,27 +24432,27 @@ async function runConnectorCommand(rawArgs, deps = {}) {
|
|
|
23428
24432
|
|
|
23429
24433
|
// src/hooks-cli.ts
|
|
23430
24434
|
init_cjs_shims();
|
|
23431
|
-
var
|
|
24435
|
+
var import_node_path79 = __toESM(require("path"), 1);
|
|
23432
24436
|
var import_node_os5 = __toESM(require("os"), 1);
|
|
23433
|
-
var
|
|
24437
|
+
var import_node_fs45 = require("fs");
|
|
23434
24438
|
var import_node_url5 = require("url");
|
|
23435
24439
|
var HOOK_FILENAME = "neat-search-nudge.mjs";
|
|
23436
24440
|
var GUIDE_FILENAME = "GRAPH_FIRST.md";
|
|
23437
24441
|
var GUIDE_INSTALL_NAME = "neat-graph-first.md";
|
|
23438
24442
|
var HOOK_MATCHER = "Grep|Glob|Bash";
|
|
23439
24443
|
function moduleDir() {
|
|
23440
|
-
return typeof __dirname !== "undefined" ? __dirname :
|
|
24444
|
+
return typeof __dirname !== "undefined" ? __dirname : import_node_path79.default.dirname((0, import_node_url5.fileURLToPath)(importMetaUrl));
|
|
23441
24445
|
}
|
|
23442
24446
|
async function readSkillAsset(rel) {
|
|
23443
24447
|
const here = moduleDir();
|
|
23444
24448
|
const candidates = [
|
|
23445
|
-
|
|
23446
|
-
|
|
23447
|
-
|
|
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)
|
|
23448
24452
|
];
|
|
23449
24453
|
for (const candidate of candidates) {
|
|
23450
24454
|
try {
|
|
23451
|
-
return await
|
|
24455
|
+
return await import_node_fs45.promises.readFile(candidate, "utf8");
|
|
23452
24456
|
} catch {
|
|
23453
24457
|
}
|
|
23454
24458
|
}
|
|
@@ -23458,17 +24462,17 @@ async function readSkillAsset(rel) {
|
|
|
23458
24462
|
}
|
|
23459
24463
|
function neatHome3() {
|
|
23460
24464
|
const override = process.env.NEAT_HOME;
|
|
23461
|
-
if (override && override.length > 0) return
|
|
23462
|
-
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");
|
|
23463
24467
|
}
|
|
23464
24468
|
function claudeSettingsPath() {
|
|
23465
24469
|
const override = process.env.NEAT_CLAUDE_SETTINGS;
|
|
23466
|
-
if (override && override.length > 0) return
|
|
24470
|
+
if (override && override.length > 0) return import_node_path79.default.resolve(override);
|
|
23467
24471
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? import_node_os5.default.homedir();
|
|
23468
|
-
return
|
|
24472
|
+
return import_node_path79.default.join(home, ".claude", "settings.json");
|
|
23469
24473
|
}
|
|
23470
24474
|
function installedHookPath() {
|
|
23471
|
-
return
|
|
24475
|
+
return import_node_path79.default.join(neatHome3(), "hooks", HOOK_FILENAME);
|
|
23472
24476
|
}
|
|
23473
24477
|
function isNeatSearchEntry(entry2) {
|
|
23474
24478
|
return (entry2.hooks ?? []).some(
|
|
@@ -23501,14 +24505,14 @@ async function runHooks(opts) {
|
|
|
23501
24505
|
const hookScript = await readSkillAsset(`hooks/${HOOK_FILENAME}`);
|
|
23502
24506
|
const guide = await readSkillAsset(GUIDE_FILENAME);
|
|
23503
24507
|
const scriptPath = installedHookPath();
|
|
23504
|
-
await
|
|
23505
|
-
await
|
|
23506
|
-
const guidePath =
|
|
23507
|
-
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");
|
|
23508
24512
|
const settingsFile = claudeSettingsPath();
|
|
23509
24513
|
let settings = {};
|
|
23510
24514
|
try {
|
|
23511
|
-
settings = JSON.parse(await
|
|
24515
|
+
settings = JSON.parse(await import_node_fs45.promises.readFile(settingsFile, "utf8"));
|
|
23512
24516
|
} catch (err) {
|
|
23513
24517
|
if (err.code !== "ENOENT") {
|
|
23514
24518
|
console.error(
|
|
@@ -23530,8 +24534,8 @@ async function runHooks(opts) {
|
|
|
23530
24534
|
...settings,
|
|
23531
24535
|
hooks: { ...hooks, PreToolUse: preToolUse }
|
|
23532
24536
|
};
|
|
23533
|
-
await
|
|
23534
|
-
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");
|
|
23535
24539
|
console.log(`neat hooks: installed the search-nudge hook`);
|
|
23536
24540
|
console.log(` script: ${scriptPath}`);
|
|
23537
24541
|
console.log(` settings: ${settingsFile} (PreToolUse \u2192 ${HOOK_MATCHER})`);
|
|
@@ -23602,9 +24606,9 @@ async function runHooksCommand(args) {
|
|
|
23602
24606
|
|
|
23603
24607
|
// src/codex-cli.ts
|
|
23604
24608
|
init_cjs_shims();
|
|
23605
|
-
var
|
|
24609
|
+
var import_node_path80 = __toESM(require("path"), 1);
|
|
23606
24610
|
var import_node_os6 = __toESM(require("os"), 1);
|
|
23607
|
-
var
|
|
24611
|
+
var import_node_fs46 = require("fs");
|
|
23608
24612
|
var import_node_util = require("util");
|
|
23609
24613
|
var import_smol_toml5 = require("smol-toml");
|
|
23610
24614
|
var CODEX_MCP_SERVER = {
|
|
@@ -23622,14 +24626,14 @@ var NEAT_GRAPH_FIRST_START = "<!-- neat:graph-first -->";
|
|
|
23622
24626
|
var NEAT_GRAPH_FIRST_END = "<!-- /neat:graph-first -->";
|
|
23623
24627
|
function codexConfigPath() {
|
|
23624
24628
|
const override = process.env.NEAT_CODEX_CONFIG;
|
|
23625
|
-
if (override && override.length > 0) return
|
|
24629
|
+
if (override && override.length > 0) return import_node_path80.default.resolve(override);
|
|
23626
24630
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? import_node_os6.default.homedir();
|
|
23627
|
-
return
|
|
24631
|
+
return import_node_path80.default.join(home, ".codex", "config.toml");
|
|
23628
24632
|
}
|
|
23629
24633
|
function agentsFilePath() {
|
|
23630
24634
|
const override = process.env.NEAT_CODEX_AGENTS;
|
|
23631
|
-
if (override && override.length > 0) return
|
|
23632
|
-
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");
|
|
23633
24637
|
}
|
|
23634
24638
|
function isTableHeader(line) {
|
|
23635
24639
|
return /^\s*\[\[?[^\]]+\]\]?\s*$/.test(line);
|
|
@@ -23763,7 +24767,7 @@ async function runCodex(opts) {
|
|
|
23763
24767
|
const agentsPath = agentsFilePath();
|
|
23764
24768
|
let configRaw = "";
|
|
23765
24769
|
try {
|
|
23766
|
-
configRaw = await
|
|
24770
|
+
configRaw = await import_node_fs46.promises.readFile(configPath, "utf8");
|
|
23767
24771
|
} catch (err) {
|
|
23768
24772
|
if (err.code !== "ENOENT") {
|
|
23769
24773
|
console.error(`neat codex: failed to read ${configPath} \u2014 ${err.message}`);
|
|
@@ -23772,7 +24776,7 @@ async function runCodex(opts) {
|
|
|
23772
24776
|
}
|
|
23773
24777
|
let agentsRaw = "";
|
|
23774
24778
|
try {
|
|
23775
|
-
agentsRaw = await
|
|
24779
|
+
agentsRaw = await import_node_fs46.promises.readFile(agentsPath, "utf8");
|
|
23776
24780
|
} catch (err) {
|
|
23777
24781
|
if (err.code !== "ENOENT") {
|
|
23778
24782
|
console.error(`neat codex: failed to read ${agentsPath} \u2014 ${err.message}`);
|
|
@@ -23812,15 +24816,15 @@ async function runCodex(opts) {
|
|
|
23812
24816
|
return { exitCode: 0 };
|
|
23813
24817
|
}
|
|
23814
24818
|
if (config.changed) {
|
|
23815
|
-
await
|
|
23816
|
-
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");
|
|
23817
24821
|
console.log(`neat codex: wrote [mcp_servers.neat] to ${configPath}`);
|
|
23818
24822
|
} else {
|
|
23819
24823
|
console.log(`neat codex: ${configPath} already has NEAT's MCP server`);
|
|
23820
24824
|
}
|
|
23821
24825
|
if (agents.changed) {
|
|
23822
|
-
await
|
|
23823
|
-
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");
|
|
23824
24828
|
console.log(`neat codex: wrote the graph-first block to ${agentsPath}`);
|
|
23825
24829
|
} else {
|
|
23826
24830
|
console.log(`neat codex: ${agentsPath} already has the graph-first block`);
|
|
@@ -23877,9 +24881,9 @@ async function runCodexCommand(args) {
|
|
|
23877
24881
|
|
|
23878
24882
|
// src/editors-cli.ts
|
|
23879
24883
|
init_cjs_shims();
|
|
23880
|
-
var
|
|
24884
|
+
var import_node_path81 = __toESM(require("path"), 1);
|
|
23881
24885
|
var import_node_os7 = __toESM(require("os"), 1);
|
|
23882
|
-
var
|
|
24886
|
+
var import_node_fs47 = require("fs");
|
|
23883
24887
|
var import_node_util2 = require("util");
|
|
23884
24888
|
var jsonc = __toESM(require("jsonc-parser"), 1);
|
|
23885
24889
|
var NEAT_MCP_SERVER = {
|
|
@@ -23903,17 +24907,17 @@ function homeDir() {
|
|
|
23903
24907
|
}
|
|
23904
24908
|
function xdgConfigDir() {
|
|
23905
24909
|
const xdg = process.env.XDG_CONFIG_HOME;
|
|
23906
|
-
return xdg && xdg.length > 0 ?
|
|
24910
|
+
return xdg && xdg.length > 0 ? import_node_path81.default.resolve(xdg) : import_node_path81.default.join(homeDir(), ".config");
|
|
23907
24911
|
}
|
|
23908
24912
|
function envOverride(name) {
|
|
23909
24913
|
const v = process.env[name];
|
|
23910
|
-
return v && v.length > 0 ?
|
|
24914
|
+
return v && v.length > 0 ? import_node_path81.default.resolve(v) : void 0;
|
|
23911
24915
|
}
|
|
23912
24916
|
var CURSOR_CLIENT = {
|
|
23913
24917
|
id: "cursor",
|
|
23914
24918
|
label: "Cursor",
|
|
23915
24919
|
docsUrl: "https://docs.cursor.com/context/mcp",
|
|
23916
|
-
mcpConfigPath: () => envOverride("NEAT_CURSOR_CONFIG") ??
|
|
24920
|
+
mcpConfigPath: () => envOverride("NEAT_CURSOR_CONFIG") ?? import_node_path81.default.join(homeDir(), ".cursor", "mcp.json"),
|
|
23917
24921
|
mcpContainerKey: "mcpServers",
|
|
23918
24922
|
format: "json",
|
|
23919
24923
|
// Cursor still reads a single `.cursorrules` at the project root (the modern
|
|
@@ -23925,7 +24929,7 @@ var DEVIN_CLIENT = {
|
|
|
23925
24929
|
id: "devin",
|
|
23926
24930
|
label: "Devin Desktop (Cascade)",
|
|
23927
24931
|
docsUrl: "https://docs.devin.ai/desktop/cascade/mcp",
|
|
23928
|
-
mcpConfigPath: () => envOverride("NEAT_DEVIN_CONFIG") ??
|
|
24932
|
+
mcpConfigPath: () => envOverride("NEAT_DEVIN_CONFIG") ?? import_node_path81.default.join(homeDir(), ".codeium", "windsurf", "mcp_config.json"),
|
|
23929
24933
|
mcpContainerKey: "mcpServers",
|
|
23930
24934
|
format: "json",
|
|
23931
24935
|
rulesFileName: ".windsurfrules"
|
|
@@ -23934,7 +24938,7 @@ var GEMINI_CLIENT = {
|
|
|
23934
24938
|
id: "gemini",
|
|
23935
24939
|
label: "Gemini CLI",
|
|
23936
24940
|
docsUrl: "https://github.com/google-gemini/gemini-cli/blob/main/docs/tools/mcp-server.md",
|
|
23937
|
-
mcpConfigPath: () => envOverride("NEAT_GEMINI_CONFIG") ??
|
|
24941
|
+
mcpConfigPath: () => envOverride("NEAT_GEMINI_CONFIG") ?? import_node_path81.default.join(homeDir(), ".gemini", "settings.json"),
|
|
23938
24942
|
mcpContainerKey: "mcpServers",
|
|
23939
24943
|
format: "json",
|
|
23940
24944
|
rulesFileName: "GEMINI.md"
|
|
@@ -23943,7 +24947,7 @@ var QWEN_CLIENT = {
|
|
|
23943
24947
|
id: "qwen",
|
|
23944
24948
|
label: "Qwen Code",
|
|
23945
24949
|
docsUrl: "https://qwenlm.github.io/qwen-code-docs/en/users/features/mcp/",
|
|
23946
|
-
mcpConfigPath: () => envOverride("NEAT_QWEN_CONFIG") ??
|
|
24950
|
+
mcpConfigPath: () => envOverride("NEAT_QWEN_CONFIG") ?? import_node_path81.default.join(homeDir(), ".qwen", "settings.json"),
|
|
23947
24951
|
mcpContainerKey: "mcpServers",
|
|
23948
24952
|
format: "json",
|
|
23949
24953
|
rulesFileName: "QWEN.md"
|
|
@@ -23952,7 +24956,7 @@ var AMAZONQ_CLIENT = {
|
|
|
23952
24956
|
id: "amazonq",
|
|
23953
24957
|
label: "Amazon Q Developer CLI",
|
|
23954
24958
|
docsUrl: "https://docs.aws.amazon.com/amazonq/latest/qdeveloper-ug/command-line-mcp-configuration.html",
|
|
23955
|
-
mcpConfigPath: () => envOverride("NEAT_AMAZONQ_CONFIG") ??
|
|
24959
|
+
mcpConfigPath: () => envOverride("NEAT_AMAZONQ_CONFIG") ?? import_node_path81.default.join(homeDir(), ".aws", "amazonq", "mcp.json"),
|
|
23956
24960
|
mcpContainerKey: "mcpServers",
|
|
23957
24961
|
format: "json"
|
|
23958
24962
|
};
|
|
@@ -23960,7 +24964,7 @@ var ROOCODE_CLIENT = {
|
|
|
23960
24964
|
id: "roocode",
|
|
23961
24965
|
label: "Roo Code",
|
|
23962
24966
|
docsUrl: "https://roocodeinc.github.io/Roo-Code/features/mcp/using-mcp-in-roo",
|
|
23963
|
-
mcpConfigPath: () => envOverride("NEAT_ROOCODE_CONFIG") ??
|
|
24967
|
+
mcpConfigPath: () => envOverride("NEAT_ROOCODE_CONFIG") ?? import_node_path81.default.join(process.cwd(), ".roo", "mcp.json"),
|
|
23964
24968
|
mcpContainerKey: "mcpServers",
|
|
23965
24969
|
format: "json"
|
|
23966
24970
|
};
|
|
@@ -23973,9 +24977,9 @@ var ZED_CLIENT = {
|
|
|
23973
24977
|
if (override) return override;
|
|
23974
24978
|
if (process.platform === "win32") {
|
|
23975
24979
|
const appData = process.env.APPDATA;
|
|
23976
|
-
if (appData && appData.length > 0) return
|
|
24980
|
+
if (appData && appData.length > 0) return import_node_path81.default.join(appData, "Zed", "settings.json");
|
|
23977
24981
|
}
|
|
23978
|
-
return
|
|
24982
|
+
return import_node_path81.default.join(homeDir(), ".config", "zed", "settings.json");
|
|
23979
24983
|
},
|
|
23980
24984
|
mcpContainerKey: "context_servers",
|
|
23981
24985
|
format: "jsonc",
|
|
@@ -23985,7 +24989,7 @@ var OPENCODE_CLIENT = {
|
|
|
23985
24989
|
id: "opencode",
|
|
23986
24990
|
label: "OpenCode",
|
|
23987
24991
|
docsUrl: "https://opencode.ai/docs/mcp-servers/",
|
|
23988
|
-
mcpConfigPath: () => envOverride("NEAT_OPENCODE_CONFIG") ??
|
|
24992
|
+
mcpConfigPath: () => envOverride("NEAT_OPENCODE_CONFIG") ?? import_node_path81.default.join(xdgConfigDir(), "opencode", "opencode.json"),
|
|
23989
24993
|
mcpContainerKey: "mcp",
|
|
23990
24994
|
format: "json",
|
|
23991
24995
|
serverEntry: NEAT_OPENCODE_SERVER,
|
|
@@ -23995,7 +24999,7 @@ var CRUSH_CLIENT = {
|
|
|
23995
24999
|
id: "crush",
|
|
23996
25000
|
label: "Crush",
|
|
23997
25001
|
docsUrl: "https://charmbracelet-crush.mintlify.app/configuration/mcp",
|
|
23998
|
-
mcpConfigPath: () => envOverride("NEAT_CRUSH_CONFIG") ??
|
|
25002
|
+
mcpConfigPath: () => envOverride("NEAT_CRUSH_CONFIG") ?? import_node_path81.default.join(xdgConfigDir(), "crush", "crush.json"),
|
|
23999
25003
|
mcpContainerKey: "mcp",
|
|
24000
25004
|
format: "json",
|
|
24001
25005
|
serverEntry: NEAT_CRUSH_SERVER,
|
|
@@ -24058,7 +25062,7 @@ async function planMcp(client, mcpPath) {
|
|
|
24058
25062
|
const serverEntry = client.serverEntry ?? NEAT_MCP_SERVER;
|
|
24059
25063
|
let raw = "";
|
|
24060
25064
|
try {
|
|
24061
|
-
raw = await
|
|
25065
|
+
raw = await import_node_fs47.promises.readFile(mcpPath, "utf8");
|
|
24062
25066
|
} catch (err) {
|
|
24063
25067
|
const e = err;
|
|
24064
25068
|
if (e.code === "ENOENT") {
|
|
@@ -24100,7 +25104,7 @@ async function runEditorInstall(client, opts) {
|
|
|
24100
25104
|
const mcpPath = client.mcpConfigPath();
|
|
24101
25105
|
const serverEntry = client.serverEntry ?? NEAT_MCP_SERVER;
|
|
24102
25106
|
const hasRules = typeof client.rulesFileName === "string";
|
|
24103
|
-
const rulesPath = hasRules ?
|
|
25107
|
+
const rulesPath = hasRules ? import_node_path81.default.join(opts.projectDir, client.rulesFileName) : "";
|
|
24104
25108
|
const mcp = await planMcp(client, mcpPath);
|
|
24105
25109
|
if (mcp === null) return { exitCode: 1 };
|
|
24106
25110
|
let existingRules = "";
|
|
@@ -24109,7 +25113,7 @@ async function runEditorInstall(client, opts) {
|
|
|
24109
25113
|
let block = "";
|
|
24110
25114
|
if (hasRules) {
|
|
24111
25115
|
try {
|
|
24112
|
-
existingRules = await
|
|
25116
|
+
existingRules = await import_node_fs47.promises.readFile(rulesPath, "utf8");
|
|
24113
25117
|
} catch (err) {
|
|
24114
25118
|
if (err.code !== "ENOENT") {
|
|
24115
25119
|
console.error(`neat ${client.id}: failed to read ${rulesPath} \u2014 ${err.message}`);
|
|
@@ -24143,11 +25147,11 @@ async function runEditorInstall(client, opts) {
|
|
|
24143
25147
|
);
|
|
24144
25148
|
return { exitCode: 0 };
|
|
24145
25149
|
}
|
|
24146
|
-
await
|
|
24147
|
-
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");
|
|
24148
25152
|
if (hasRules) {
|
|
24149
|
-
await
|
|
24150
|
-
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");
|
|
24151
25155
|
}
|
|
24152
25156
|
console.log(`neat ${client.id}: wired NEAT into ${client.label}`);
|
|
24153
25157
|
console.log(` MCP server: ${mcpPath} (${client.mcpContainerKey}.neat \u2192 npx -y @neat.is/mcp)`);
|
|
@@ -24183,11 +25187,11 @@ function usage3(client) {
|
|
|
24183
25187
|
}
|
|
24184
25188
|
async function runEditorCommand(clientId, args, projectDir = process.cwd()) {
|
|
24185
25189
|
const client = CLIENTS[clientId];
|
|
24186
|
-
let
|
|
25190
|
+
let apply6 = false;
|
|
24187
25191
|
for (const arg of args) {
|
|
24188
25192
|
switch (arg) {
|
|
24189
25193
|
case "--apply":
|
|
24190
|
-
|
|
25194
|
+
apply6 = true;
|
|
24191
25195
|
break;
|
|
24192
25196
|
case "-h":
|
|
24193
25197
|
case "--help":
|
|
@@ -24200,7 +25204,7 @@ async function runEditorCommand(clientId, args, projectDir = process.cwd()) {
|
|
|
24200
25204
|
}
|
|
24201
25205
|
}
|
|
24202
25206
|
try {
|
|
24203
|
-
const { exitCode } = await runEditorInstall(client, { apply:
|
|
25207
|
+
const { exitCode } = await runEditorInstall(client, { apply: apply6, projectDir });
|
|
24204
25208
|
return exitCode;
|
|
24205
25209
|
} catch (err) {
|
|
24206
25210
|
console.error(err.message);
|
|
@@ -24210,11 +25214,11 @@ async function runEditorCommand(clientId, args, projectDir = process.cwd()) {
|
|
|
24210
25214
|
|
|
24211
25215
|
// src/monitor.ts
|
|
24212
25216
|
init_cjs_shims();
|
|
24213
|
-
var
|
|
25217
|
+
var import_types89 = require("@neat.is/types");
|
|
24214
25218
|
|
|
24215
25219
|
// src/cli-client.ts
|
|
24216
25220
|
init_cjs_shims();
|
|
24217
|
-
var
|
|
25221
|
+
var import_types88 = require("@neat.is/types");
|
|
24218
25222
|
var HttpError = class extends Error {
|
|
24219
25223
|
constructor(status2, message, responseBody = "") {
|
|
24220
25224
|
super(message);
|
|
@@ -24239,10 +25243,10 @@ function createHttpClient(baseUrl, bearerToken) {
|
|
|
24239
25243
|
const root = baseUrl.replace(/\/$/, "");
|
|
24240
25244
|
const authHeader = bearerToken && bearerToken.length > 0 ? { authorization: `Bearer ${bearerToken}` } : {};
|
|
24241
25245
|
return {
|
|
24242
|
-
async get(
|
|
25246
|
+
async get(path84) {
|
|
24243
25247
|
let res;
|
|
24244
25248
|
try {
|
|
24245
|
-
res = await fetch(`${root}${
|
|
25249
|
+
res = await fetch(`${root}${path84}`, {
|
|
24246
25250
|
headers: { ...authHeader }
|
|
24247
25251
|
});
|
|
24248
25252
|
} catch (err) {
|
|
@@ -24254,16 +25258,16 @@ function createHttpClient(baseUrl, bearerToken) {
|
|
|
24254
25258
|
const body = await res.text().catch(() => "");
|
|
24255
25259
|
throw new HttpError(
|
|
24256
25260
|
res.status,
|
|
24257
|
-
`${res.status} ${res.statusText} on GET ${
|
|
25261
|
+
`${res.status} ${res.statusText} on GET ${path84}: ${body}`,
|
|
24258
25262
|
body
|
|
24259
25263
|
);
|
|
24260
25264
|
}
|
|
24261
25265
|
return await res.json();
|
|
24262
25266
|
},
|
|
24263
|
-
async post(
|
|
25267
|
+
async post(path84, body) {
|
|
24264
25268
|
let res;
|
|
24265
25269
|
try {
|
|
24266
|
-
res = await fetch(`${root}${
|
|
25270
|
+
res = await fetch(`${root}${path84}`, {
|
|
24267
25271
|
method: "POST",
|
|
24268
25272
|
headers: { "content-type": "application/json", ...authHeader },
|
|
24269
25273
|
body: JSON.stringify(body)
|
|
@@ -24277,7 +25281,7 @@ function createHttpClient(baseUrl, bearerToken) {
|
|
|
24277
25281
|
const text = await res.text().catch(() => "");
|
|
24278
25282
|
throw new HttpError(
|
|
24279
25283
|
res.status,
|
|
24280
|
-
`${res.status} ${res.statusText} on POST ${
|
|
25284
|
+
`${res.status} ${res.statusText} on POST ${path84}: ${text}`,
|
|
24281
25285
|
text
|
|
24282
25286
|
);
|
|
24283
25287
|
}
|
|
@@ -24291,12 +25295,12 @@ function projectPath(project, suffix) {
|
|
|
24291
25295
|
}
|
|
24292
25296
|
async function runRootCause(client, input) {
|
|
24293
25297
|
const qs = input.errorId ? `?errorId=${encodeURIComponent(input.errorId)}` : "";
|
|
24294
|
-
const
|
|
25298
|
+
const path84 = projectPath(
|
|
24295
25299
|
input.project,
|
|
24296
25300
|
`/graph/root-cause/${encodeURIComponent(input.errorNode)}${qs}`
|
|
24297
25301
|
);
|
|
24298
25302
|
try {
|
|
24299
|
-
const result = await client.get(
|
|
25303
|
+
const result = await client.get(path84);
|
|
24300
25304
|
const arrowPath = result.traversalPath.join(" \u2190 ");
|
|
24301
25305
|
const provenances = result.edgeProvenances.length ? result.edgeProvenances.join(", ") : "(direct, no edges traversed)";
|
|
24302
25306
|
const summary = `Root cause for ${input.errorNode} is ${result.rootCauseNode}. ` + result.rootCauseReason + (result.fixRecommendation ? ` Recommended fix: ${result.fixRecommendation}.` : "");
|
|
@@ -24322,12 +25326,12 @@ async function runRootCause(client, input) {
|
|
|
24322
25326
|
}
|
|
24323
25327
|
async function runBlastRadius(client, input) {
|
|
24324
25328
|
const qs = input.depth !== void 0 ? `?depth=${input.depth}` : "";
|
|
24325
|
-
const
|
|
25329
|
+
const path84 = projectPath(
|
|
24326
25330
|
input.project,
|
|
24327
25331
|
`/graph/blast-radius/${encodeURIComponent(input.nodeId)}${qs}`
|
|
24328
25332
|
);
|
|
24329
25333
|
try {
|
|
24330
|
-
const result = await client.get(
|
|
25334
|
+
const result = await client.get(path84);
|
|
24331
25335
|
if (result.totalAffected === 0) {
|
|
24332
25336
|
return {
|
|
24333
25337
|
summary: `${result.origin} has no dependents. Nothing else would break if it failed.`
|
|
@@ -24356,17 +25360,17 @@ async function runBlastRadius(client, input) {
|
|
|
24356
25360
|
}
|
|
24357
25361
|
}
|
|
24358
25362
|
function formatBlastEntry(n) {
|
|
24359
|
-
const tag = n.edgeProvenance ===
|
|
25363
|
+
const tag = n.edgeProvenance === import_types88.Provenance.STALE ? " [STALE \u2014 last seen too long ago]" : "";
|
|
24360
25364
|
return ` \u2022 ${n.nodeId} (distance ${n.distance}, ${n.edgeProvenance})${tag}`;
|
|
24361
25365
|
}
|
|
24362
25366
|
async function runDependencies(client, input) {
|
|
24363
25367
|
const depth = input.depth ?? 3;
|
|
24364
|
-
const
|
|
25368
|
+
const path84 = projectPath(
|
|
24365
25369
|
input.project,
|
|
24366
25370
|
`/graph/dependencies/${encodeURIComponent(input.nodeId)}?depth=${depth}`
|
|
24367
25371
|
);
|
|
24368
25372
|
try {
|
|
24369
|
-
const result = await client.get(
|
|
25373
|
+
const result = await client.get(path84);
|
|
24370
25374
|
if (result.total === 0) {
|
|
24371
25375
|
return {
|
|
24372
25376
|
summary: depth === 1 ? `${input.nodeId} has no direct dependencies in the graph.` : `${input.nodeId} has no dependencies (BFS to depth ${depth}).`
|
|
@@ -24413,7 +25417,7 @@ async function runObservedDependencies(client, input) {
|
|
|
24413
25417
|
if (result.observed) {
|
|
24414
25418
|
return {
|
|
24415
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.`,
|
|
24416
|
-
provenance:
|
|
25420
|
+
provenance: import_types88.Provenance.OBSERVED
|
|
24417
25421
|
};
|
|
24418
25422
|
}
|
|
24419
25423
|
const note = result.hasExtractedOutbound ? " Static (EXTRACTED) dependencies exist but no runtime traffic has been seen \u2014 is OTel running?" : "";
|
|
@@ -24423,7 +25427,7 @@ async function runObservedDependencies(client, input) {
|
|
|
24423
25427
|
return {
|
|
24424
25428
|
summary: `${input.nodeId} has ${result.dependencies.length} runtime dependenc${result.dependencies.length === 1 ? "y" : "ies"} confirmed by OTel.`,
|
|
24425
25429
|
block: blockLines.join("\n"),
|
|
24426
|
-
provenance:
|
|
25430
|
+
provenance: import_types88.Provenance.OBSERVED
|
|
24427
25431
|
};
|
|
24428
25432
|
} catch (err) {
|
|
24429
25433
|
if (err instanceof HttpError && err.status === 404) {
|
|
@@ -24458,9 +25462,9 @@ function formatDuration(ms) {
|
|
|
24458
25462
|
return `${Math.round(h / 24)}d`;
|
|
24459
25463
|
}
|
|
24460
25464
|
async function runIncidents(client, input) {
|
|
24461
|
-
const
|
|
25465
|
+
const path84 = input.nodeId ? projectPath(input.project, `/incidents/${encodeURIComponent(input.nodeId)}`) : projectPath(input.project, "/incidents");
|
|
24462
25466
|
try {
|
|
24463
|
-
const body = await client.get(
|
|
25467
|
+
const body = await client.get(path84);
|
|
24464
25468
|
const events = body.events;
|
|
24465
25469
|
if (events.length === 0) {
|
|
24466
25470
|
return {
|
|
@@ -24477,7 +25481,7 @@ async function runIncidents(client, input) {
|
|
|
24477
25481
|
return {
|
|
24478
25482
|
summary: `${target} has ${body.total} recorded incident${body.total === 1 ? "" : "s"}; showing the ${ordered.length} most recent.`,
|
|
24479
25483
|
block: blockLines.join("\n"),
|
|
24480
|
-
provenance:
|
|
25484
|
+
provenance: import_types88.Provenance.OBSERVED
|
|
24481
25485
|
};
|
|
24482
25486
|
} catch (err) {
|
|
24483
25487
|
if (err instanceof HttpError && err.status === 404) {
|
|
@@ -24586,7 +25590,7 @@ async function runStaleEdges(client, input) {
|
|
|
24586
25590
|
return {
|
|
24587
25591
|
summary: `${events.length} stale-edge transition${events.length === 1 ? "" : "s"} recorded${input.edgeType ? ` for ${input.edgeType}` : ""}.`,
|
|
24588
25592
|
block: blockLines.join("\n"),
|
|
24589
|
-
provenance:
|
|
25593
|
+
provenance: import_types88.Provenance.STALE
|
|
24590
25594
|
};
|
|
24591
25595
|
}
|
|
24592
25596
|
async function runPolicies(client, input) {
|
|
@@ -24745,10 +25749,10 @@ async function pushSnapshotToRemote(input) {
|
|
|
24745
25749
|
|
|
24746
25750
|
// src/monitor.ts
|
|
24747
25751
|
var OBSERVED_DEP_EDGE_TYPES = /* @__PURE__ */ new Set([
|
|
24748
|
-
|
|
24749
|
-
|
|
24750
|
-
|
|
24751
|
-
|
|
25752
|
+
import_types89.EdgeType.CALLS,
|
|
25753
|
+
import_types89.EdgeType.CONNECTS_TO,
|
|
25754
|
+
import_types89.EdgeType.PUBLISHES_TO,
|
|
25755
|
+
import_types89.EdgeType.CONSUMES_FROM
|
|
24752
25756
|
]);
|
|
24753
25757
|
function divergenceKey(d) {
|
|
24754
25758
|
const column = "column" in d && d.column ? d.column : "";
|
|
@@ -24793,7 +25797,7 @@ function formatDivergenceLine2(d) {
|
|
|
24793
25797
|
}
|
|
24794
25798
|
}
|
|
24795
25799
|
function formatStaleLine(edgeId) {
|
|
24796
|
-
const parsed = (0,
|
|
25800
|
+
const parsed = (0, import_types89.parseEdgeId)(edgeId);
|
|
24797
25801
|
if (parsed) {
|
|
24798
25802
|
return `\u22EF stale ${parsed.source} \u2192 ${parsed.target} (observed edge went quiet)`;
|
|
24799
25803
|
}
|
|
@@ -24806,7 +25810,7 @@ function divergenceJson(d) {
|
|
|
24806
25810
|
return JSON.stringify({ kind: "divergence", ...d });
|
|
24807
25811
|
}
|
|
24808
25812
|
function staleJson(edgeId) {
|
|
24809
|
-
const parsed = (0,
|
|
25813
|
+
const parsed = (0, import_types89.parseEdgeId)(edgeId);
|
|
24810
25814
|
return JSON.stringify({
|
|
24811
25815
|
kind: "stale",
|
|
24812
25816
|
edgeId,
|
|
@@ -24876,7 +25880,7 @@ var MonitorEmitter = class {
|
|
|
24876
25880
|
// ignores non-OBSERVED edges and non-dependency edge types (structural
|
|
24877
25881
|
// ownership), so only real runtime dependencies reach stdout.
|
|
24878
25882
|
emitObservedEdge(edge) {
|
|
24879
|
-
if (edge.provenance !==
|
|
25883
|
+
if (edge.provenance !== import_types89.Provenance.OBSERVED) return false;
|
|
24880
25884
|
if (!OBSERVED_DEP_EDGE_TYPES.has(edge.type)) return false;
|
|
24881
25885
|
const key = `edge|${edge.id}`;
|
|
24882
25886
|
if (this.seen.has(key)) return false;
|
|
@@ -25034,7 +26038,7 @@ async function runMonitor(opts) {
|
|
|
25034
26038
|
case "edge-added": {
|
|
25035
26039
|
const payload = safeParse(frame.data);
|
|
25036
26040
|
const edge = payload?.edge;
|
|
25037
|
-
if (edge && edge.provenance ===
|
|
26041
|
+
if (edge && edge.provenance === import_types89.Provenance.OBSERVED) {
|
|
25038
26042
|
emitter.emitObservedEdge(edge);
|
|
25039
26043
|
divergences.schedule();
|
|
25040
26044
|
}
|
|
@@ -25114,7 +26118,7 @@ function sleep(ms, signal) {
|
|
|
25114
26118
|
|
|
25115
26119
|
// src/cli-verbs.ts
|
|
25116
26120
|
init_cjs_shims();
|
|
25117
|
-
var
|
|
26121
|
+
var import_node_path82 = __toESM(require("path"), 1);
|
|
25118
26122
|
async function resolveProjectEntry(opts) {
|
|
25119
26123
|
const entries = await listProjects();
|
|
25120
26124
|
if (opts.project) {
|
|
@@ -25124,7 +26128,7 @@ async function resolveProjectEntry(opts) {
|
|
|
25124
26128
|
const cwd = opts.cwd ?? process.cwd();
|
|
25125
26129
|
const resolvedCwd = await normalizeProjectPath(cwd);
|
|
25126
26130
|
for (const entry2 of entries) {
|
|
25127
|
-
if (resolvedCwd === entry2.path || resolvedCwd.startsWith(`${entry2.path}${
|
|
26131
|
+
if (resolvedCwd === entry2.path || resolvedCwd.startsWith(`${entry2.path}${import_node_path82.default.sep}`)) {
|
|
25128
26132
|
return entry2;
|
|
25129
26133
|
}
|
|
25130
26134
|
}
|
|
@@ -25277,7 +26281,7 @@ async function runSync(opts) {
|
|
|
25277
26281
|
}
|
|
25278
26282
|
|
|
25279
26283
|
// src/cli.ts
|
|
25280
|
-
var
|
|
26284
|
+
var import_types90 = require("@neat.is/types");
|
|
25281
26285
|
function isNpxInvocation() {
|
|
25282
26286
|
if (process.env.npm_command === "exec") return true;
|
|
25283
26287
|
const execpath = process.env.npm_execpath ?? "";
|
|
@@ -25631,15 +26635,15 @@ async function buildPatchSections(services, project) {
|
|
|
25631
26635
|
for (const svc of services) {
|
|
25632
26636
|
const installer = await pickInstaller(svc.dir);
|
|
25633
26637
|
if (!installer) continue;
|
|
25634
|
-
const
|
|
25635
|
-
if (isEmptyPlan(
|
|
25636
|
-
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 });
|
|
25637
26641
|
}
|
|
25638
26642
|
return sections;
|
|
25639
26643
|
}
|
|
25640
26644
|
async function runInit(opts) {
|
|
25641
26645
|
const written = [];
|
|
25642
|
-
const stat = await
|
|
26646
|
+
const stat = await import_node_fs48.promises.stat(opts.scanPath).catch(() => null);
|
|
25643
26647
|
if (!stat || !stat.isDirectory()) {
|
|
25644
26648
|
console.error(`neat init: ${opts.scanPath} is not a directory`);
|
|
25645
26649
|
return { exitCode: 2, writtenFiles: written };
|
|
@@ -25648,13 +26652,13 @@ async function runInit(opts) {
|
|
|
25648
26652
|
printDiscoveryReport(opts, services);
|
|
25649
26653
|
const sections = opts.noInstall ? [] : await buildPatchSections(services, opts.project);
|
|
25650
26654
|
const patch = renderPatch(sections);
|
|
25651
|
-
const patchPath =
|
|
26655
|
+
const patchPath = import_node_path83.default.join(opts.scanPath, "neat.patch");
|
|
25652
26656
|
if (opts.dryRun) {
|
|
25653
|
-
await
|
|
26657
|
+
await import_node_fs48.promises.writeFile(patchPath, patch, "utf8");
|
|
25654
26658
|
written.push(patchPath);
|
|
25655
26659
|
console.log(`dry-run: patch written to ${patchPath}`);
|
|
25656
|
-
const gitignorePath =
|
|
25657
|
-
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);
|
|
25658
26662
|
const verb = gitignoreExists ? "append" : "create";
|
|
25659
26663
|
console.log(`dry-run: would ${verb} ${gitignorePath} (add neat-out/)`);
|
|
25660
26664
|
console.log("rerun without --dry-run to register and snapshot.");
|
|
@@ -25665,9 +26669,9 @@ async function runInit(opts) {
|
|
|
25665
26669
|
const graph = getGraph(graphKey);
|
|
25666
26670
|
const projectPaths = pathsForProject(
|
|
25667
26671
|
graphKey,
|
|
25668
|
-
|
|
26672
|
+
import_node_path83.default.join(opts.scanPath, "neat-out")
|
|
25669
26673
|
);
|
|
25670
|
-
const errorsPath =
|
|
26674
|
+
const errorsPath = import_node_path83.default.join(import_node_path83.default.dirname(opts.outPath), import_node_path83.default.basename(projectPaths.errorsPath));
|
|
25671
26675
|
const result = await extractFromDirectory(graph, opts.scanPath, { errorsPath });
|
|
25672
26676
|
await saveGraphToDisk(graph, opts.outPath);
|
|
25673
26677
|
written.push(opts.outPath);
|
|
@@ -25746,7 +26750,7 @@ async function runInit(opts) {
|
|
|
25746
26750
|
console.log("Run `npm install` (or your language equivalent) to refresh lockfiles.");
|
|
25747
26751
|
}
|
|
25748
26752
|
} else {
|
|
25749
|
-
await
|
|
26753
|
+
await import_node_fs48.promises.writeFile(patchPath, patch, "utf8");
|
|
25750
26754
|
written.push(patchPath);
|
|
25751
26755
|
}
|
|
25752
26756
|
}
|
|
@@ -25786,9 +26790,9 @@ var CLAUDE_SKILL_CONFIG = {
|
|
|
25786
26790
|
};
|
|
25787
26791
|
function claudeConfigPath() {
|
|
25788
26792
|
const override = process.env.NEAT_CLAUDE_CONFIG;
|
|
25789
|
-
if (override && override.length > 0) return
|
|
26793
|
+
if (override && override.length > 0) return import_node_path83.default.resolve(override);
|
|
25790
26794
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
25791
|
-
return
|
|
26795
|
+
return import_node_path83.default.join(home, ".claude.json");
|
|
25792
26796
|
}
|
|
25793
26797
|
async function runSkill(opts) {
|
|
25794
26798
|
const snippet2 = JSON.stringify(CLAUDE_SKILL_CONFIG, null, 2) + "\n";
|
|
@@ -25800,7 +26804,7 @@ async function runSkill(opts) {
|
|
|
25800
26804
|
const target = claudeConfigPath();
|
|
25801
26805
|
let existing = {};
|
|
25802
26806
|
try {
|
|
25803
|
-
existing = JSON.parse(await
|
|
26807
|
+
existing = JSON.parse(await import_node_fs48.promises.readFile(target, "utf8"));
|
|
25804
26808
|
} catch (err) {
|
|
25805
26809
|
if (err.code !== "ENOENT") {
|
|
25806
26810
|
console.error(`neat skill: failed to read ${target} \u2014 ${err.message}`);
|
|
@@ -25812,8 +26816,8 @@ async function runSkill(opts) {
|
|
|
25812
26816
|
...existing,
|
|
25813
26817
|
mcpServers: { ...mcp, neat: CLAUDE_SKILL_CONFIG.mcpServers.neat }
|
|
25814
26818
|
};
|
|
25815
|
-
await
|
|
25816
|
-
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");
|
|
25817
26821
|
console.log(`neat skill: wrote mcpServers.neat to ${target}`);
|
|
25818
26822
|
console.log("restart Claude Code to pick up the new MCP server.");
|
|
25819
26823
|
console.log("");
|
|
@@ -25886,7 +26890,7 @@ async function main() {
|
|
|
25886
26890
|
}
|
|
25887
26891
|
const cmd = argvParsed.positional[0];
|
|
25888
26892
|
const parsed = { ...argvParsed, positional: argvParsed.positional.slice(1) };
|
|
25889
|
-
const { positional, apply:
|
|
26893
|
+
const { positional, apply: apply6, dryRun, noInstall } = parsed;
|
|
25890
26894
|
const project = parsed.project ?? DEFAULT_PROJECT;
|
|
25891
26895
|
if (cmd === "init") {
|
|
25892
26896
|
const target = positional[0];
|
|
@@ -25895,22 +26899,22 @@ async function main() {
|
|
|
25895
26899
|
usage4();
|
|
25896
26900
|
process.exit(2);
|
|
25897
26901
|
}
|
|
25898
|
-
if (
|
|
26902
|
+
if (apply6 && dryRun) {
|
|
25899
26903
|
console.error("neat init: --apply and --dry-run are mutually exclusive");
|
|
25900
26904
|
process.exit(2);
|
|
25901
26905
|
}
|
|
25902
|
-
const scanPath =
|
|
26906
|
+
const scanPath = import_node_path83.default.resolve(target);
|
|
25903
26907
|
const projectExplicit = parsed.project !== null;
|
|
25904
|
-
const projectName = projectExplicit ? project :
|
|
26908
|
+
const projectName = projectExplicit ? project : import_node_path83.default.basename(scanPath);
|
|
25905
26909
|
const projectKey = projectExplicit ? project : DEFAULT_PROJECT;
|
|
25906
|
-
const fallback = pathsForProject(projectKey,
|
|
25907
|
-
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);
|
|
25908
26912
|
const result = await runInit({
|
|
25909
26913
|
scanPath,
|
|
25910
26914
|
outPath,
|
|
25911
26915
|
project: projectName,
|
|
25912
26916
|
projectExplicit,
|
|
25913
|
-
apply:
|
|
26917
|
+
apply: apply6,
|
|
25914
26918
|
dryRun,
|
|
25915
26919
|
noInstall,
|
|
25916
26920
|
verbose: parsed.verbose
|
|
@@ -25925,21 +26929,21 @@ async function main() {
|
|
|
25925
26929
|
usage4();
|
|
25926
26930
|
process.exit(2);
|
|
25927
26931
|
}
|
|
25928
|
-
const scanPath =
|
|
25929
|
-
const stat = await
|
|
26932
|
+
const scanPath = import_node_path83.default.resolve(target);
|
|
26933
|
+
const stat = await import_node_fs48.promises.stat(scanPath).catch(() => null);
|
|
25930
26934
|
if (!stat || !stat.isDirectory()) {
|
|
25931
26935
|
console.error(`neat watch: ${scanPath} is not a directory`);
|
|
25932
26936
|
process.exit(2);
|
|
25933
26937
|
}
|
|
25934
|
-
const projectPaths = pathsForProject(project,
|
|
25935
|
-
const outPath =
|
|
25936
|
-
const errorsPath =
|
|
25937
|
-
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))
|
|
25938
26942
|
);
|
|
25939
|
-
const staleEventsPath =
|
|
25940
|
-
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))
|
|
25941
26945
|
);
|
|
25942
|
-
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;
|
|
25943
26947
|
const handle = await startWatch(getGraph(project), {
|
|
25944
26948
|
scanPath,
|
|
25945
26949
|
outPath,
|
|
@@ -25948,7 +26952,7 @@ async function main() {
|
|
|
25948
26952
|
project,
|
|
25949
26953
|
// Resolve NEAT_HOME so a `neat watch` picks up connectors added to
|
|
25950
26954
|
// ~/.neat/connectors.json (#871). Same resolution the rest of the CLI uses.
|
|
25951
|
-
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"),
|
|
25952
26956
|
...embeddingsCachePath ? { embeddingsCachePath } : {},
|
|
25953
26957
|
host: process.env.HOST ?? "0.0.0.0",
|
|
25954
26958
|
port: Number(process.env.PORT ?? 8080),
|
|
@@ -26130,11 +27134,11 @@ async function main() {
|
|
|
26130
27134
|
process.exit(1);
|
|
26131
27135
|
}
|
|
26132
27136
|
async function tryOrchestrator(cmd, parsed) {
|
|
26133
|
-
const scanPath =
|
|
26134
|
-
const stat = await
|
|
27137
|
+
const scanPath = import_node_path83.default.resolve(cmd);
|
|
27138
|
+
const stat = await import_node_fs48.promises.stat(scanPath).catch(() => null);
|
|
26135
27139
|
if (!stat || !stat.isDirectory()) return null;
|
|
26136
27140
|
const projectExplicit = parsed.project !== null;
|
|
26137
|
-
const projectName = projectExplicit ? parsed.project :
|
|
27141
|
+
const projectName = projectExplicit ? parsed.project : import_node_path83.default.basename(scanPath);
|
|
26138
27142
|
const result = await runOrchestrator({
|
|
26139
27143
|
scanPath,
|
|
26140
27144
|
project: projectName,
|
|
@@ -26323,10 +27327,10 @@ async function runQueryVerb(cmd, parsed) {
|
|
|
26323
27327
|
const parts = parsed.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
26324
27328
|
const out = [];
|
|
26325
27329
|
for (const p of parts) {
|
|
26326
|
-
const r =
|
|
27330
|
+
const r = import_types90.DivergenceTypeSchema.safeParse(p);
|
|
26327
27331
|
if (!r.success) {
|
|
26328
27332
|
console.error(
|
|
26329
|
-
`neat divergences: unknown --type "${p}". allowed: ${
|
|
27333
|
+
`neat divergences: unknown --type "${p}". allowed: ${import_types90.DivergenceTypeSchema.options.join(", ")}`
|
|
26330
27334
|
);
|
|
26331
27335
|
return 2;
|
|
26332
27336
|
}
|