@neat.is/core 0.4.0 → 0.4.3
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-N6RPINEJ.js → chunk-CB2UK4EH.js} +138 -41
- package/dist/chunk-CB2UK4EH.js.map +1 -0
- package/dist/{chunk-IXIFJKMM.js → chunk-D5PIJFBE.js} +2 -2
- package/dist/{chunk-4V23KYOP.js → chunk-KYRIQIPG.js} +17 -9
- package/dist/chunk-KYRIQIPG.js.map +1 -0
- package/dist/{chunk-UPW4CMOH.js → chunk-NTQHMXWE.js} +122 -46
- package/dist/chunk-NTQHMXWE.js.map +1 -0
- package/dist/cli.cjs +351 -121
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.d.cts +2 -1
- package/dist/cli.d.ts +2 -1
- package/dist/cli.js +235 -81
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +267 -93
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +19 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +4 -4
- package/dist/neatd.cjs +276 -102
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +3 -3
- package/dist/{otel-grpc-GGZHR7VM.js → otel-grpc-QTX2YQJZ.js} +3 -3
- package/dist/server.cjs +120 -44
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +3 -3
- package/package.json +2 -2
- package/dist/chunk-4V23KYOP.js.map +0 -1
- package/dist/chunk-N6RPINEJ.js.map +0 -1
- package/dist/chunk-UPW4CMOH.js.map +0 -1
- /package/dist/{chunk-IXIFJKMM.js.map → chunk-D5PIJFBE.js.map} +0 -0
- /package/dist/{otel-grpc-GGZHR7VM.js.map → otel-grpc-QTX2YQJZ.js.map} +0 -0
package/dist/cli.cjs
CHANGED
|
@@ -480,6 +480,7 @@ __export(cli_exports, {
|
|
|
480
480
|
CLAUDE_SKILL_CONFIG: () => CLAUDE_SKILL_CONFIG,
|
|
481
481
|
QUERY_VERBS: () => QUERY_VERBS,
|
|
482
482
|
parseArgs: () => parseArgs,
|
|
483
|
+
readPackageVersion: () => readPackageVersion,
|
|
483
484
|
runInit: () => runInit,
|
|
484
485
|
runQueryVerb: () => runQueryVerb,
|
|
485
486
|
runSkill: () => runSkill
|
|
@@ -488,6 +489,7 @@ module.exports = __toCommonJS(cli_exports);
|
|
|
488
489
|
init_cjs_shims();
|
|
489
490
|
var import_node_path44 = __toESM(require("path"), 1);
|
|
490
491
|
var import_node_fs28 = require("fs");
|
|
492
|
+
var import_node_url3 = require("url");
|
|
491
493
|
|
|
492
494
|
// src/graph.ts
|
|
493
495
|
init_cjs_shims();
|
|
@@ -2093,8 +2095,36 @@ var IGNORED_DIRS = /* @__PURE__ */ new Set([
|
|
|
2093
2095
|
".turbo",
|
|
2094
2096
|
"dist",
|
|
2095
2097
|
"build",
|
|
2096
|
-
".next"
|
|
2098
|
+
".next",
|
|
2099
|
+
// Python virtualenv shapes (issue #344). Walking into a venv pulls in the
|
|
2100
|
+
// entire CPython stdlib + every installed package as if it were first-party
|
|
2101
|
+
// service code — 20k+ files, none of which the user wrote. The shape names
|
|
2102
|
+
// cover the three common venv tools (`venv` / `python -m venv`,
|
|
2103
|
+
// `virtualenv`) and the tox + PEP 582 conventions.
|
|
2104
|
+
".venv",
|
|
2105
|
+
"venv",
|
|
2106
|
+
"__pypackages__",
|
|
2107
|
+
".tox",
|
|
2108
|
+
// `site-packages` shows up nested inside venvs that don't carry one of the
|
|
2109
|
+
// outer names (system Python on macOS, in-place pyenv layouts). Listing it
|
|
2110
|
+
// here means we stop the walk at the boundary even when the wrapper dir
|
|
2111
|
+
// wasn't recognisable.
|
|
2112
|
+
"site-packages"
|
|
2097
2113
|
]);
|
|
2114
|
+
var PYVENV_MARKER_CACHE = /* @__PURE__ */ new Map();
|
|
2115
|
+
async function isPythonVenvDir(dir) {
|
|
2116
|
+
const cached = PYVENV_MARKER_CACHE.get(dir);
|
|
2117
|
+
if (cached !== void 0) return cached;
|
|
2118
|
+
try {
|
|
2119
|
+
const stat = await import_node_fs4.promises.stat(import_node_path4.default.join(dir, "pyvenv.cfg"));
|
|
2120
|
+
const ok = stat.isFile();
|
|
2121
|
+
PYVENV_MARKER_CACHE.set(dir, ok);
|
|
2122
|
+
return ok;
|
|
2123
|
+
} catch {
|
|
2124
|
+
PYVENV_MARKER_CACHE.set(dir, false);
|
|
2125
|
+
return false;
|
|
2126
|
+
}
|
|
2127
|
+
}
|
|
2098
2128
|
function isConfigFile(name) {
|
|
2099
2129
|
const ext = import_node_path4.default.extname(name);
|
|
2100
2130
|
if (CONFIG_FILE_EXTENSIONS.has(ext)) return { match: true, fileType: ext.slice(1) };
|
|
@@ -2443,6 +2473,7 @@ async function walkDirs(start, scanPath, options, visit) {
|
|
|
2443
2473
|
const rel = import_node_path8.default.relative(scanPath, child).split(import_node_path8.default.sep).join("/");
|
|
2444
2474
|
if (rel && options.ig.ignores(rel + "/")) continue;
|
|
2445
2475
|
}
|
|
2476
|
+
if (await isPythonVenvDir(child)) continue;
|
|
2446
2477
|
await visit(child);
|
|
2447
2478
|
await recurse(child, depth + 1);
|
|
2448
2479
|
}
|
|
@@ -2721,7 +2752,9 @@ async function walkYamlFiles(start, depth = 0, max = 5) {
|
|
|
2721
2752
|
for (const entry2 of entries) {
|
|
2722
2753
|
if (entry2.isDirectory()) {
|
|
2723
2754
|
if (IGNORED_DIRS.has(entry2.name)) continue;
|
|
2724
|
-
|
|
2755
|
+
const child = import_node_path9.default.join(start, entry2.name);
|
|
2756
|
+
if (await isPythonVenvDir(child)) continue;
|
|
2757
|
+
out.push(...await walkYamlFiles(child, depth + 1, max));
|
|
2725
2758
|
} else if (entry2.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path9.default.extname(entry2.name))) {
|
|
2726
2759
|
out.push(import_node_path9.default.join(start, entry2.name));
|
|
2727
2760
|
}
|
|
@@ -3403,7 +3436,9 @@ async function walkConfigFiles(dir) {
|
|
|
3403
3436
|
for (const entry2 of entries) {
|
|
3404
3437
|
const full = import_node_path18.default.join(current, entry2.name);
|
|
3405
3438
|
if (entry2.isDirectory()) {
|
|
3406
|
-
if (
|
|
3439
|
+
if (IGNORED_DIRS.has(entry2.name)) continue;
|
|
3440
|
+
if (await isPythonVenvDir(full)) continue;
|
|
3441
|
+
await walk(full);
|
|
3407
3442
|
} else if (entry2.isFile() && isConfigFile(entry2.name).match) {
|
|
3408
3443
|
out.push(full);
|
|
3409
3444
|
}
|
|
@@ -3471,7 +3506,9 @@ async function walkSourceFiles(dir) {
|
|
|
3471
3506
|
for (const entry2 of entries) {
|
|
3472
3507
|
const full = import_node_path19.default.join(current, entry2.name);
|
|
3473
3508
|
if (entry2.isDirectory()) {
|
|
3474
|
-
if (
|
|
3509
|
+
if (IGNORED_DIRS.has(entry2.name)) continue;
|
|
3510
|
+
if (await isPythonVenvDir(full)) continue;
|
|
3511
|
+
await walk(full);
|
|
3475
3512
|
} else if (entry2.isFile() && SERVICE_FILE_EXTENSIONS.has(import_node_path19.default.extname(entry2.name))) {
|
|
3476
3513
|
out.push(full);
|
|
3477
3514
|
}
|
|
@@ -4107,7 +4144,9 @@ async function walkTfFiles(start, depth = 0, max = 5) {
|
|
|
4107
4144
|
for (const entry2 of entries) {
|
|
4108
4145
|
if (entry2.isDirectory()) {
|
|
4109
4146
|
if (IGNORED_DIRS.has(entry2.name) || entry2.name === ".terraform") continue;
|
|
4110
|
-
|
|
4147
|
+
const child = import_node_path27.default.join(start, entry2.name);
|
|
4148
|
+
if (await isPythonVenvDir(child)) continue;
|
|
4149
|
+
out.push(...await walkTfFiles(child, depth + 1, max));
|
|
4111
4150
|
} else if (entry2.isFile() && entry2.name.endsWith(".tf")) {
|
|
4112
4151
|
out.push(import_node_path27.default.join(start, entry2.name));
|
|
4113
4152
|
}
|
|
@@ -4155,7 +4194,9 @@ async function walkYamlFiles2(start, depth = 0, max = 5) {
|
|
|
4155
4194
|
for (const entry2 of entries) {
|
|
4156
4195
|
if (entry2.isDirectory()) {
|
|
4157
4196
|
if (IGNORED_DIRS.has(entry2.name)) continue;
|
|
4158
|
-
|
|
4197
|
+
const child = import_node_path28.default.join(start, entry2.name);
|
|
4198
|
+
if (await isPythonVenvDir(child)) continue;
|
|
4199
|
+
out.push(...await walkYamlFiles2(child, depth + 1, max));
|
|
4159
4200
|
} else if (entry2.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path28.default.extname(entry2.name))) {
|
|
4160
4201
|
out.push(import_node_path28.default.join(start, entry2.name));
|
|
4161
4202
|
}
|
|
@@ -5155,10 +5196,19 @@ function projectFromReq(req) {
|
|
|
5155
5196
|
const params = req.params;
|
|
5156
5197
|
return params.project ?? DEFAULT_PROJECT;
|
|
5157
5198
|
}
|
|
5158
|
-
function resolveProject(registry, req, reply) {
|
|
5199
|
+
function resolveProject(registry, req, reply, bootstrap) {
|
|
5159
5200
|
const name = projectFromReq(req);
|
|
5160
5201
|
const ctx = registry.get(name);
|
|
5161
5202
|
if (!ctx) {
|
|
5203
|
+
const phase = bootstrap?.status(name);
|
|
5204
|
+
if (phase === "bootstrapping") {
|
|
5205
|
+
void reply.code(503).send({ ready: false, project: name, status: "bootstrapping" });
|
|
5206
|
+
return null;
|
|
5207
|
+
}
|
|
5208
|
+
if (phase === "broken") {
|
|
5209
|
+
void reply.code(503).send({ ready: false, project: name, status: "broken" });
|
|
5210
|
+
return null;
|
|
5211
|
+
}
|
|
5162
5212
|
void reply.code(404).send({ error: "project not found", project: name });
|
|
5163
5213
|
return null;
|
|
5164
5214
|
}
|
|
@@ -5188,36 +5238,38 @@ function buildLegacyRegistry(opts) {
|
|
|
5188
5238
|
function registerRoutes(scope, ctx) {
|
|
5189
5239
|
const { registry, startedAt, errorsPathFor, staleEventsPathFor } = ctx;
|
|
5190
5240
|
scope.get("/events", (req, reply) => {
|
|
5191
|
-
const proj = resolveProject(registry, req, reply);
|
|
5241
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap);
|
|
5192
5242
|
if (!proj) return;
|
|
5193
5243
|
handleSse(req, reply, { project: proj.name });
|
|
5194
5244
|
});
|
|
5195
|
-
scope
|
|
5196
|
-
|
|
5197
|
-
|
|
5198
|
-
|
|
5199
|
-
|
|
5200
|
-
|
|
5201
|
-
|
|
5202
|
-
|
|
5203
|
-
|
|
5204
|
-
|
|
5205
|
-
|
|
5206
|
-
|
|
5207
|
-
|
|
5208
|
-
|
|
5209
|
-
|
|
5210
|
-
|
|
5211
|
-
|
|
5245
|
+
if (ctx.scope === "project") {
|
|
5246
|
+
scope.get("/health", async (req, reply) => {
|
|
5247
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap);
|
|
5248
|
+
if (!proj) return;
|
|
5249
|
+
const uptimeMs = Date.now() - startedAt;
|
|
5250
|
+
return {
|
|
5251
|
+
ok: true,
|
|
5252
|
+
project: proj.name,
|
|
5253
|
+
uptimeMs,
|
|
5254
|
+
// Legacy fields kept additively. The web shell's StatusBar reads
|
|
5255
|
+
// nodeCount / edgeCount; ADR-061's HealthResponseSchema validates
|
|
5256
|
+
// the canonical triple and lets the extras pass through.
|
|
5257
|
+
uptime: Math.floor(uptimeMs / 1e3),
|
|
5258
|
+
nodeCount: proj.graph.order,
|
|
5259
|
+
edgeCount: proj.graph.size,
|
|
5260
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
|
|
5261
|
+
};
|
|
5262
|
+
});
|
|
5263
|
+
}
|
|
5212
5264
|
scope.get("/graph", async (req, reply) => {
|
|
5213
|
-
const proj = resolveProject(registry, req, reply);
|
|
5265
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap);
|
|
5214
5266
|
if (!proj) return;
|
|
5215
5267
|
return serializeGraph(proj.graph);
|
|
5216
5268
|
});
|
|
5217
5269
|
scope.get(
|
|
5218
5270
|
"/graph/node/:id",
|
|
5219
5271
|
async (req, reply) => {
|
|
5220
|
-
const proj = resolveProject(registry, req, reply);
|
|
5272
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap);
|
|
5221
5273
|
if (!proj) return;
|
|
5222
5274
|
const { id } = req.params;
|
|
5223
5275
|
if (!proj.graph.hasNode(id)) {
|
|
@@ -5229,7 +5281,7 @@ function registerRoutes(scope, ctx) {
|
|
|
5229
5281
|
scope.get(
|
|
5230
5282
|
"/graph/edges/:id",
|
|
5231
5283
|
async (req, reply) => {
|
|
5232
|
-
const proj = resolveProject(registry, req, reply);
|
|
5284
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap);
|
|
5233
5285
|
if (!proj) return;
|
|
5234
5286
|
const { id } = req.params;
|
|
5235
5287
|
if (!proj.graph.hasNode(id)) {
|
|
@@ -5241,7 +5293,7 @@ function registerRoutes(scope, ctx) {
|
|
|
5241
5293
|
}
|
|
5242
5294
|
);
|
|
5243
5295
|
scope.get("/graph/dependencies/:nodeId", async (req, reply) => {
|
|
5244
|
-
const proj = resolveProject(registry, req, reply);
|
|
5296
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap);
|
|
5245
5297
|
if (!proj) return;
|
|
5246
5298
|
const { nodeId } = req.params;
|
|
5247
5299
|
if (!proj.graph.hasNode(nodeId)) {
|
|
@@ -5256,7 +5308,7 @@ function registerRoutes(scope, ctx) {
|
|
|
5256
5308
|
return getTransitiveDependencies(proj.graph, nodeId, depth);
|
|
5257
5309
|
});
|
|
5258
5310
|
scope.get("/graph/divergences", async (req, reply) => {
|
|
5259
|
-
const proj = resolveProject(registry, req, reply);
|
|
5311
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap);
|
|
5260
5312
|
if (!proj) return;
|
|
5261
5313
|
let typeFilter;
|
|
5262
5314
|
if (req.query.type) {
|
|
@@ -5291,7 +5343,7 @@ function registerRoutes(scope, ctx) {
|
|
|
5291
5343
|
});
|
|
5292
5344
|
});
|
|
5293
5345
|
scope.get("/incidents", async (req, reply) => {
|
|
5294
|
-
const proj = resolveProject(registry, req, reply);
|
|
5346
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap);
|
|
5295
5347
|
if (!proj) return;
|
|
5296
5348
|
const epath = errorsPathFor(proj);
|
|
5297
5349
|
if (!epath) return { count: 0, total: 0, events: [] };
|
|
@@ -5303,7 +5355,7 @@ function registerRoutes(scope, ctx) {
|
|
|
5303
5355
|
return { count: sliced.length, total, events: sliced };
|
|
5304
5356
|
});
|
|
5305
5357
|
scope.get("/stale-events", async (req, reply) => {
|
|
5306
|
-
const proj = resolveProject(registry, req, reply);
|
|
5358
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap);
|
|
5307
5359
|
if (!proj) return;
|
|
5308
5360
|
const spath = staleEventsPathFor(proj);
|
|
5309
5361
|
if (!spath) return { count: 0, total: 0, events: [] };
|
|
@@ -5318,7 +5370,7 @@ function registerRoutes(scope, ctx) {
|
|
|
5318
5370
|
scope.get(
|
|
5319
5371
|
"/incidents/:nodeId",
|
|
5320
5372
|
async (req, reply) => {
|
|
5321
|
-
const proj = resolveProject(registry, req, reply);
|
|
5373
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap);
|
|
5322
5374
|
if (!proj) return;
|
|
5323
5375
|
const { nodeId } = req.params;
|
|
5324
5376
|
if (!proj.graph.hasNode(nodeId)) {
|
|
@@ -5334,7 +5386,7 @@ function registerRoutes(scope, ctx) {
|
|
|
5334
5386
|
}
|
|
5335
5387
|
);
|
|
5336
5388
|
scope.get("/graph/root-cause/:nodeId", async (req, reply) => {
|
|
5337
|
-
const proj = resolveProject(registry, req, reply);
|
|
5389
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap);
|
|
5338
5390
|
if (!proj) return;
|
|
5339
5391
|
const { nodeId } = req.params;
|
|
5340
5392
|
if (!proj.graph.hasNode(nodeId)) {
|
|
@@ -5354,7 +5406,7 @@ function registerRoutes(scope, ctx) {
|
|
|
5354
5406
|
return result;
|
|
5355
5407
|
});
|
|
5356
5408
|
scope.get("/graph/blast-radius/:nodeId", async (req, reply) => {
|
|
5357
|
-
const proj = resolveProject(registry, req, reply);
|
|
5409
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap);
|
|
5358
5410
|
if (!proj) return;
|
|
5359
5411
|
const { nodeId } = req.params;
|
|
5360
5412
|
if (!proj.graph.hasNode(nodeId)) {
|
|
@@ -5367,7 +5419,7 @@ function registerRoutes(scope, ctx) {
|
|
|
5367
5419
|
return getBlastRadius(proj.graph, nodeId, depth);
|
|
5368
5420
|
});
|
|
5369
5421
|
scope.get("/search", async (req, reply) => {
|
|
5370
|
-
const proj = resolveProject(registry, req, reply);
|
|
5422
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap);
|
|
5371
5423
|
if (!proj) return;
|
|
5372
5424
|
const raw = (req.query.q ?? "").trim();
|
|
5373
5425
|
if (!raw) return reply.code(400).send({ error: "query parameter `q` is required" });
|
|
@@ -5398,7 +5450,7 @@ function registerRoutes(scope, ctx) {
|
|
|
5398
5450
|
scope.get(
|
|
5399
5451
|
"/graph/diff",
|
|
5400
5452
|
async (req, reply) => {
|
|
5401
|
-
const proj = resolveProject(registry, req, reply);
|
|
5453
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap);
|
|
5402
5454
|
if (!proj) return;
|
|
5403
5455
|
const against = req.query.against;
|
|
5404
5456
|
if (!against) {
|
|
@@ -5413,7 +5465,7 @@ function registerRoutes(scope, ctx) {
|
|
|
5413
5465
|
}
|
|
5414
5466
|
);
|
|
5415
5467
|
scope.post("/snapshot", async (req, reply) => {
|
|
5416
|
-
const proj = resolveProject(registry, req, reply);
|
|
5468
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap);
|
|
5417
5469
|
if (!proj) return;
|
|
5418
5470
|
const body = req.body;
|
|
5419
5471
|
if (!body || typeof body !== "object" || !body.snapshot) {
|
|
@@ -5442,7 +5494,7 @@ function registerRoutes(scope, ctx) {
|
|
|
5442
5494
|
}
|
|
5443
5495
|
});
|
|
5444
5496
|
scope.post("/graph/scan", async (req, reply) => {
|
|
5445
|
-
const proj = resolveProject(registry, req, reply);
|
|
5497
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap);
|
|
5446
5498
|
if (!proj) return;
|
|
5447
5499
|
if (!proj.scanPath) {
|
|
5448
5500
|
return reply.code(409).send({ error: "scan path not configured for this project", project: proj.name });
|
|
@@ -5458,7 +5510,7 @@ function registerRoutes(scope, ctx) {
|
|
|
5458
5510
|
};
|
|
5459
5511
|
});
|
|
5460
5512
|
scope.get("/policies", async (req, reply) => {
|
|
5461
|
-
const proj = resolveProject(registry, req, reply);
|
|
5513
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap);
|
|
5462
5514
|
if (!proj) return;
|
|
5463
5515
|
const policyPath = ctx.policyFilePathFor(proj);
|
|
5464
5516
|
if (!policyPath) {
|
|
@@ -5475,7 +5527,7 @@ function registerRoutes(scope, ctx) {
|
|
|
5475
5527
|
}
|
|
5476
5528
|
});
|
|
5477
5529
|
scope.get("/policies/violations", async (req, reply) => {
|
|
5478
|
-
const proj = resolveProject(registry, req, reply);
|
|
5530
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap);
|
|
5479
5531
|
if (!proj) return;
|
|
5480
5532
|
const log = new PolicyViolationsLog(proj.paths.policyViolationsPath);
|
|
5481
5533
|
let violations = await log.readAll();
|
|
@@ -5495,7 +5547,7 @@ function registerRoutes(scope, ctx) {
|
|
|
5495
5547
|
return { violations };
|
|
5496
5548
|
});
|
|
5497
5549
|
scope.post("/policies/check", async (req, reply) => {
|
|
5498
|
-
const proj = resolveProject(registry, req, reply);
|
|
5550
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap);
|
|
5499
5551
|
if (!proj) return;
|
|
5500
5552
|
const parsed = import_types23.PoliciesCheckBodySchema.safeParse(req.body ?? {});
|
|
5501
5553
|
if (!parsed.success) {
|
|
@@ -5566,10 +5618,36 @@ async function buildApi(opts) {
|
|
|
5566
5618
|
const routeCtx = {
|
|
5567
5619
|
registry,
|
|
5568
5620
|
startedAt,
|
|
5621
|
+
scope: "root",
|
|
5569
5622
|
errorsPathFor,
|
|
5570
5623
|
staleEventsPathFor,
|
|
5571
|
-
policyFilePathFor
|
|
5624
|
+
policyFilePathFor,
|
|
5625
|
+
bootstrap: opts.bootstrap
|
|
5572
5626
|
};
|
|
5627
|
+
app.get("/health", async () => {
|
|
5628
|
+
const uptimeMs = Date.now() - startedAt;
|
|
5629
|
+
const bootstrapList = opts.bootstrap?.list() ?? [];
|
|
5630
|
+
const byName = new Map(bootstrapList.map((p) => [p.name, p]));
|
|
5631
|
+
const names = /* @__PURE__ */ new Set([
|
|
5632
|
+
...registry.list(),
|
|
5633
|
+
...bootstrapList.map((p) => p.name)
|
|
5634
|
+
]);
|
|
5635
|
+
const projects = [...names].sort().map((name) => {
|
|
5636
|
+
const proj = registry.get(name);
|
|
5637
|
+
const tracked = byName.get(name);
|
|
5638
|
+
return {
|
|
5639
|
+
name,
|
|
5640
|
+
nodeCount: proj?.graph.order ?? 0,
|
|
5641
|
+
edgeCount: proj?.graph.size ?? 0,
|
|
5642
|
+
...tracked ? { status: tracked.status, elapsedMs: tracked.elapsedMs } : {}
|
|
5643
|
+
};
|
|
5644
|
+
});
|
|
5645
|
+
return {
|
|
5646
|
+
ok: true,
|
|
5647
|
+
uptimeMs,
|
|
5648
|
+
projects
|
|
5649
|
+
};
|
|
5650
|
+
});
|
|
5573
5651
|
app.get("/projects", async (_req, reply) => {
|
|
5574
5652
|
try {
|
|
5575
5653
|
return await listProjects();
|
|
@@ -5597,7 +5675,7 @@ async function buildApi(opts) {
|
|
|
5597
5675
|
registerRoutes(app, routeCtx);
|
|
5598
5676
|
await app.register(
|
|
5599
5677
|
async (scope) => {
|
|
5600
|
-
registerRoutes(scope, routeCtx);
|
|
5678
|
+
registerRoutes(scope, { ...routeCtx, scope: "project" });
|
|
5601
5679
|
},
|
|
5602
5680
|
{ prefix: "/projects/:project" }
|
|
5603
5681
|
);
|
|
@@ -5980,6 +6058,14 @@ var IGNORED_WATCH_GLOBS = [
|
|
|
5980
6058
|
"**/.turbo/**",
|
|
5981
6059
|
"**/.next/**",
|
|
5982
6060
|
"**/neat-out/**",
|
|
6061
|
+
// Python venv shapes (issue #344). chokidar opens one watch handle per
|
|
6062
|
+
// descended dir; a CPython venv carries 20k+ files and trivially blows
|
|
6063
|
+
// through the macOS kqueue cap before extraction even runs.
|
|
6064
|
+
"**/.venv/**",
|
|
6065
|
+
"**/venv/**",
|
|
6066
|
+
"**/__pypackages__/**",
|
|
6067
|
+
"**/.tox/**",
|
|
6068
|
+
"**/site-packages/**",
|
|
5983
6069
|
"**/.DS_Store"
|
|
5984
6070
|
];
|
|
5985
6071
|
var IGNORED_WATCH_PATHS = [
|
|
@@ -5990,6 +6076,11 @@ var IGNORED_WATCH_PATHS = [
|
|
|
5990
6076
|
/(?:^|[\\/])\.turbo[\\/]/,
|
|
5991
6077
|
/(?:^|[\\/])\.next[\\/]/,
|
|
5992
6078
|
/(?:^|[\\/])neat-out[\\/]/,
|
|
6079
|
+
/(?:^|[\\/])\.venv[\\/]/,
|
|
6080
|
+
/(?:^|[\\/])venv[\\/]/,
|
|
6081
|
+
/(?:^|[\\/])__pypackages__[\\/]/,
|
|
6082
|
+
/(?:^|[\\/])\.tox[\\/]/,
|
|
6083
|
+
/(?:^|[\\/])site-packages[\\/]/,
|
|
5993
6084
|
/[\\/]?\.DS_Store$/
|
|
5994
6085
|
];
|
|
5995
6086
|
function shouldIgnore(absPath) {
|
|
@@ -6427,10 +6518,10 @@ dotenv.config({ path: path.join(here, '.env.neat') })
|
|
|
6427
6518
|
|
|
6428
6519
|
await import('@opentelemetry/auto-instrumentations-node/register')
|
|
6429
6520
|
`;
|
|
6430
|
-
function renderEnvNeat(
|
|
6521
|
+
function renderEnvNeat(projectName) {
|
|
6431
6522
|
return [
|
|
6432
6523
|
"# Generated by `neat init --apply` (ADR-069).",
|
|
6433
|
-
`OTEL_SERVICE_NAME=${
|
|
6524
|
+
`OTEL_SERVICE_NAME=${projectName}`,
|
|
6434
6525
|
"OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318",
|
|
6435
6526
|
""
|
|
6436
6527
|
].join("\n");
|
|
@@ -6451,41 +6542,26 @@ export async function register() {
|
|
|
6451
6542
|
}
|
|
6452
6543
|
`;
|
|
6453
6544
|
var NEXT_INSTRUMENTATION_NODE_TS = `${NEXT_INSTRUMENTATION_HEADER}
|
|
6454
|
-
|
|
6455
|
-
|
|
6456
|
-
|
|
6457
|
-
import path from 'node:path'
|
|
6458
|
-
import dotenv from 'dotenv'
|
|
6545
|
+
process.env.OTEL_SERVICE_NAME ||= '__PROJECT_NAME__'
|
|
6546
|
+
process.env.OTEL_EXPORTER_OTLP_ENDPOINT ||= 'http://localhost:4318'
|
|
6547
|
+
|
|
6459
6548
|
import { NodeSDK } from '@opentelemetry/sdk-node'
|
|
6460
6549
|
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node'
|
|
6461
6550
|
|
|
6462
|
-
|
|
6463
|
-
dotenv.config({ path: path.join(here, '.env.neat') })
|
|
6464
|
-
|
|
6465
|
-
const sdk = new NodeSDK({
|
|
6466
|
-
serviceName: process.env.OTEL_SERVICE_NAME,
|
|
6467
|
-
instrumentations: [getNodeAutoInstrumentations()],
|
|
6468
|
-
})
|
|
6469
|
-
sdk.start()
|
|
6551
|
+
new NodeSDK({ instrumentations: [getNodeAutoInstrumentations()] }).start()
|
|
6470
6552
|
`;
|
|
6471
6553
|
var NEXT_INSTRUMENTATION_NODE_JS = `${NEXT_INSTRUMENTATION_HEADER}
|
|
6472
|
-
|
|
6473
|
-
|
|
6474
|
-
import { fileURLToPath } from 'node:url'
|
|
6475
|
-
import path from 'node:path'
|
|
6476
|
-
import dotenv from 'dotenv'
|
|
6477
|
-
import { NodeSDK } from '@opentelemetry/sdk-node'
|
|
6478
|
-
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node'
|
|
6554
|
+
process.env.OTEL_SERVICE_NAME ||= '__PROJECT_NAME__'
|
|
6555
|
+
process.env.OTEL_EXPORTER_OTLP_ENDPOINT ||= 'http://localhost:4318'
|
|
6479
6556
|
|
|
6480
|
-
const
|
|
6481
|
-
|
|
6557
|
+
const { NodeSDK } = require('@opentelemetry/sdk-node')
|
|
6558
|
+
const { getNodeAutoInstrumentations } = require('@opentelemetry/auto-instrumentations-node')
|
|
6482
6559
|
|
|
6483
|
-
|
|
6484
|
-
serviceName: process.env.OTEL_SERVICE_NAME,
|
|
6485
|
-
instrumentations: [getNodeAutoInstrumentations()],
|
|
6486
|
-
})
|
|
6487
|
-
sdk.start()
|
|
6560
|
+
new NodeSDK({ instrumentations: [getNodeAutoInstrumentations()] }).start()
|
|
6488
6561
|
`;
|
|
6562
|
+
function renderNextInstrumentationNode(template, projectName) {
|
|
6563
|
+
return template.replace(/__PROJECT_NAME__/g, projectName);
|
|
6564
|
+
}
|
|
6489
6565
|
var FRAMEWORK_OTEL_INIT_HEADER = "// Generated by `neat init --apply` (ADR-074). OpenTelemetry SDK hook.";
|
|
6490
6566
|
var FRAMEWORK_OTEL_INIT_TS_BODY = `${FRAMEWORK_OTEL_INIT_HEADER}
|
|
6491
6567
|
// Loads .env.neat (OTEL_SERVICE_NAME, OTEL_EXPORTER_OTLP_ENDPOINT) before
|
|
@@ -6600,6 +6676,10 @@ var OTEL_ENV = {
|
|
|
6600
6676
|
key: "OTEL_EXPORTER_OTLP_ENDPOINT",
|
|
6601
6677
|
value: "http://localhost:4318"
|
|
6602
6678
|
};
|
|
6679
|
+
function envServiceName(pkg, serviceDir, project) {
|
|
6680
|
+
if (project && project.length > 0) return project;
|
|
6681
|
+
return pkg.name ?? import_node_path40.default.basename(serviceDir);
|
|
6682
|
+
}
|
|
6603
6683
|
async function readPackageJson(serviceDir) {
|
|
6604
6684
|
try {
|
|
6605
6685
|
const raw = await import_node_fs25.promises.readFile(import_node_path40.default.join(serviceDir, "package.json"), "utf8");
|
|
@@ -6824,17 +6904,29 @@ function lineIsOtelInjection(line) {
|
|
|
6824
6904
|
if (trimmed.length === 0) return false;
|
|
6825
6905
|
return /(?:require\(|import\s+)['"]\.\/otel-init[^'"]*['"]/.test(trimmed);
|
|
6826
6906
|
}
|
|
6827
|
-
async function
|
|
6907
|
+
async function detectsSrcLayout(serviceDir) {
|
|
6908
|
+
const [hasSrcApp, hasSrcPages, hasRootApp, hasRootPages] = await Promise.all([
|
|
6909
|
+
exists2(import_node_path40.default.join(serviceDir, "src", "app")),
|
|
6910
|
+
exists2(import_node_path40.default.join(serviceDir, "src", "pages")),
|
|
6911
|
+
exists2(import_node_path40.default.join(serviceDir, "app")),
|
|
6912
|
+
exists2(import_node_path40.default.join(serviceDir, "pages"))
|
|
6913
|
+
]);
|
|
6914
|
+
return (hasSrcApp || hasSrcPages) && !hasRootApp && !hasRootPages;
|
|
6915
|
+
}
|
|
6916
|
+
async function planNext(serviceDir, pkg, manifestPath, nextConfigPath, project) {
|
|
6828
6917
|
const useTs = await isTypeScriptProject(serviceDir);
|
|
6829
|
-
const
|
|
6918
|
+
const srcLayout = await detectsSrcLayout(serviceDir);
|
|
6919
|
+
const baseDir = srcLayout ? import_node_path40.default.join(serviceDir, "src") : serviceDir;
|
|
6920
|
+
const instrumentationFile = import_node_path40.default.join(baseDir, useTs ? "instrumentation.ts" : "instrumentation.js");
|
|
6830
6921
|
const instrumentationNodeFile = import_node_path40.default.join(
|
|
6831
|
-
|
|
6922
|
+
baseDir,
|
|
6832
6923
|
useTs ? "instrumentation.node.ts" : "instrumentation.node.js"
|
|
6833
6924
|
);
|
|
6834
|
-
const envNeatFile = import_node_path40.default.join(
|
|
6925
|
+
const envNeatFile = import_node_path40.default.join(baseDir, ".env.neat");
|
|
6835
6926
|
const existingDeps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
6836
6927
|
const dependencyEdits = [];
|
|
6837
6928
|
for (const sdk of SDK_PACKAGES) {
|
|
6929
|
+
if (sdk.name === "dotenv") continue;
|
|
6838
6930
|
if (sdk.name in existingDeps) continue;
|
|
6839
6931
|
dependencyEdits.push({
|
|
6840
6932
|
file: manifestPath,
|
|
@@ -6843,6 +6935,7 @@ async function planNext(serviceDir, pkg, manifestPath, nextConfigPath) {
|
|
|
6843
6935
|
version: sdk.version
|
|
6844
6936
|
});
|
|
6845
6937
|
}
|
|
6938
|
+
const projectName = envServiceName(pkg, serviceDir, project);
|
|
6846
6939
|
const generatedFiles = [];
|
|
6847
6940
|
if (!await exists2(instrumentationFile)) {
|
|
6848
6941
|
generatedFiles.push({
|
|
@@ -6854,14 +6947,17 @@ async function planNext(serviceDir, pkg, manifestPath, nextConfigPath) {
|
|
|
6854
6947
|
if (!await exists2(instrumentationNodeFile)) {
|
|
6855
6948
|
generatedFiles.push({
|
|
6856
6949
|
file: instrumentationNodeFile,
|
|
6857
|
-
contents:
|
|
6950
|
+
contents: renderNextInstrumentationNode(
|
|
6951
|
+
useTs ? NEXT_INSTRUMENTATION_NODE_TS : NEXT_INSTRUMENTATION_NODE_JS,
|
|
6952
|
+
projectName
|
|
6953
|
+
),
|
|
6858
6954
|
skipIfExists: true
|
|
6859
6955
|
});
|
|
6860
6956
|
}
|
|
6861
6957
|
if (!await exists2(envNeatFile)) {
|
|
6862
6958
|
generatedFiles.push({
|
|
6863
6959
|
file: envNeatFile,
|
|
6864
|
-
contents: renderEnvNeat(
|
|
6960
|
+
contents: renderEnvNeat(projectName),
|
|
6865
6961
|
skipIfExists: true
|
|
6866
6962
|
});
|
|
6867
6963
|
}
|
|
@@ -6917,12 +7013,12 @@ function buildDependencyEdits(pkg, manifestPath) {
|
|
|
6917
7013
|
}
|
|
6918
7014
|
return edits;
|
|
6919
7015
|
}
|
|
6920
|
-
async function queueEnvNeat(serviceDir, pkg, generatedFiles) {
|
|
7016
|
+
async function queueEnvNeat(serviceDir, pkg, project, generatedFiles) {
|
|
6921
7017
|
const envNeatFile = import_node_path40.default.join(serviceDir, ".env.neat");
|
|
6922
7018
|
if (!await exists2(envNeatFile)) {
|
|
6923
7019
|
generatedFiles.push({
|
|
6924
7020
|
file: envNeatFile,
|
|
6925
|
-
contents: renderEnvNeat(pkg
|
|
7021
|
+
contents: renderEnvNeat(envServiceName(pkg, serviceDir, project)),
|
|
6926
7022
|
skipIfExists: true
|
|
6927
7023
|
});
|
|
6928
7024
|
}
|
|
@@ -6941,7 +7037,7 @@ function fileImportsOtelHook(raw, specifiers) {
|
|
|
6941
7037
|
}
|
|
6942
7038
|
return false;
|
|
6943
7039
|
}
|
|
6944
|
-
async function planRemix(serviceDir, pkg, manifestPath, entryFile) {
|
|
7040
|
+
async function planRemix(serviceDir, pkg, manifestPath, entryFile, project) {
|
|
6945
7041
|
const useTs = await isTypeScriptProject(serviceDir);
|
|
6946
7042
|
const otelServerFile = import_node_path40.default.join(
|
|
6947
7043
|
serviceDir,
|
|
@@ -6956,7 +7052,7 @@ async function planRemix(serviceDir, pkg, manifestPath, entryFile) {
|
|
|
6956
7052
|
skipIfExists: true
|
|
6957
7053
|
});
|
|
6958
7054
|
}
|
|
6959
|
-
await queueEnvNeat(serviceDir, pkg, generatedFiles);
|
|
7055
|
+
await queueEnvNeat(serviceDir, pkg, project, generatedFiles);
|
|
6960
7056
|
const entrypointEdits = [];
|
|
6961
7057
|
try {
|
|
6962
7058
|
const raw = await import_node_fs25.promises.readFile(entryFile, "utf8");
|
|
@@ -6993,7 +7089,7 @@ async function planRemix(serviceDir, pkg, manifestPath, entryFile) {
|
|
|
6993
7089
|
framework: "remix"
|
|
6994
7090
|
};
|
|
6995
7091
|
}
|
|
6996
|
-
async function planSvelteKit(serviceDir, pkg, manifestPath, hooksFile) {
|
|
7092
|
+
async function planSvelteKit(serviceDir, pkg, manifestPath, hooksFile, project) {
|
|
6997
7093
|
const useTs = await isTypeScriptProject(serviceDir);
|
|
6998
7094
|
const otelInitFile = import_node_path40.default.join(
|
|
6999
7095
|
serviceDir,
|
|
@@ -7010,7 +7106,7 @@ async function planSvelteKit(serviceDir, pkg, manifestPath, hooksFile) {
|
|
|
7010
7106
|
skipIfExists: true
|
|
7011
7107
|
});
|
|
7012
7108
|
}
|
|
7013
|
-
await queueEnvNeat(serviceDir, pkg, generatedFiles);
|
|
7109
|
+
await queueEnvNeat(serviceDir, pkg, project, generatedFiles);
|
|
7014
7110
|
if (hooksFile === null) {
|
|
7015
7111
|
generatedFiles.push({
|
|
7016
7112
|
file: resolvedHooksFile,
|
|
@@ -7054,7 +7150,7 @@ async function planSvelteKit(serviceDir, pkg, manifestPath, hooksFile) {
|
|
|
7054
7150
|
framework: "sveltekit"
|
|
7055
7151
|
};
|
|
7056
7152
|
}
|
|
7057
|
-
async function planNuxt(serviceDir, pkg, manifestPath) {
|
|
7153
|
+
async function planNuxt(serviceDir, pkg, manifestPath, project) {
|
|
7058
7154
|
const useTs = await isTypeScriptProject(serviceDir);
|
|
7059
7155
|
const otelPluginFile = import_node_path40.default.join(
|
|
7060
7156
|
serviceDir,
|
|
@@ -7080,7 +7176,7 @@ async function planNuxt(serviceDir, pkg, manifestPath) {
|
|
|
7080
7176
|
skipIfExists: true
|
|
7081
7177
|
});
|
|
7082
7178
|
}
|
|
7083
|
-
await queueEnvNeat(serviceDir, pkg, generatedFiles);
|
|
7179
|
+
await queueEnvNeat(serviceDir, pkg, project, generatedFiles);
|
|
7084
7180
|
const empty = dependencyEdits.length === 0 && generatedFiles.length === 0;
|
|
7085
7181
|
if (empty) {
|
|
7086
7182
|
return {
|
|
@@ -7111,7 +7207,7 @@ async function findAstroMiddleware(serviceDir) {
|
|
|
7111
7207
|
}
|
|
7112
7208
|
return null;
|
|
7113
7209
|
}
|
|
7114
|
-
async function planAstro(serviceDir, pkg, manifestPath) {
|
|
7210
|
+
async function planAstro(serviceDir, pkg, manifestPath, project) {
|
|
7115
7211
|
const useTs = await isTypeScriptProject(serviceDir);
|
|
7116
7212
|
const otelInitFile = import_node_path40.default.join(
|
|
7117
7213
|
serviceDir,
|
|
@@ -7129,7 +7225,7 @@ async function planAstro(serviceDir, pkg, manifestPath) {
|
|
|
7129
7225
|
skipIfExists: true
|
|
7130
7226
|
});
|
|
7131
7227
|
}
|
|
7132
|
-
await queueEnvNeat(serviceDir, pkg, generatedFiles);
|
|
7228
|
+
await queueEnvNeat(serviceDir, pkg, project, generatedFiles);
|
|
7133
7229
|
if (existingMiddleware === null) {
|
|
7134
7230
|
generatedFiles.push({
|
|
7135
7231
|
file: middlewareFile,
|
|
@@ -7173,9 +7269,10 @@ async function planAstro(serviceDir, pkg, manifestPath) {
|
|
|
7173
7269
|
framework: "astro"
|
|
7174
7270
|
};
|
|
7175
7271
|
}
|
|
7176
|
-
async function plan(serviceDir) {
|
|
7272
|
+
async function plan(serviceDir, opts) {
|
|
7177
7273
|
const pkg = await readPackageJson(serviceDir);
|
|
7178
7274
|
const manifestPath = import_node_path40.default.join(serviceDir, "package.json");
|
|
7275
|
+
const project = opts?.project;
|
|
7179
7276
|
const empty = {
|
|
7180
7277
|
language: "javascript",
|
|
7181
7278
|
serviceDir,
|
|
@@ -7188,32 +7285,32 @@ async function plan(serviceDir) {
|
|
|
7188
7285
|
if (hasNextDependency(pkg)) {
|
|
7189
7286
|
const nextConfig = await findNextConfig(serviceDir);
|
|
7190
7287
|
if (nextConfig) {
|
|
7191
|
-
return planNext(serviceDir, pkg, manifestPath, nextConfig);
|
|
7288
|
+
return planNext(serviceDir, pkg, manifestPath, nextConfig, project);
|
|
7192
7289
|
}
|
|
7193
7290
|
}
|
|
7194
7291
|
if (hasRemixDependency(pkg)) {
|
|
7195
7292
|
const remixEntry = await findRemixEntry(serviceDir);
|
|
7196
7293
|
if (remixEntry) {
|
|
7197
|
-
return planRemix(serviceDir, pkg, manifestPath, remixEntry);
|
|
7294
|
+
return planRemix(serviceDir, pkg, manifestPath, remixEntry, project);
|
|
7198
7295
|
}
|
|
7199
7296
|
}
|
|
7200
7297
|
if (hasSvelteKitDependency(pkg)) {
|
|
7201
7298
|
const hooks = await findSvelteKitHooks(serviceDir);
|
|
7202
7299
|
const config = await findSvelteKitConfig(serviceDir);
|
|
7203
7300
|
if (hooks || config) {
|
|
7204
|
-
return planSvelteKit(serviceDir, pkg, manifestPath, hooks);
|
|
7301
|
+
return planSvelteKit(serviceDir, pkg, manifestPath, hooks, project);
|
|
7205
7302
|
}
|
|
7206
7303
|
}
|
|
7207
7304
|
if (hasNuxtDependency(pkg)) {
|
|
7208
7305
|
const nuxtConfig = await findNuxtConfig(serviceDir);
|
|
7209
7306
|
if (nuxtConfig) {
|
|
7210
|
-
return planNuxt(serviceDir, pkg, manifestPath);
|
|
7307
|
+
return planNuxt(serviceDir, pkg, manifestPath, project);
|
|
7211
7308
|
}
|
|
7212
7309
|
}
|
|
7213
7310
|
if (hasAstroDependency(pkg)) {
|
|
7214
7311
|
const astroConfig = await findAstroConfig(serviceDir);
|
|
7215
7312
|
if (astroConfig) {
|
|
7216
|
-
return planAstro(serviceDir, pkg, manifestPath);
|
|
7313
|
+
return planAstro(serviceDir, pkg, manifestPath, project);
|
|
7217
7314
|
}
|
|
7218
7315
|
}
|
|
7219
7316
|
const entryFile = await resolveEntry(serviceDir, pkg);
|
|
@@ -7261,7 +7358,7 @@ async function plan(serviceDir) {
|
|
|
7261
7358
|
if (!await exists2(envNeatFile)) {
|
|
7262
7359
|
generatedFiles.push({
|
|
7263
7360
|
file: envNeatFile,
|
|
7264
|
-
contents: renderEnvNeat(pkg
|
|
7361
|
+
contents: renderEnvNeat(envServiceName(pkg, serviceDir, project)),
|
|
7265
7362
|
skipIfExists: true
|
|
7266
7363
|
});
|
|
7267
7364
|
}
|
|
@@ -7286,9 +7383,13 @@ function isAllowedWritePath(serviceDir, target) {
|
|
|
7286
7383
|
if (base === "package.json") return true;
|
|
7287
7384
|
if (base === ".env.neat") return true;
|
|
7288
7385
|
if (/^otel-init\.(?:js|cjs|mjs|ts)$/.test(base)) return true;
|
|
7289
|
-
if (/^instrumentation(?:\.node)?\.(?:js|cjs|mjs|ts)$/.test(base)) return true;
|
|
7290
|
-
if (/^next\.config\.(?:js|mjs|ts)$/.test(base)) return true;
|
|
7291
7386
|
const relPosix = rel.split(import_node_path40.default.sep).join("/");
|
|
7387
|
+
if (/^instrumentation(?:\.node)?\.(?:js|cjs|mjs|ts)$/.test(base)) {
|
|
7388
|
+
if (relPosix === base) return true;
|
|
7389
|
+
if (relPosix === `src/${base}`) return true;
|
|
7390
|
+
return false;
|
|
7391
|
+
}
|
|
7392
|
+
if (/^next\.config\.(?:js|mjs|ts)$/.test(base)) return true;
|
|
7292
7393
|
if (relPosix === "app/otel.server.ts" || relPosix === "app/otel.server.js") return true;
|
|
7293
7394
|
if (/^app\/entry\.server\.(?:tsx?|jsx?)$/.test(relPosix)) return true;
|
|
7294
7395
|
if (relPosix === "src/otel-init.ts" || relPosix === "src/otel-init.js") return true;
|
|
@@ -7796,14 +7897,14 @@ async function extractAndPersist(opts) {
|
|
|
7796
7897
|
errorsPath: projectPaths.errorsPath
|
|
7797
7898
|
};
|
|
7798
7899
|
}
|
|
7799
|
-
async function applyInstallersOver(services) {
|
|
7900
|
+
async function applyInstallersOver(services, project) {
|
|
7800
7901
|
let instrumented = 0;
|
|
7801
7902
|
let already = 0;
|
|
7802
7903
|
let libOnly = 0;
|
|
7803
7904
|
for (const svc of services) {
|
|
7804
7905
|
const installer = await pickInstaller(svc.dir);
|
|
7805
7906
|
if (!installer) continue;
|
|
7806
|
-
const plan3 = await installer.plan(svc.dir);
|
|
7907
|
+
const plan3 = await installer.plan(svc.dir, { project });
|
|
7807
7908
|
if (isEmptyPlan(plan3) && !plan3.libOnly) {
|
|
7808
7909
|
already++;
|
|
7809
7910
|
continue;
|
|
@@ -7825,28 +7926,106 @@ async function promptYesNo(question) {
|
|
|
7825
7926
|
});
|
|
7826
7927
|
});
|
|
7827
7928
|
}
|
|
7828
|
-
var DEFAULT_DAEMON_READY_TIMEOUT_MS =
|
|
7829
|
-
|
|
7929
|
+
var DEFAULT_DAEMON_READY_TIMEOUT_MS = 6e4;
|
|
7930
|
+
var PROBE_INTERVAL_MS = 500;
|
|
7931
|
+
async function fetchDaemonHealth(restPort) {
|
|
7830
7932
|
return new Promise((resolve) => {
|
|
7831
7933
|
const req = import_node_http.default.get(`http://127.0.0.1:${restPort}/health`, (res) => {
|
|
7832
7934
|
const ok = res.statusCode !== void 0 && res.statusCode >= 200 && res.statusCode < 300;
|
|
7833
|
-
|
|
7834
|
-
|
|
7935
|
+
if (!ok) {
|
|
7936
|
+
res.resume();
|
|
7937
|
+
resolve(null);
|
|
7938
|
+
return;
|
|
7939
|
+
}
|
|
7940
|
+
let body = "";
|
|
7941
|
+
res.setEncoding("utf8");
|
|
7942
|
+
res.on("data", (chunk) => {
|
|
7943
|
+
body += chunk;
|
|
7944
|
+
});
|
|
7945
|
+
res.on("end", () => {
|
|
7946
|
+
try {
|
|
7947
|
+
resolve(JSON.parse(body));
|
|
7948
|
+
} catch {
|
|
7949
|
+
resolve({ ok: true });
|
|
7950
|
+
}
|
|
7951
|
+
});
|
|
7835
7952
|
});
|
|
7836
|
-
req.on("error", () => resolve(
|
|
7953
|
+
req.on("error", () => resolve(null));
|
|
7837
7954
|
req.setTimeout(1e3, () => {
|
|
7838
7955
|
req.destroy();
|
|
7839
|
-
resolve(
|
|
7956
|
+
resolve(null);
|
|
7957
|
+
});
|
|
7958
|
+
});
|
|
7959
|
+
}
|
|
7960
|
+
async function checkDaemonHealth(restPort) {
|
|
7961
|
+
const body = await fetchDaemonHealth(restPort);
|
|
7962
|
+
return body !== null;
|
|
7963
|
+
}
|
|
7964
|
+
async function probeProjectHealth(restPort, name) {
|
|
7965
|
+
return new Promise((resolve) => {
|
|
7966
|
+
const req = import_node_http.default.get(
|
|
7967
|
+
`http://127.0.0.1:${restPort}/projects/${encodeURIComponent(name)}/health`,
|
|
7968
|
+
(res) => {
|
|
7969
|
+
const code = res.statusCode ?? 0;
|
|
7970
|
+
res.resume();
|
|
7971
|
+
if (code >= 200 && code < 300) resolve("active");
|
|
7972
|
+
else resolve("bootstrapping");
|
|
7973
|
+
}
|
|
7974
|
+
);
|
|
7975
|
+
req.on("error", () => resolve("bootstrapping"));
|
|
7976
|
+
req.setTimeout(1e3, () => {
|
|
7977
|
+
req.destroy();
|
|
7978
|
+
resolve("bootstrapping");
|
|
7840
7979
|
});
|
|
7841
7980
|
});
|
|
7842
7981
|
}
|
|
7982
|
+
async function snapshotProjectStatus(restPort, body) {
|
|
7983
|
+
if (body.projects && body.projects.length > 0) {
|
|
7984
|
+
return body.projects.map((p) => ({
|
|
7985
|
+
name: p.name,
|
|
7986
|
+
status: p.status ?? "active"
|
|
7987
|
+
}));
|
|
7988
|
+
}
|
|
7989
|
+
const entries = await listProjects().catch(() => []);
|
|
7990
|
+
if (entries.length === 0) return [];
|
|
7991
|
+
return Promise.all(
|
|
7992
|
+
entries.map(async (entry2) => ({
|
|
7993
|
+
name: entry2.name,
|
|
7994
|
+
status: await probeProjectHealth(restPort, entry2.name)
|
|
7995
|
+
}))
|
|
7996
|
+
);
|
|
7997
|
+
}
|
|
7843
7998
|
async function waitForDaemonReady(restPort, timeoutMs) {
|
|
7844
7999
|
const deadline = Date.now() + timeoutMs;
|
|
8000
|
+
let lastBootstrapping = [];
|
|
7845
8001
|
while (Date.now() < deadline) {
|
|
7846
|
-
|
|
7847
|
-
|
|
8002
|
+
const body = await fetchDaemonHealth(restPort);
|
|
8003
|
+
if (body !== null) {
|
|
8004
|
+
const projects2 = await snapshotProjectStatus(restPort, body);
|
|
8005
|
+
const bootstrapping = projects2.filter((p) => p.status === "bootstrapping").map((p) => p.name);
|
|
8006
|
+
const broken = projects2.filter((p) => p.status === "broken").map((p) => p.name);
|
|
8007
|
+
if (bootstrapping.length === 0) {
|
|
8008
|
+
return { ready: true, brokenProjects: broken, stillBootstrapping: [] };
|
|
8009
|
+
}
|
|
8010
|
+
const key = bootstrapping.slice().sort().join(",");
|
|
8011
|
+
const prevKey = lastBootstrapping.slice().sort().join(",");
|
|
8012
|
+
if (key !== prevKey) {
|
|
8013
|
+
const plural = bootstrapping.length === 1 ? "" : "s";
|
|
8014
|
+
console.log(
|
|
8015
|
+
`neat: waiting on ${bootstrapping.length} project${plural}: ${bootstrapping.join(", ")}`
|
|
8016
|
+
);
|
|
8017
|
+
lastBootstrapping = bootstrapping;
|
|
8018
|
+
}
|
|
8019
|
+
}
|
|
8020
|
+
await new Promise((r) => setTimeout(r, PROBE_INTERVAL_MS));
|
|
7848
8021
|
}
|
|
7849
|
-
|
|
8022
|
+
const final = await fetchDaemonHealth(restPort);
|
|
8023
|
+
const projects = final ? await snapshotProjectStatus(restPort, final) : [];
|
|
8024
|
+
return {
|
|
8025
|
+
ready: false,
|
|
8026
|
+
brokenProjects: projects.filter((p) => p.status === "broken").map((p) => p.name),
|
|
8027
|
+
stillBootstrapping: projects.filter((p) => p.status === "bootstrapping").map((p) => p.name)
|
|
8028
|
+
};
|
|
7850
8029
|
}
|
|
7851
8030
|
function spawnDaemonDetached() {
|
|
7852
8031
|
const here = import_node_path42.default.dirname(new URL(importMetaUrl).pathname);
|
|
@@ -7867,12 +8046,21 @@ function spawnDaemonDetached() {
|
|
|
7867
8046
|
if (!entry2) {
|
|
7868
8047
|
throw new Error(`orchestrator: cannot locate neatd entry in ${here}`);
|
|
7869
8048
|
}
|
|
8049
|
+
const env = { ...process.env };
|
|
8050
|
+
const hasToken = typeof env.NEAT_AUTH_TOKEN === "string" && env.NEAT_AUTH_TOKEN.length > 0;
|
|
8051
|
+
if (!hasToken && (!env.HOST || env.HOST.length === 0)) {
|
|
8052
|
+
env.HOST = "127.0.0.1";
|
|
8053
|
+
}
|
|
7870
8054
|
const child = (0, import_node_child_process2.spawn)(process.execPath, [entry2, "start"], {
|
|
7871
8055
|
detached: true,
|
|
7872
|
-
|
|
7873
|
-
|
|
8056
|
+
// stderr inherits the orchestrator's fd so the daemon's
|
|
8057
|
+
// `BindAuthorityError` message lands in front of the operator instead
|
|
8058
|
+
// of being swallowed (issue #341).
|
|
8059
|
+
stdio: ["ignore", "ignore", "inherit"],
|
|
8060
|
+
env
|
|
7874
8061
|
});
|
|
7875
8062
|
child.unref();
|
|
8063
|
+
return child;
|
|
7876
8064
|
}
|
|
7877
8065
|
function openBrowser(url) {
|
|
7878
8066
|
if (!process.stdout.isTTY) return "failed";
|
|
@@ -7948,7 +8136,7 @@ async function runOrchestrator(opts) {
|
|
|
7948
8136
|
result.steps.apply.skipped = true;
|
|
7949
8137
|
console.log("skipped instrumentation (--no-instrument)");
|
|
7950
8138
|
} else {
|
|
7951
|
-
const tally = await applyInstallersOver(services);
|
|
8139
|
+
const tally = await applyInstallersOver(services, opts.project);
|
|
7952
8140
|
result.steps.apply = { ...tally, skipped: false };
|
|
7953
8141
|
console.log(
|
|
7954
8142
|
`instrumented ${tally.instrumented}, already ${tally.alreadyInstrumented}, lib-only ${tally.libOnly}`
|
|
@@ -7967,12 +8155,25 @@ async function runOrchestrator(opts) {
|
|
|
7967
8155
|
return result;
|
|
7968
8156
|
}
|
|
7969
8157
|
const ready = await waitForDaemonReady(restPort, timeoutMs);
|
|
7970
|
-
result.steps.daemon = ready ? "spawned" : "timed-out";
|
|
7971
|
-
if (!ready) {
|
|
8158
|
+
result.steps.daemon = ready.ready ? "spawned" : "timed-out";
|
|
8159
|
+
if (!ready.ready) {
|
|
7972
8160
|
console.error(`neat: daemon did not become ready within ${timeoutMs}ms`);
|
|
8161
|
+
if (ready.stillBootstrapping.length > 0) {
|
|
8162
|
+
console.error(
|
|
8163
|
+
`neat: still bootstrapping: ${ready.stillBootstrapping.join(", ")}`
|
|
8164
|
+
);
|
|
8165
|
+
}
|
|
8166
|
+
if (ready.brokenProjects.length > 0) {
|
|
8167
|
+
console.error(`neat: broken projects: ${ready.brokenProjects.join(", ")}`);
|
|
8168
|
+
}
|
|
7973
8169
|
result.exitCode = 1;
|
|
7974
8170
|
return result;
|
|
7975
8171
|
}
|
|
8172
|
+
if (ready.brokenProjects.length > 0) {
|
|
8173
|
+
console.warn(
|
|
8174
|
+
`neat: ${ready.brokenProjects.length} project(s) reported broken: ${ready.brokenProjects.join(", ")}`
|
|
8175
|
+
);
|
|
8176
|
+
}
|
|
7976
8177
|
}
|
|
7977
8178
|
const dashboardUrl = opts.dashboardUrl ?? "http://localhost:6328";
|
|
7978
8179
|
if (opts.noOpen || !process.stdout.isTTY) {
|
|
@@ -8603,7 +8804,7 @@ async function runSync(opts) {
|
|
|
8603
8804
|
snapshotPath = target;
|
|
8604
8805
|
}
|
|
8605
8806
|
const skipApply = opts.dryRun || opts.noInstrument;
|
|
8606
|
-
const applyTally = skipApply ? { instrumented: 0, alreadyInstrumented: 0, libOnly: 0 } : await applyInstallersOver(persisted.services);
|
|
8807
|
+
const applyTally = skipApply ? { instrumented: 0, alreadyInstrumented: 0, libOnly: 0 } : await applyInstallersOver(persisted.services, entry2.name);
|
|
8607
8808
|
const warnings = [];
|
|
8608
8809
|
let daemonState = "skipped";
|
|
8609
8810
|
let exitCode = 0;
|
|
@@ -8699,6 +8900,8 @@ function usage() {
|
|
|
8699
8900
|
console.log(" uninstall <name>");
|
|
8700
8901
|
console.log(" Remove a project from the registry. Does not touch");
|
|
8701
8902
|
console.log(" neat-out/, policy.json, or any user file.");
|
|
8903
|
+
console.log(" version Print the installed @neat.is/core version and exit.");
|
|
8904
|
+
console.log(" Aliases: --version, -v.");
|
|
8702
8905
|
console.log(" skill Install or print the Claude Code MCP drop-in.");
|
|
8703
8906
|
console.log(" Flags:");
|
|
8704
8907
|
console.log(" --print-config print the JSON snippet to stdout");
|
|
@@ -8825,7 +9028,7 @@ function parseArgs(rest) {
|
|
|
8825
9028
|
out.yes = true;
|
|
8826
9029
|
continue;
|
|
8827
9030
|
}
|
|
8828
|
-
if (arg === "--verbose"
|
|
9031
|
+
if (arg === "--verbose") {
|
|
8829
9032
|
out.verbose = true;
|
|
8830
9033
|
continue;
|
|
8831
9034
|
}
|
|
@@ -8884,6 +9087,28 @@ function assignFlag(out, field, value) {
|
|
|
8884
9087
|
;
|
|
8885
9088
|
out[field] = value;
|
|
8886
9089
|
}
|
|
9090
|
+
function readPackageVersion() {
|
|
9091
|
+
const here = typeof __dirname !== "undefined" ? __dirname : import_node_path44.default.dirname((0, import_node_url3.fileURLToPath)(importMetaUrl));
|
|
9092
|
+
const candidates = [
|
|
9093
|
+
import_node_path44.default.resolve(here, "../package.json"),
|
|
9094
|
+
import_node_path44.default.resolve(here, "../../package.json")
|
|
9095
|
+
];
|
|
9096
|
+
for (const candidate of candidates) {
|
|
9097
|
+
try {
|
|
9098
|
+
const raw = (0, import_node_fs28.readFileSync)(candidate, "utf8");
|
|
9099
|
+
const parsed = JSON.parse(raw);
|
|
9100
|
+
if (parsed.name === "@neat.is/core" && typeof parsed.version === "string") {
|
|
9101
|
+
return parsed.version;
|
|
9102
|
+
}
|
|
9103
|
+
} catch {
|
|
9104
|
+
}
|
|
9105
|
+
}
|
|
9106
|
+
return "unknown";
|
|
9107
|
+
}
|
|
9108
|
+
function printVersion() {
|
|
9109
|
+
process.stdout.write(`${readPackageVersion()}
|
|
9110
|
+
`);
|
|
9111
|
+
}
|
|
8887
9112
|
function printBanner() {
|
|
8888
9113
|
console.log("\u2588\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557");
|
|
8889
9114
|
console.log("\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D");
|
|
@@ -8893,7 +9118,7 @@ function printBanner() {
|
|
|
8893
9118
|
console.log("\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D ");
|
|
8894
9119
|
console.log("");
|
|
8895
9120
|
console.log(" Network Expressive Architecting Tool");
|
|
8896
|
-
console.log(" neat.is \xB7 v0.
|
|
9121
|
+
console.log(" neat.is \xB7 v0.4.0 \xB7 Apache 2.0");
|
|
8897
9122
|
console.log("");
|
|
8898
9123
|
}
|
|
8899
9124
|
function printDiscoveryReport(opts, services) {
|
|
@@ -8921,12 +9146,12 @@ function printDiscoveryReport(opts, services) {
|
|
|
8921
9146
|
}
|
|
8922
9147
|
console.log("");
|
|
8923
9148
|
}
|
|
8924
|
-
async function buildPatchSections(services) {
|
|
9149
|
+
async function buildPatchSections(services, project) {
|
|
8925
9150
|
const sections = [];
|
|
8926
9151
|
for (const svc of services) {
|
|
8927
9152
|
const installer = await pickInstaller(svc.dir);
|
|
8928
9153
|
if (!installer) continue;
|
|
8929
|
-
const plan3 = await installer.plan(svc.dir);
|
|
9154
|
+
const plan3 = await installer.plan(svc.dir, { project });
|
|
8930
9155
|
if (isEmptyPlan(plan3) && !plan3.libOnly) continue;
|
|
8931
9156
|
sections.push({ installer: installer.name, plan: plan3 });
|
|
8932
9157
|
}
|
|
@@ -8941,7 +9166,7 @@ async function runInit(opts) {
|
|
|
8941
9166
|
}
|
|
8942
9167
|
const services = await discoverServices(opts.scanPath);
|
|
8943
9168
|
printDiscoveryReport(opts, services);
|
|
8944
|
-
const sections = opts.noInstall ? [] : await buildPatchSections(services);
|
|
9169
|
+
const sections = opts.noInstall ? [] : await buildPatchSections(services, opts.project);
|
|
8945
9170
|
const patch = renderPatch(sections);
|
|
8946
9171
|
const patchPath = import_node_path44.default.join(opts.scanPath, "neat.patch");
|
|
8947
9172
|
if (opts.dryRun) {
|
|
@@ -9099,6 +9324,10 @@ async function main() {
|
|
|
9099
9324
|
usage();
|
|
9100
9325
|
process.exit(0);
|
|
9101
9326
|
}
|
|
9327
|
+
if (cmd === "--version" || cmd === "-v" || cmd === "version") {
|
|
9328
|
+
printVersion();
|
|
9329
|
+
process.exit(0);
|
|
9330
|
+
}
|
|
9102
9331
|
const parsed = parseArgs(rest);
|
|
9103
9332
|
const { positional, apply: apply3, dryRun, noInstall } = parsed;
|
|
9104
9333
|
const project = parsed.project ?? DEFAULT_PROJECT;
|
|
@@ -9494,7 +9723,7 @@ async function runQueryVerb(cmd, parsed) {
|
|
|
9494
9723
|
}
|
|
9495
9724
|
}
|
|
9496
9725
|
var entry = process.argv[1] ?? "";
|
|
9497
|
-
if (/[\\/]cli\.(?:cjs|js)$/.test(entry) || entry.endsWith("/cli") || entry.endsWith("/neat")) {
|
|
9726
|
+
if (/[\\/]cli\.(?:cjs|js)$/.test(entry) || entry.endsWith("/cli") || entry.endsWith("/neat") || entry.endsWith("/neat.is")) {
|
|
9498
9727
|
main().catch((err) => {
|
|
9499
9728
|
console.error(err);
|
|
9500
9729
|
process.exit(1);
|
|
@@ -9505,6 +9734,7 @@ if (/[\\/]cli\.(?:cjs|js)$/.test(entry) || entry.endsWith("/cli") || entry.endsW
|
|
|
9505
9734
|
CLAUDE_SKILL_CONFIG,
|
|
9506
9735
|
QUERY_VERBS,
|
|
9507
9736
|
parseArgs,
|
|
9737
|
+
readPackageVersion,
|
|
9508
9738
|
runInit,
|
|
9509
9739
|
runQueryVerb,
|
|
9510
9740
|
runSkill
|