@neat.is/core 0.4.0 → 0.4.2
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 +312 -87
- 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 +196 -47
- 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");
|
|
@@ -6600,6 +6691,10 @@ var OTEL_ENV = {
|
|
|
6600
6691
|
key: "OTEL_EXPORTER_OTLP_ENDPOINT",
|
|
6601
6692
|
value: "http://localhost:4318"
|
|
6602
6693
|
};
|
|
6694
|
+
function envServiceName(pkg, serviceDir, project) {
|
|
6695
|
+
if (project && project.length > 0) return project;
|
|
6696
|
+
return pkg.name ?? import_node_path40.default.basename(serviceDir);
|
|
6697
|
+
}
|
|
6603
6698
|
async function readPackageJson(serviceDir) {
|
|
6604
6699
|
try {
|
|
6605
6700
|
const raw = await import_node_fs25.promises.readFile(import_node_path40.default.join(serviceDir, "package.json"), "utf8");
|
|
@@ -6824,7 +6919,7 @@ function lineIsOtelInjection(line) {
|
|
|
6824
6919
|
if (trimmed.length === 0) return false;
|
|
6825
6920
|
return /(?:require\(|import\s+)['"]\.\/otel-init[^'"]*['"]/.test(trimmed);
|
|
6826
6921
|
}
|
|
6827
|
-
async function planNext(serviceDir, pkg, manifestPath, nextConfigPath) {
|
|
6922
|
+
async function planNext(serviceDir, pkg, manifestPath, nextConfigPath, project) {
|
|
6828
6923
|
const useTs = await isTypeScriptProject(serviceDir);
|
|
6829
6924
|
const instrumentationFile = import_node_path40.default.join(serviceDir, useTs ? "instrumentation.ts" : "instrumentation.js");
|
|
6830
6925
|
const instrumentationNodeFile = import_node_path40.default.join(
|
|
@@ -6861,7 +6956,7 @@ async function planNext(serviceDir, pkg, manifestPath, nextConfigPath) {
|
|
|
6861
6956
|
if (!await exists2(envNeatFile)) {
|
|
6862
6957
|
generatedFiles.push({
|
|
6863
6958
|
file: envNeatFile,
|
|
6864
|
-
contents: renderEnvNeat(pkg
|
|
6959
|
+
contents: renderEnvNeat(envServiceName(pkg, serviceDir, project)),
|
|
6865
6960
|
skipIfExists: true
|
|
6866
6961
|
});
|
|
6867
6962
|
}
|
|
@@ -6917,12 +7012,12 @@ function buildDependencyEdits(pkg, manifestPath) {
|
|
|
6917
7012
|
}
|
|
6918
7013
|
return edits;
|
|
6919
7014
|
}
|
|
6920
|
-
async function queueEnvNeat(serviceDir, pkg, generatedFiles) {
|
|
7015
|
+
async function queueEnvNeat(serviceDir, pkg, project, generatedFiles) {
|
|
6921
7016
|
const envNeatFile = import_node_path40.default.join(serviceDir, ".env.neat");
|
|
6922
7017
|
if (!await exists2(envNeatFile)) {
|
|
6923
7018
|
generatedFiles.push({
|
|
6924
7019
|
file: envNeatFile,
|
|
6925
|
-
contents: renderEnvNeat(pkg
|
|
7020
|
+
contents: renderEnvNeat(envServiceName(pkg, serviceDir, project)),
|
|
6926
7021
|
skipIfExists: true
|
|
6927
7022
|
});
|
|
6928
7023
|
}
|
|
@@ -6941,7 +7036,7 @@ function fileImportsOtelHook(raw, specifiers) {
|
|
|
6941
7036
|
}
|
|
6942
7037
|
return false;
|
|
6943
7038
|
}
|
|
6944
|
-
async function planRemix(serviceDir, pkg, manifestPath, entryFile) {
|
|
7039
|
+
async function planRemix(serviceDir, pkg, manifestPath, entryFile, project) {
|
|
6945
7040
|
const useTs = await isTypeScriptProject(serviceDir);
|
|
6946
7041
|
const otelServerFile = import_node_path40.default.join(
|
|
6947
7042
|
serviceDir,
|
|
@@ -6956,7 +7051,7 @@ async function planRemix(serviceDir, pkg, manifestPath, entryFile) {
|
|
|
6956
7051
|
skipIfExists: true
|
|
6957
7052
|
});
|
|
6958
7053
|
}
|
|
6959
|
-
await queueEnvNeat(serviceDir, pkg, generatedFiles);
|
|
7054
|
+
await queueEnvNeat(serviceDir, pkg, project, generatedFiles);
|
|
6960
7055
|
const entrypointEdits = [];
|
|
6961
7056
|
try {
|
|
6962
7057
|
const raw = await import_node_fs25.promises.readFile(entryFile, "utf8");
|
|
@@ -6993,7 +7088,7 @@ async function planRemix(serviceDir, pkg, manifestPath, entryFile) {
|
|
|
6993
7088
|
framework: "remix"
|
|
6994
7089
|
};
|
|
6995
7090
|
}
|
|
6996
|
-
async function planSvelteKit(serviceDir, pkg, manifestPath, hooksFile) {
|
|
7091
|
+
async function planSvelteKit(serviceDir, pkg, manifestPath, hooksFile, project) {
|
|
6997
7092
|
const useTs = await isTypeScriptProject(serviceDir);
|
|
6998
7093
|
const otelInitFile = import_node_path40.default.join(
|
|
6999
7094
|
serviceDir,
|
|
@@ -7010,7 +7105,7 @@ async function planSvelteKit(serviceDir, pkg, manifestPath, hooksFile) {
|
|
|
7010
7105
|
skipIfExists: true
|
|
7011
7106
|
});
|
|
7012
7107
|
}
|
|
7013
|
-
await queueEnvNeat(serviceDir, pkg, generatedFiles);
|
|
7108
|
+
await queueEnvNeat(serviceDir, pkg, project, generatedFiles);
|
|
7014
7109
|
if (hooksFile === null) {
|
|
7015
7110
|
generatedFiles.push({
|
|
7016
7111
|
file: resolvedHooksFile,
|
|
@@ -7054,7 +7149,7 @@ async function planSvelteKit(serviceDir, pkg, manifestPath, hooksFile) {
|
|
|
7054
7149
|
framework: "sveltekit"
|
|
7055
7150
|
};
|
|
7056
7151
|
}
|
|
7057
|
-
async function planNuxt(serviceDir, pkg, manifestPath) {
|
|
7152
|
+
async function planNuxt(serviceDir, pkg, manifestPath, project) {
|
|
7058
7153
|
const useTs = await isTypeScriptProject(serviceDir);
|
|
7059
7154
|
const otelPluginFile = import_node_path40.default.join(
|
|
7060
7155
|
serviceDir,
|
|
@@ -7080,7 +7175,7 @@ async function planNuxt(serviceDir, pkg, manifestPath) {
|
|
|
7080
7175
|
skipIfExists: true
|
|
7081
7176
|
});
|
|
7082
7177
|
}
|
|
7083
|
-
await queueEnvNeat(serviceDir, pkg, generatedFiles);
|
|
7178
|
+
await queueEnvNeat(serviceDir, pkg, project, generatedFiles);
|
|
7084
7179
|
const empty = dependencyEdits.length === 0 && generatedFiles.length === 0;
|
|
7085
7180
|
if (empty) {
|
|
7086
7181
|
return {
|
|
@@ -7111,7 +7206,7 @@ async function findAstroMiddleware(serviceDir) {
|
|
|
7111
7206
|
}
|
|
7112
7207
|
return null;
|
|
7113
7208
|
}
|
|
7114
|
-
async function planAstro(serviceDir, pkg, manifestPath) {
|
|
7209
|
+
async function planAstro(serviceDir, pkg, manifestPath, project) {
|
|
7115
7210
|
const useTs = await isTypeScriptProject(serviceDir);
|
|
7116
7211
|
const otelInitFile = import_node_path40.default.join(
|
|
7117
7212
|
serviceDir,
|
|
@@ -7129,7 +7224,7 @@ async function planAstro(serviceDir, pkg, manifestPath) {
|
|
|
7129
7224
|
skipIfExists: true
|
|
7130
7225
|
});
|
|
7131
7226
|
}
|
|
7132
|
-
await queueEnvNeat(serviceDir, pkg, generatedFiles);
|
|
7227
|
+
await queueEnvNeat(serviceDir, pkg, project, generatedFiles);
|
|
7133
7228
|
if (existingMiddleware === null) {
|
|
7134
7229
|
generatedFiles.push({
|
|
7135
7230
|
file: middlewareFile,
|
|
@@ -7173,9 +7268,10 @@ async function planAstro(serviceDir, pkg, manifestPath) {
|
|
|
7173
7268
|
framework: "astro"
|
|
7174
7269
|
};
|
|
7175
7270
|
}
|
|
7176
|
-
async function plan(serviceDir) {
|
|
7271
|
+
async function plan(serviceDir, opts) {
|
|
7177
7272
|
const pkg = await readPackageJson(serviceDir);
|
|
7178
7273
|
const manifestPath = import_node_path40.default.join(serviceDir, "package.json");
|
|
7274
|
+
const project = opts?.project;
|
|
7179
7275
|
const empty = {
|
|
7180
7276
|
language: "javascript",
|
|
7181
7277
|
serviceDir,
|
|
@@ -7188,32 +7284,32 @@ async function plan(serviceDir) {
|
|
|
7188
7284
|
if (hasNextDependency(pkg)) {
|
|
7189
7285
|
const nextConfig = await findNextConfig(serviceDir);
|
|
7190
7286
|
if (nextConfig) {
|
|
7191
|
-
return planNext(serviceDir, pkg, manifestPath, nextConfig);
|
|
7287
|
+
return planNext(serviceDir, pkg, manifestPath, nextConfig, project);
|
|
7192
7288
|
}
|
|
7193
7289
|
}
|
|
7194
7290
|
if (hasRemixDependency(pkg)) {
|
|
7195
7291
|
const remixEntry = await findRemixEntry(serviceDir);
|
|
7196
7292
|
if (remixEntry) {
|
|
7197
|
-
return planRemix(serviceDir, pkg, manifestPath, remixEntry);
|
|
7293
|
+
return planRemix(serviceDir, pkg, manifestPath, remixEntry, project);
|
|
7198
7294
|
}
|
|
7199
7295
|
}
|
|
7200
7296
|
if (hasSvelteKitDependency(pkg)) {
|
|
7201
7297
|
const hooks = await findSvelteKitHooks(serviceDir);
|
|
7202
7298
|
const config = await findSvelteKitConfig(serviceDir);
|
|
7203
7299
|
if (hooks || config) {
|
|
7204
|
-
return planSvelteKit(serviceDir, pkg, manifestPath, hooks);
|
|
7300
|
+
return planSvelteKit(serviceDir, pkg, manifestPath, hooks, project);
|
|
7205
7301
|
}
|
|
7206
7302
|
}
|
|
7207
7303
|
if (hasNuxtDependency(pkg)) {
|
|
7208
7304
|
const nuxtConfig = await findNuxtConfig(serviceDir);
|
|
7209
7305
|
if (nuxtConfig) {
|
|
7210
|
-
return planNuxt(serviceDir, pkg, manifestPath);
|
|
7306
|
+
return planNuxt(serviceDir, pkg, manifestPath, project);
|
|
7211
7307
|
}
|
|
7212
7308
|
}
|
|
7213
7309
|
if (hasAstroDependency(pkg)) {
|
|
7214
7310
|
const astroConfig = await findAstroConfig(serviceDir);
|
|
7215
7311
|
if (astroConfig) {
|
|
7216
|
-
return planAstro(serviceDir, pkg, manifestPath);
|
|
7312
|
+
return planAstro(serviceDir, pkg, manifestPath, project);
|
|
7217
7313
|
}
|
|
7218
7314
|
}
|
|
7219
7315
|
const entryFile = await resolveEntry(serviceDir, pkg);
|
|
@@ -7261,7 +7357,7 @@ async function plan(serviceDir) {
|
|
|
7261
7357
|
if (!await exists2(envNeatFile)) {
|
|
7262
7358
|
generatedFiles.push({
|
|
7263
7359
|
file: envNeatFile,
|
|
7264
|
-
contents: renderEnvNeat(pkg
|
|
7360
|
+
contents: renderEnvNeat(envServiceName(pkg, serviceDir, project)),
|
|
7265
7361
|
skipIfExists: true
|
|
7266
7362
|
});
|
|
7267
7363
|
}
|
|
@@ -7796,14 +7892,14 @@ async function extractAndPersist(opts) {
|
|
|
7796
7892
|
errorsPath: projectPaths.errorsPath
|
|
7797
7893
|
};
|
|
7798
7894
|
}
|
|
7799
|
-
async function applyInstallersOver(services) {
|
|
7895
|
+
async function applyInstallersOver(services, project) {
|
|
7800
7896
|
let instrumented = 0;
|
|
7801
7897
|
let already = 0;
|
|
7802
7898
|
let libOnly = 0;
|
|
7803
7899
|
for (const svc of services) {
|
|
7804
7900
|
const installer = await pickInstaller(svc.dir);
|
|
7805
7901
|
if (!installer) continue;
|
|
7806
|
-
const plan3 = await installer.plan(svc.dir);
|
|
7902
|
+
const plan3 = await installer.plan(svc.dir, { project });
|
|
7807
7903
|
if (isEmptyPlan(plan3) && !plan3.libOnly) {
|
|
7808
7904
|
already++;
|
|
7809
7905
|
continue;
|
|
@@ -7825,28 +7921,106 @@ async function promptYesNo(question) {
|
|
|
7825
7921
|
});
|
|
7826
7922
|
});
|
|
7827
7923
|
}
|
|
7828
|
-
var DEFAULT_DAEMON_READY_TIMEOUT_MS =
|
|
7829
|
-
|
|
7924
|
+
var DEFAULT_DAEMON_READY_TIMEOUT_MS = 6e4;
|
|
7925
|
+
var PROBE_INTERVAL_MS = 500;
|
|
7926
|
+
async function fetchDaemonHealth(restPort) {
|
|
7830
7927
|
return new Promise((resolve) => {
|
|
7831
7928
|
const req = import_node_http.default.get(`http://127.0.0.1:${restPort}/health`, (res) => {
|
|
7832
7929
|
const ok = res.statusCode !== void 0 && res.statusCode >= 200 && res.statusCode < 300;
|
|
7833
|
-
|
|
7834
|
-
|
|
7930
|
+
if (!ok) {
|
|
7931
|
+
res.resume();
|
|
7932
|
+
resolve(null);
|
|
7933
|
+
return;
|
|
7934
|
+
}
|
|
7935
|
+
let body = "";
|
|
7936
|
+
res.setEncoding("utf8");
|
|
7937
|
+
res.on("data", (chunk) => {
|
|
7938
|
+
body += chunk;
|
|
7939
|
+
});
|
|
7940
|
+
res.on("end", () => {
|
|
7941
|
+
try {
|
|
7942
|
+
resolve(JSON.parse(body));
|
|
7943
|
+
} catch {
|
|
7944
|
+
resolve({ ok: true });
|
|
7945
|
+
}
|
|
7946
|
+
});
|
|
7835
7947
|
});
|
|
7836
|
-
req.on("error", () => resolve(
|
|
7948
|
+
req.on("error", () => resolve(null));
|
|
7837
7949
|
req.setTimeout(1e3, () => {
|
|
7838
7950
|
req.destroy();
|
|
7839
|
-
resolve(
|
|
7951
|
+
resolve(null);
|
|
7952
|
+
});
|
|
7953
|
+
});
|
|
7954
|
+
}
|
|
7955
|
+
async function checkDaemonHealth(restPort) {
|
|
7956
|
+
const body = await fetchDaemonHealth(restPort);
|
|
7957
|
+
return body !== null;
|
|
7958
|
+
}
|
|
7959
|
+
async function probeProjectHealth(restPort, name) {
|
|
7960
|
+
return new Promise((resolve) => {
|
|
7961
|
+
const req = import_node_http.default.get(
|
|
7962
|
+
`http://127.0.0.1:${restPort}/projects/${encodeURIComponent(name)}/health`,
|
|
7963
|
+
(res) => {
|
|
7964
|
+
const code = res.statusCode ?? 0;
|
|
7965
|
+
res.resume();
|
|
7966
|
+
if (code >= 200 && code < 300) resolve("active");
|
|
7967
|
+
else resolve("bootstrapping");
|
|
7968
|
+
}
|
|
7969
|
+
);
|
|
7970
|
+
req.on("error", () => resolve("bootstrapping"));
|
|
7971
|
+
req.setTimeout(1e3, () => {
|
|
7972
|
+
req.destroy();
|
|
7973
|
+
resolve("bootstrapping");
|
|
7840
7974
|
});
|
|
7841
7975
|
});
|
|
7842
7976
|
}
|
|
7977
|
+
async function snapshotProjectStatus(restPort, body) {
|
|
7978
|
+
if (body.projects && body.projects.length > 0) {
|
|
7979
|
+
return body.projects.map((p) => ({
|
|
7980
|
+
name: p.name,
|
|
7981
|
+
status: p.status ?? "active"
|
|
7982
|
+
}));
|
|
7983
|
+
}
|
|
7984
|
+
const entries = await listProjects().catch(() => []);
|
|
7985
|
+
if (entries.length === 0) return [];
|
|
7986
|
+
return Promise.all(
|
|
7987
|
+
entries.map(async (entry2) => ({
|
|
7988
|
+
name: entry2.name,
|
|
7989
|
+
status: await probeProjectHealth(restPort, entry2.name)
|
|
7990
|
+
}))
|
|
7991
|
+
);
|
|
7992
|
+
}
|
|
7843
7993
|
async function waitForDaemonReady(restPort, timeoutMs) {
|
|
7844
7994
|
const deadline = Date.now() + timeoutMs;
|
|
7995
|
+
let lastBootstrapping = [];
|
|
7845
7996
|
while (Date.now() < deadline) {
|
|
7846
|
-
|
|
7847
|
-
|
|
7997
|
+
const body = await fetchDaemonHealth(restPort);
|
|
7998
|
+
if (body !== null) {
|
|
7999
|
+
const projects2 = await snapshotProjectStatus(restPort, body);
|
|
8000
|
+
const bootstrapping = projects2.filter((p) => p.status === "bootstrapping").map((p) => p.name);
|
|
8001
|
+
const broken = projects2.filter((p) => p.status === "broken").map((p) => p.name);
|
|
8002
|
+
if (bootstrapping.length === 0) {
|
|
8003
|
+
return { ready: true, brokenProjects: broken, stillBootstrapping: [] };
|
|
8004
|
+
}
|
|
8005
|
+
const key = bootstrapping.slice().sort().join(",");
|
|
8006
|
+
const prevKey = lastBootstrapping.slice().sort().join(",");
|
|
8007
|
+
if (key !== prevKey) {
|
|
8008
|
+
const plural = bootstrapping.length === 1 ? "" : "s";
|
|
8009
|
+
console.log(
|
|
8010
|
+
`neat: waiting on ${bootstrapping.length} project${plural}: ${bootstrapping.join(", ")}`
|
|
8011
|
+
);
|
|
8012
|
+
lastBootstrapping = bootstrapping;
|
|
8013
|
+
}
|
|
8014
|
+
}
|
|
8015
|
+
await new Promise((r) => setTimeout(r, PROBE_INTERVAL_MS));
|
|
7848
8016
|
}
|
|
7849
|
-
|
|
8017
|
+
const final = await fetchDaemonHealth(restPort);
|
|
8018
|
+
const projects = final ? await snapshotProjectStatus(restPort, final) : [];
|
|
8019
|
+
return {
|
|
8020
|
+
ready: false,
|
|
8021
|
+
brokenProjects: projects.filter((p) => p.status === "broken").map((p) => p.name),
|
|
8022
|
+
stillBootstrapping: projects.filter((p) => p.status === "bootstrapping").map((p) => p.name)
|
|
8023
|
+
};
|
|
7850
8024
|
}
|
|
7851
8025
|
function spawnDaemonDetached() {
|
|
7852
8026
|
const here = import_node_path42.default.dirname(new URL(importMetaUrl).pathname);
|
|
@@ -7867,12 +8041,21 @@ function spawnDaemonDetached() {
|
|
|
7867
8041
|
if (!entry2) {
|
|
7868
8042
|
throw new Error(`orchestrator: cannot locate neatd entry in ${here}`);
|
|
7869
8043
|
}
|
|
8044
|
+
const env = { ...process.env };
|
|
8045
|
+
const hasToken = typeof env.NEAT_AUTH_TOKEN === "string" && env.NEAT_AUTH_TOKEN.length > 0;
|
|
8046
|
+
if (!hasToken && (!env.HOST || env.HOST.length === 0)) {
|
|
8047
|
+
env.HOST = "127.0.0.1";
|
|
8048
|
+
}
|
|
7870
8049
|
const child = (0, import_node_child_process2.spawn)(process.execPath, [entry2, "start"], {
|
|
7871
8050
|
detached: true,
|
|
7872
|
-
|
|
7873
|
-
|
|
8051
|
+
// stderr inherits the orchestrator's fd so the daemon's
|
|
8052
|
+
// `BindAuthorityError` message lands in front of the operator instead
|
|
8053
|
+
// of being swallowed (issue #341).
|
|
8054
|
+
stdio: ["ignore", "ignore", "inherit"],
|
|
8055
|
+
env
|
|
7874
8056
|
});
|
|
7875
8057
|
child.unref();
|
|
8058
|
+
return child;
|
|
7876
8059
|
}
|
|
7877
8060
|
function openBrowser(url) {
|
|
7878
8061
|
if (!process.stdout.isTTY) return "failed";
|
|
@@ -7948,7 +8131,7 @@ async function runOrchestrator(opts) {
|
|
|
7948
8131
|
result.steps.apply.skipped = true;
|
|
7949
8132
|
console.log("skipped instrumentation (--no-instrument)");
|
|
7950
8133
|
} else {
|
|
7951
|
-
const tally = await applyInstallersOver(services);
|
|
8134
|
+
const tally = await applyInstallersOver(services, opts.project);
|
|
7952
8135
|
result.steps.apply = { ...tally, skipped: false };
|
|
7953
8136
|
console.log(
|
|
7954
8137
|
`instrumented ${tally.instrumented}, already ${tally.alreadyInstrumented}, lib-only ${tally.libOnly}`
|
|
@@ -7967,12 +8150,25 @@ async function runOrchestrator(opts) {
|
|
|
7967
8150
|
return result;
|
|
7968
8151
|
}
|
|
7969
8152
|
const ready = await waitForDaemonReady(restPort, timeoutMs);
|
|
7970
|
-
result.steps.daemon = ready ? "spawned" : "timed-out";
|
|
7971
|
-
if (!ready) {
|
|
8153
|
+
result.steps.daemon = ready.ready ? "spawned" : "timed-out";
|
|
8154
|
+
if (!ready.ready) {
|
|
7972
8155
|
console.error(`neat: daemon did not become ready within ${timeoutMs}ms`);
|
|
8156
|
+
if (ready.stillBootstrapping.length > 0) {
|
|
8157
|
+
console.error(
|
|
8158
|
+
`neat: still bootstrapping: ${ready.stillBootstrapping.join(", ")}`
|
|
8159
|
+
);
|
|
8160
|
+
}
|
|
8161
|
+
if (ready.brokenProjects.length > 0) {
|
|
8162
|
+
console.error(`neat: broken projects: ${ready.brokenProjects.join(", ")}`);
|
|
8163
|
+
}
|
|
7973
8164
|
result.exitCode = 1;
|
|
7974
8165
|
return result;
|
|
7975
8166
|
}
|
|
8167
|
+
if (ready.brokenProjects.length > 0) {
|
|
8168
|
+
console.warn(
|
|
8169
|
+
`neat: ${ready.brokenProjects.length} project(s) reported broken: ${ready.brokenProjects.join(", ")}`
|
|
8170
|
+
);
|
|
8171
|
+
}
|
|
7976
8172
|
}
|
|
7977
8173
|
const dashboardUrl = opts.dashboardUrl ?? "http://localhost:6328";
|
|
7978
8174
|
if (opts.noOpen || !process.stdout.isTTY) {
|
|
@@ -8603,7 +8799,7 @@ async function runSync(opts) {
|
|
|
8603
8799
|
snapshotPath = target;
|
|
8604
8800
|
}
|
|
8605
8801
|
const skipApply = opts.dryRun || opts.noInstrument;
|
|
8606
|
-
const applyTally = skipApply ? { instrumented: 0, alreadyInstrumented: 0, libOnly: 0 } : await applyInstallersOver(persisted.services);
|
|
8802
|
+
const applyTally = skipApply ? { instrumented: 0, alreadyInstrumented: 0, libOnly: 0 } : await applyInstallersOver(persisted.services, entry2.name);
|
|
8607
8803
|
const warnings = [];
|
|
8608
8804
|
let daemonState = "skipped";
|
|
8609
8805
|
let exitCode = 0;
|
|
@@ -8699,6 +8895,8 @@ function usage() {
|
|
|
8699
8895
|
console.log(" uninstall <name>");
|
|
8700
8896
|
console.log(" Remove a project from the registry. Does not touch");
|
|
8701
8897
|
console.log(" neat-out/, policy.json, or any user file.");
|
|
8898
|
+
console.log(" version Print the installed @neat.is/core version and exit.");
|
|
8899
|
+
console.log(" Aliases: --version, -v.");
|
|
8702
8900
|
console.log(" skill Install or print the Claude Code MCP drop-in.");
|
|
8703
8901
|
console.log(" Flags:");
|
|
8704
8902
|
console.log(" --print-config print the JSON snippet to stdout");
|
|
@@ -8825,7 +9023,7 @@ function parseArgs(rest) {
|
|
|
8825
9023
|
out.yes = true;
|
|
8826
9024
|
continue;
|
|
8827
9025
|
}
|
|
8828
|
-
if (arg === "--verbose"
|
|
9026
|
+
if (arg === "--verbose") {
|
|
8829
9027
|
out.verbose = true;
|
|
8830
9028
|
continue;
|
|
8831
9029
|
}
|
|
@@ -8884,6 +9082,28 @@ function assignFlag(out, field, value) {
|
|
|
8884
9082
|
;
|
|
8885
9083
|
out[field] = value;
|
|
8886
9084
|
}
|
|
9085
|
+
function readPackageVersion() {
|
|
9086
|
+
const here = typeof __dirname !== "undefined" ? __dirname : import_node_path44.default.dirname((0, import_node_url3.fileURLToPath)(importMetaUrl));
|
|
9087
|
+
const candidates = [
|
|
9088
|
+
import_node_path44.default.resolve(here, "../package.json"),
|
|
9089
|
+
import_node_path44.default.resolve(here, "../../package.json")
|
|
9090
|
+
];
|
|
9091
|
+
for (const candidate of candidates) {
|
|
9092
|
+
try {
|
|
9093
|
+
const raw = (0, import_node_fs28.readFileSync)(candidate, "utf8");
|
|
9094
|
+
const parsed = JSON.parse(raw);
|
|
9095
|
+
if (parsed.name === "@neat.is/core" && typeof parsed.version === "string") {
|
|
9096
|
+
return parsed.version;
|
|
9097
|
+
}
|
|
9098
|
+
} catch {
|
|
9099
|
+
}
|
|
9100
|
+
}
|
|
9101
|
+
return "unknown";
|
|
9102
|
+
}
|
|
9103
|
+
function printVersion() {
|
|
9104
|
+
process.stdout.write(`${readPackageVersion()}
|
|
9105
|
+
`);
|
|
9106
|
+
}
|
|
8887
9107
|
function printBanner() {
|
|
8888
9108
|
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
9109
|
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 +9113,7 @@ function printBanner() {
|
|
|
8893
9113
|
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
9114
|
console.log("");
|
|
8895
9115
|
console.log(" Network Expressive Architecting Tool");
|
|
8896
|
-
console.log(" neat.is \xB7 v0.
|
|
9116
|
+
console.log(" neat.is \xB7 v0.4.0 \xB7 Apache 2.0");
|
|
8897
9117
|
console.log("");
|
|
8898
9118
|
}
|
|
8899
9119
|
function printDiscoveryReport(opts, services) {
|
|
@@ -8921,12 +9141,12 @@ function printDiscoveryReport(opts, services) {
|
|
|
8921
9141
|
}
|
|
8922
9142
|
console.log("");
|
|
8923
9143
|
}
|
|
8924
|
-
async function buildPatchSections(services) {
|
|
9144
|
+
async function buildPatchSections(services, project) {
|
|
8925
9145
|
const sections = [];
|
|
8926
9146
|
for (const svc of services) {
|
|
8927
9147
|
const installer = await pickInstaller(svc.dir);
|
|
8928
9148
|
if (!installer) continue;
|
|
8929
|
-
const plan3 = await installer.plan(svc.dir);
|
|
9149
|
+
const plan3 = await installer.plan(svc.dir, { project });
|
|
8930
9150
|
if (isEmptyPlan(plan3) && !plan3.libOnly) continue;
|
|
8931
9151
|
sections.push({ installer: installer.name, plan: plan3 });
|
|
8932
9152
|
}
|
|
@@ -8941,7 +9161,7 @@ async function runInit(opts) {
|
|
|
8941
9161
|
}
|
|
8942
9162
|
const services = await discoverServices(opts.scanPath);
|
|
8943
9163
|
printDiscoveryReport(opts, services);
|
|
8944
|
-
const sections = opts.noInstall ? [] : await buildPatchSections(services);
|
|
9164
|
+
const sections = opts.noInstall ? [] : await buildPatchSections(services, opts.project);
|
|
8945
9165
|
const patch = renderPatch(sections);
|
|
8946
9166
|
const patchPath = import_node_path44.default.join(opts.scanPath, "neat.patch");
|
|
8947
9167
|
if (opts.dryRun) {
|
|
@@ -9099,6 +9319,10 @@ async function main() {
|
|
|
9099
9319
|
usage();
|
|
9100
9320
|
process.exit(0);
|
|
9101
9321
|
}
|
|
9322
|
+
if (cmd === "--version" || cmd === "-v" || cmd === "version") {
|
|
9323
|
+
printVersion();
|
|
9324
|
+
process.exit(0);
|
|
9325
|
+
}
|
|
9102
9326
|
const parsed = parseArgs(rest);
|
|
9103
9327
|
const { positional, apply: apply3, dryRun, noInstall } = parsed;
|
|
9104
9328
|
const project = parsed.project ?? DEFAULT_PROJECT;
|
|
@@ -9505,6 +9729,7 @@ if (/[\\/]cli\.(?:cjs|js)$/.test(entry) || entry.endsWith("/cli") || entry.endsW
|
|
|
9505
9729
|
CLAUDE_SKILL_CONFIG,
|
|
9506
9730
|
QUERY_VERBS,
|
|
9507
9731
|
parseArgs,
|
|
9732
|
+
readPackageVersion,
|
|
9508
9733
|
runInit,
|
|
9509
9734
|
runQueryVerb,
|
|
9510
9735
|
runSkill
|