@neat.is/core 0.4.17 → 0.4.18
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-6CO7C4IU.js → chunk-BGPWBRLU.js} +4 -2
- package/dist/{chunk-6CO7C4IU.js.map → chunk-BGPWBRLU.js.map} +1 -1
- package/dist/{chunk-CEDXXMGO.js → chunk-GXWBMJDJ.js} +207 -22
- package/dist/chunk-GXWBMJDJ.js.map +1 -0
- package/dist/{chunk-GHPHVXYM.js → chunk-MTXF77TN.js} +2 -2
- package/dist/{chunk-LUDSPX5N.js → chunk-T3X6XJPU.js} +111 -2
- package/dist/{chunk-LUDSPX5N.js.map → chunk-T3X6XJPU.js.map} +1 -1
- package/dist/cli.cjs +453 -135
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +256 -70
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +193 -19
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +43 -13
- package/dist/index.d.ts +43 -13
- package/dist/index.js +4 -4
- package/dist/neatd.cjs +355 -57
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +161 -37
- package/dist/neatd.js.map +1 -1
- package/dist/{otel-grpc-QAISVAY5.js → otel-grpc-I67ONCRM.js} +3 -3
- package/dist/server.cjs +2 -0
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +3 -3
- package/package.json +2 -2
- package/dist/chunk-CEDXXMGO.js.map +0 -1
- /package/dist/{chunk-GHPHVXYM.js.map → chunk-MTXF77TN.js.map} +0 -0
- /package/dist/{otel-grpc-QAISVAY5.js.map → otel-grpc-I67ONCRM.js.map} +0 -0
package/dist/cli.cjs
CHANGED
|
@@ -58,9 +58,9 @@ function mountBearerAuth(app, opts) {
|
|
|
58
58
|
const suffixes = [...DEFAULT_UNAUTH_SUFFIXES, ...opts.extraUnauthenticatedSuffixes ?? []];
|
|
59
59
|
const publicRead = opts.publicRead === true;
|
|
60
60
|
app.addHook("preHandler", (req, reply, done) => {
|
|
61
|
-
const
|
|
61
|
+
const path53 = (req.url.split("?")[0] ?? "").replace(/\/+$/, "");
|
|
62
62
|
for (const suffix of suffixes) {
|
|
63
|
-
if (
|
|
63
|
+
if (path53 === suffix || path53.endsWith(suffix)) {
|
|
64
64
|
done();
|
|
65
65
|
return;
|
|
66
66
|
}
|
|
@@ -474,8 +474,10 @@ async function buildOtelReceiver(opts) {
|
|
|
474
474
|
for (const s of spans) projectQueue.push({ project, span: s });
|
|
475
475
|
projectDrainPromise = projectDrainPromise.then(() => drainProject());
|
|
476
476
|
};
|
|
477
|
+
const offersProjectRouting = opts.onProjectSpan !== void 0;
|
|
477
478
|
const legacyEndpointWarned = /* @__PURE__ */ new Set();
|
|
478
479
|
function warnLegacyEndpoint(serviceName) {
|
|
480
|
+
if (!offersProjectRouting) return;
|
|
479
481
|
if (legacyEndpointWarned.has(serviceName)) return;
|
|
480
482
|
legacyEndpointWarned.add(serviceName);
|
|
481
483
|
console.warn(
|
|
@@ -611,8 +613,8 @@ __export(cli_exports, {
|
|
|
611
613
|
});
|
|
612
614
|
module.exports = __toCommonJS(cli_exports);
|
|
613
615
|
init_cjs_shims();
|
|
614
|
-
var
|
|
615
|
-
var
|
|
616
|
+
var import_node_path52 = __toESM(require("path"), 1);
|
|
617
|
+
var import_node_fs34 = require("fs");
|
|
616
618
|
|
|
617
619
|
// src/banner.ts
|
|
618
620
|
init_cjs_shims();
|
|
@@ -1177,19 +1179,19 @@ function confidenceFromMix(edges, now = Date.now()) {
|
|
|
1177
1179
|
function longestIncomingWalk(graph, start, maxDepth) {
|
|
1178
1180
|
let best = { path: [start], edges: [] };
|
|
1179
1181
|
const visited = /* @__PURE__ */ new Set([start]);
|
|
1180
|
-
function step(node,
|
|
1181
|
-
if (
|
|
1182
|
-
best = { path: [...
|
|
1182
|
+
function step(node, path53, edges) {
|
|
1183
|
+
if (path53.length > best.path.length) {
|
|
1184
|
+
best = { path: [...path53], edges: [...edges] };
|
|
1183
1185
|
}
|
|
1184
|
-
if (
|
|
1186
|
+
if (path53.length - 1 >= maxDepth) return;
|
|
1185
1187
|
const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
|
|
1186
1188
|
for (const [srcId, edge] of incoming) {
|
|
1187
1189
|
if (visited.has(srcId)) continue;
|
|
1188
1190
|
visited.add(srcId);
|
|
1189
|
-
|
|
1191
|
+
path53.push(srcId);
|
|
1190
1192
|
edges.push(edge);
|
|
1191
|
-
step(srcId,
|
|
1192
|
-
|
|
1193
|
+
step(srcId, path53, edges);
|
|
1194
|
+
path53.pop();
|
|
1193
1195
|
edges.pop();
|
|
1194
1196
|
visited.delete(srcId);
|
|
1195
1197
|
}
|
|
@@ -6259,6 +6261,111 @@ function registryLockPath() {
|
|
|
6259
6261
|
function daemonPidPath() {
|
|
6260
6262
|
return import_node_path40.default.join(neatHome(), "neatd.pid");
|
|
6261
6263
|
}
|
|
6264
|
+
function daemonsDir() {
|
|
6265
|
+
return import_node_path40.default.join(neatHome(), "daemons");
|
|
6266
|
+
}
|
|
6267
|
+
function isFiniteInt(v) {
|
|
6268
|
+
return typeof v === "number" && Number.isFinite(v);
|
|
6269
|
+
}
|
|
6270
|
+
function parseDaemonRecord(raw) {
|
|
6271
|
+
let obj;
|
|
6272
|
+
try {
|
|
6273
|
+
obj = JSON.parse(raw);
|
|
6274
|
+
} catch {
|
|
6275
|
+
return void 0;
|
|
6276
|
+
}
|
|
6277
|
+
if (typeof obj !== "object" || obj === null) return void 0;
|
|
6278
|
+
const r = obj;
|
|
6279
|
+
const ports = r.ports;
|
|
6280
|
+
if (typeof r.project !== "string" || typeof r.projectPath !== "string" || !isFiniteInt(r.pid) || r.status !== "running" && r.status !== "stopped" || typeof r.startedAt !== "string" || typeof r.neatVersion !== "string" || !ports || !isFiniteInt(ports.rest) || !isFiniteInt(ports.otlp) || !isFiniteInt(ports.web)) {
|
|
6281
|
+
return void 0;
|
|
6282
|
+
}
|
|
6283
|
+
return {
|
|
6284
|
+
project: r.project,
|
|
6285
|
+
projectPath: r.projectPath,
|
|
6286
|
+
pid: r.pid,
|
|
6287
|
+
status: r.status,
|
|
6288
|
+
ports: { rest: ports.rest, otlp: ports.otlp, web: ports.web },
|
|
6289
|
+
startedAt: r.startedAt,
|
|
6290
|
+
neatVersion: r.neatVersion
|
|
6291
|
+
};
|
|
6292
|
+
}
|
|
6293
|
+
var defaultDiscoveryProbe = { isPidAlive: isPidAliveDefault };
|
|
6294
|
+
async function discoverDaemons(probe = defaultDiscoveryProbe) {
|
|
6295
|
+
const dir = daemonsDir();
|
|
6296
|
+
let names;
|
|
6297
|
+
try {
|
|
6298
|
+
names = await import_node_fs25.promises.readdir(dir);
|
|
6299
|
+
} catch (err) {
|
|
6300
|
+
if (err.code === "ENOENT") return [];
|
|
6301
|
+
throw err;
|
|
6302
|
+
}
|
|
6303
|
+
const out = [];
|
|
6304
|
+
for (const name of names) {
|
|
6305
|
+
if (!name.endsWith(".json")) continue;
|
|
6306
|
+
const file = import_node_path40.default.join(dir, name);
|
|
6307
|
+
let raw;
|
|
6308
|
+
try {
|
|
6309
|
+
raw = await import_node_fs25.promises.readFile(file, "utf8");
|
|
6310
|
+
} catch {
|
|
6311
|
+
continue;
|
|
6312
|
+
}
|
|
6313
|
+
const record = parseDaemonRecord(raw);
|
|
6314
|
+
if (!record) continue;
|
|
6315
|
+
const live = record.status === "running" && probe.isPidAlive(record.pid);
|
|
6316
|
+
out.push({ record, live, source: file });
|
|
6317
|
+
}
|
|
6318
|
+
out.sort((a, b) => a.record.project.localeCompare(b.record.project));
|
|
6319
|
+
return out;
|
|
6320
|
+
}
|
|
6321
|
+
async function removeDaemonRecord(source) {
|
|
6322
|
+
await import_node_fs25.promises.unlink(source).catch(() => {
|
|
6323
|
+
});
|
|
6324
|
+
}
|
|
6325
|
+
async function listMachineProjects(probe = defaultDiscoveryProbe) {
|
|
6326
|
+
const discovered = await discoverDaemons(probe);
|
|
6327
|
+
const byPath = /* @__PURE__ */ new Map();
|
|
6328
|
+
for (const d of discovered) {
|
|
6329
|
+
const key = await normalizeProjectPath(d.record.projectPath);
|
|
6330
|
+
byPath.set(key, {
|
|
6331
|
+
project: d.record.project,
|
|
6332
|
+
projectPath: d.record.projectPath,
|
|
6333
|
+
state: d.live ? "running" : "stopped",
|
|
6334
|
+
ports: d.record.ports,
|
|
6335
|
+
pid: d.record.pid
|
|
6336
|
+
});
|
|
6337
|
+
}
|
|
6338
|
+
let legacy = [];
|
|
6339
|
+
try {
|
|
6340
|
+
legacy = (await readRegistry()).projects;
|
|
6341
|
+
} catch {
|
|
6342
|
+
legacy = [];
|
|
6343
|
+
}
|
|
6344
|
+
for (const entry2 of legacy) {
|
|
6345
|
+
const key = await normalizeProjectPath(entry2.path);
|
|
6346
|
+
if (byPath.has(key)) continue;
|
|
6347
|
+
byPath.set(key, {
|
|
6348
|
+
project: entry2.name,
|
|
6349
|
+
projectPath: entry2.path,
|
|
6350
|
+
state: "registered",
|
|
6351
|
+
registryStatus: entry2.status
|
|
6352
|
+
});
|
|
6353
|
+
}
|
|
6354
|
+
return [...byPath.values()].sort((a, b) => a.project.localeCompare(b.project));
|
|
6355
|
+
}
|
|
6356
|
+
async function findDaemonByProject(name, probe = defaultDiscoveryProbe) {
|
|
6357
|
+
const discovered = await discoverDaemons(probe);
|
|
6358
|
+
return discovered.find((d) => d.record.project === name);
|
|
6359
|
+
}
|
|
6360
|
+
function signalDaemonStop(pid) {
|
|
6361
|
+
try {
|
|
6362
|
+
process.kill(pid, "SIGTERM");
|
|
6363
|
+
return true;
|
|
6364
|
+
} catch (err) {
|
|
6365
|
+
if (err.code === "ESRCH") return false;
|
|
6366
|
+
throw err;
|
|
6367
|
+
}
|
|
6368
|
+
}
|
|
6262
6369
|
function isPidAliveDefault(pid) {
|
|
6263
6370
|
try {
|
|
6264
6371
|
process.kill(pid, 0);
|
|
@@ -7994,9 +8101,50 @@ var import_semver2 = __toESM(require("semver"), 1);
|
|
|
7994
8101
|
// src/installers/templates.ts
|
|
7995
8102
|
init_cjs_shims();
|
|
7996
8103
|
var OTEL_INIT_HEADER = "// Generated by `neat init --apply` (ADR-069). OpenTelemetry auto-instrumentation hook.";
|
|
7997
|
-
var OTEL_INIT_STAMP = "// neat-template-version:
|
|
8104
|
+
var OTEL_INIT_STAMP = "// neat-template-version: 6 \u2014 daemon.json endpoint resolution (ADR-096) + layered file-first capture (ADR-090).";
|
|
7998
8105
|
var OTEL_OTLP_HEADERS_JS = "if (process.env.NEAT_OTEL_TOKEN) process.env.OTEL_EXPORTER_OTLP_HEADERS ||= 'Authorization=Bearer ' + process.env.NEAT_OTEL_TOKEN";
|
|
7999
8106
|
var OTEL_OTLP_PROTOCOL_JS = "process.env.OTEL_EXPORTER_OTLP_PROTOCOL ||= 'http/json'";
|
|
8107
|
+
var OTEL_ENDPOINT_RESOLVER_CJS = `;(function () {
|
|
8108
|
+
try {
|
|
8109
|
+
if (process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT || process.env.OTEL_EXPORTER_OTLP_ENDPOINT) return
|
|
8110
|
+
const __neatFs = require('node:fs')
|
|
8111
|
+
const __neatPath = require('node:path')
|
|
8112
|
+
let __neatDir = process.cwd()
|
|
8113
|
+
for (let __i = 0; __i < 8; __i++) {
|
|
8114
|
+
try {
|
|
8115
|
+
const __rec = JSON.parse(__neatFs.readFileSync(__neatPath.join(__neatDir, 'neat-out', 'daemon.json'), 'utf8'))
|
|
8116
|
+
if (__rec && __rec.ports && typeof __rec.ports.otlp === 'number') {
|
|
8117
|
+
process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT = 'http://localhost:' + __rec.ports.otlp + '/v1/traces'
|
|
8118
|
+
break
|
|
8119
|
+
}
|
|
8120
|
+
} catch (_e) {}
|
|
8121
|
+
const __parent = __neatPath.dirname(__neatDir)
|
|
8122
|
+
if (__parent === __neatDir) break
|
|
8123
|
+
__neatDir = __parent
|
|
8124
|
+
}
|
|
8125
|
+
} catch (_e) {}
|
|
8126
|
+
process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT ||= 'http://localhost:4318/v1/traces'
|
|
8127
|
+
})()`;
|
|
8128
|
+
var OTEL_ESM_NODE_IMPORTS = "import { readFileSync as __neatReadFileSync } from 'node:fs'\nimport { join as __neatJoin, dirname as __neatDirname } from 'node:path'";
|
|
8129
|
+
var OTEL_ENDPOINT_RESOLVER_ESM = `;(function () {
|
|
8130
|
+
try {
|
|
8131
|
+
if (process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT || process.env.OTEL_EXPORTER_OTLP_ENDPOINT) return
|
|
8132
|
+
let __neatDir = process.cwd()
|
|
8133
|
+
for (let __i = 0; __i < 8; __i++) {
|
|
8134
|
+
try {
|
|
8135
|
+
const __rec = JSON.parse(__neatReadFileSync(__neatJoin(__neatDir, 'neat-out', 'daemon.json'), 'utf8'))
|
|
8136
|
+
if (__rec && __rec.ports && typeof __rec.ports.otlp === 'number') {
|
|
8137
|
+
process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT = 'http://localhost:' + __rec.ports.otlp + '/v1/traces'
|
|
8138
|
+
break
|
|
8139
|
+
}
|
|
8140
|
+
} catch (_e) {}
|
|
8141
|
+
const __parent = __neatDirname(__neatDir)
|
|
8142
|
+
if (__parent === __neatDir) break
|
|
8143
|
+
__neatDir = __parent
|
|
8144
|
+
}
|
|
8145
|
+
} catch (_e) {}
|
|
8146
|
+
process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT ||= 'http://localhost:4318/v1/traces'
|
|
8147
|
+
})()`;
|
|
8000
8148
|
function neatCaptureSource(ts) {
|
|
8001
8149
|
const spanT = ts ? ": any" : "";
|
|
8002
8150
|
const strOpt = ts ? ": string | undefined" : "";
|
|
@@ -8285,7 +8433,7 @@ try {
|
|
|
8285
8433
|
var OTEL_INIT_CJS = `${OTEL_INIT_HEADER}
|
|
8286
8434
|
${OTEL_INIT_STAMP}
|
|
8287
8435
|
process.env.OTEL_SERVICE_NAME ||= '__SERVICE_NAME__'
|
|
8288
|
-
|
|
8436
|
+
${OTEL_ENDPOINT_RESOLVER_CJS}
|
|
8289
8437
|
${OTEL_OTLP_PROTOCOL_JS}
|
|
8290
8438
|
${OTEL_OTLP_HEADERS_JS}
|
|
8291
8439
|
|
|
@@ -8303,8 +8451,9 @@ ${neatWireCaptureSource(false)}
|
|
|
8303
8451
|
`;
|
|
8304
8452
|
var OTEL_INIT_ESM = `${OTEL_INIT_HEADER}
|
|
8305
8453
|
${OTEL_INIT_STAMP}
|
|
8454
|
+
${OTEL_ESM_NODE_IMPORTS}
|
|
8306
8455
|
process.env.OTEL_SERVICE_NAME ||= '__SERVICE_NAME__'
|
|
8307
|
-
|
|
8456
|
+
${OTEL_ENDPOINT_RESOLVER_ESM}
|
|
8308
8457
|
${OTEL_OTLP_PROTOCOL_JS}
|
|
8309
8458
|
${OTEL_OTLP_HEADERS_JS}
|
|
8310
8459
|
|
|
@@ -8327,8 +8476,9 @@ ${OTEL_INIT_STAMP}
|
|
|
8327
8476
|
// strict user tsconfig would reject; suppressing type-checking here keeps the
|
|
8328
8477
|
// generated file from breaking the host project's \`tsc\` gate (#427) without
|
|
8329
8478
|
// constraining the runtime logic. The file is regenerated, never hand-edited.
|
|
8479
|
+
${OTEL_ESM_NODE_IMPORTS}
|
|
8330
8480
|
process.env.OTEL_SERVICE_NAME ||= '__SERVICE_NAME__'
|
|
8331
|
-
|
|
8481
|
+
${OTEL_ENDPOINT_RESOLVER_ESM}
|
|
8332
8482
|
${OTEL_OTLP_PROTOCOL_JS}
|
|
8333
8483
|
${OTEL_OTLP_HEADERS_JS}
|
|
8334
8484
|
|
|
@@ -8350,11 +8500,14 @@ ${registrations.join("\n")}
|
|
|
8350
8500
|
`;
|
|
8351
8501
|
return template.replace(/__SERVICE_NAME__/g, serviceName).replace(/__PROJECT__/g, projectName).replace(/__INSTRUMENTATION_BLOCK__\n?/g, block);
|
|
8352
8502
|
}
|
|
8353
|
-
function renderEnvNeat(serviceName,
|
|
8503
|
+
function renderEnvNeat(serviceName, _projectName) {
|
|
8354
8504
|
return [
|
|
8355
8505
|
"# Generated by `neat init --apply` (ADR-069).",
|
|
8356
8506
|
`OTEL_SERVICE_NAME=${serviceName}`,
|
|
8357
|
-
|
|
8507
|
+
"# Advisory only \u2014 the generated otel-init resolves the live endpoint from",
|
|
8508
|
+
"# <project>/neat-out/daemon.json (ports.otlp) at boot (ADR-096). This is the",
|
|
8509
|
+
"# canonical default the daemon takes when its first-choice OTLP port is free.",
|
|
8510
|
+
"OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=http://localhost:4318/v1/traces",
|
|
8358
8511
|
"OTEL_EXPORTER_OTLP_PROTOCOL=http/json",
|
|
8359
8512
|
"# Set NEAT_OTEL_TOKEN to the daemon's OTLP secret to authenticate exported spans (#410).",
|
|
8360
8513
|
"# NEAT_OTEL_TOKEN=",
|
|
@@ -8377,8 +8530,9 @@ export async function register() {
|
|
|
8377
8530
|
}
|
|
8378
8531
|
`;
|
|
8379
8532
|
var NEXT_INSTRUMENTATION_NODE_TS = `${NEXT_INSTRUMENTATION_HEADER}
|
|
8533
|
+
${OTEL_ESM_NODE_IMPORTS}
|
|
8380
8534
|
process.env.OTEL_SERVICE_NAME ||= '__SERVICE_NAME__'
|
|
8381
|
-
|
|
8535
|
+
${OTEL_ENDPOINT_RESOLVER_ESM}
|
|
8382
8536
|
${OTEL_OTLP_PROTOCOL_JS}
|
|
8383
8537
|
${OTEL_OTLP_HEADERS_JS}
|
|
8384
8538
|
|
|
@@ -8391,7 +8545,7 @@ new NodeSDK({ instrumentations }).start()
|
|
|
8391
8545
|
`;
|
|
8392
8546
|
var NEXT_INSTRUMENTATION_NODE_JS = `${NEXT_INSTRUMENTATION_HEADER}
|
|
8393
8547
|
process.env.OTEL_SERVICE_NAME ||= '__SERVICE_NAME__'
|
|
8394
|
-
|
|
8548
|
+
${OTEL_ENDPOINT_RESOLVER_CJS}
|
|
8395
8549
|
${OTEL_OTLP_PROTOCOL_JS}
|
|
8396
8550
|
${OTEL_OTLP_HEADERS_JS}
|
|
8397
8551
|
|
|
@@ -8410,8 +8564,9 @@ ${registrations.join("\n")}
|
|
|
8410
8564
|
}
|
|
8411
8565
|
var FRAMEWORK_OTEL_INIT_HEADER = "// Generated by `neat init --apply` (ADR-074). OpenTelemetry SDK hook.";
|
|
8412
8566
|
var FRAMEWORK_OTEL_INIT_TS_BODY = `${FRAMEWORK_OTEL_INIT_HEADER}
|
|
8567
|
+
${OTEL_ESM_NODE_IMPORTS}
|
|
8413
8568
|
process.env.OTEL_SERVICE_NAME ||= '__SERVICE_NAME__'
|
|
8414
|
-
|
|
8569
|
+
${OTEL_ENDPOINT_RESOLVER_ESM}
|
|
8415
8570
|
${OTEL_OTLP_PROTOCOL_JS}
|
|
8416
8571
|
${OTEL_OTLP_HEADERS_JS}
|
|
8417
8572
|
|
|
@@ -8426,7 +8581,7 @@ sdk.start()
|
|
|
8426
8581
|
`;
|
|
8427
8582
|
var FRAMEWORK_OTEL_INIT_JS_BODY = `${FRAMEWORK_OTEL_INIT_HEADER}
|
|
8428
8583
|
process.env.OTEL_SERVICE_NAME ||= '__SERVICE_NAME__'
|
|
8429
|
-
|
|
8584
|
+
${OTEL_ENDPOINT_RESOLVER_CJS}
|
|
8430
8585
|
${OTEL_OTLP_PROTOCOL_JS}
|
|
8431
8586
|
${OTEL_OTLP_HEADERS_JS}
|
|
8432
8587
|
|
|
@@ -9895,19 +10050,51 @@ function renderPatch(sections) {
|
|
|
9895
10050
|
|
|
9896
10051
|
// src/orchestrator.ts
|
|
9897
10052
|
init_cjs_shims();
|
|
9898
|
-
var
|
|
10053
|
+
var import_node_fs33 = require("fs");
|
|
9899
10054
|
var import_node_http = __toESM(require("http"), 1);
|
|
9900
10055
|
var import_node_net = __toESM(require("net"), 1);
|
|
9901
|
-
var
|
|
10056
|
+
var import_node_path50 = __toESM(require("path"), 1);
|
|
9902
10057
|
var import_node_child_process3 = require("child_process");
|
|
9903
10058
|
var import_node_readline = __toESM(require("readline"), 1);
|
|
10059
|
+
|
|
10060
|
+
// src/daemon.ts
|
|
10061
|
+
init_cjs_shims();
|
|
10062
|
+
var import_node_fs32 = require("fs");
|
|
10063
|
+
var import_node_path49 = __toESM(require("path"), 1);
|
|
10064
|
+
var import_node_module = require("module");
|
|
10065
|
+
init_otel();
|
|
10066
|
+
init_auth();
|
|
10067
|
+
|
|
10068
|
+
// src/unrouted.ts
|
|
10069
|
+
init_cjs_shims();
|
|
10070
|
+
var import_node_fs31 = require("fs");
|
|
10071
|
+
var import_node_path48 = __toESM(require("path"), 1);
|
|
10072
|
+
|
|
10073
|
+
// src/daemon.ts
|
|
10074
|
+
function daemonJsonPath(scanPath) {
|
|
10075
|
+
return import_node_path49.default.join(scanPath, "neat-out", "daemon.json");
|
|
10076
|
+
}
|
|
10077
|
+
async function readDaemonRecord(scanPath) {
|
|
10078
|
+
try {
|
|
10079
|
+
const raw = await import_node_fs32.promises.readFile(daemonJsonPath(scanPath), "utf8");
|
|
10080
|
+
const parsed = JSON.parse(raw);
|
|
10081
|
+
if (typeof parsed.project === "string" && parsed.ports && typeof parsed.ports.rest === "number" && typeof parsed.ports.otlp === "number" && typeof parsed.ports.web === "number") {
|
|
10082
|
+
return parsed;
|
|
10083
|
+
}
|
|
10084
|
+
return null;
|
|
10085
|
+
} catch {
|
|
10086
|
+
return null;
|
|
10087
|
+
}
|
|
10088
|
+
}
|
|
10089
|
+
|
|
10090
|
+
// src/orchestrator.ts
|
|
9904
10091
|
async function extractAndPersist(opts) {
|
|
9905
10092
|
const services = await discoverServices(opts.scanPath);
|
|
9906
10093
|
const languages = [...new Set(services.map((s) => s.node.language))].sort();
|
|
9907
10094
|
const graphKey = opts.projectExplicit ? opts.project : DEFAULT_PROJECT;
|
|
9908
10095
|
resetGraph(graphKey);
|
|
9909
10096
|
const graph = getGraph(graphKey);
|
|
9910
|
-
const projectPaths = pathsForProject(graphKey,
|
|
10097
|
+
const projectPaths = pathsForProject(graphKey, import_node_path50.default.join(opts.scanPath, "neat-out"));
|
|
9911
10098
|
const extraction = await extractFromDirectory(graph, opts.scanPath, {
|
|
9912
10099
|
errorsPath: projectPaths.errorsPath
|
|
9913
10100
|
});
|
|
@@ -9961,7 +10148,7 @@ async function applyInstallersOver(services, project, options = {}) {
|
|
|
9961
10148
|
console.log(`skipping ${svc.dir}: browser bundle; browser-OTel support lands in a future release.`);
|
|
9962
10149
|
} else if (outcome.outcome === "react-native") {
|
|
9963
10150
|
reactNative++;
|
|
9964
|
-
const svcName =
|
|
10151
|
+
const svcName = import_node_path50.default.basename(svc.dir);
|
|
9965
10152
|
console.log(
|
|
9966
10153
|
`neat: ${svc.dir} detected as React Native / Expo
|
|
9967
10154
|
The installer doesn't cover this runtime deterministically.
|
|
@@ -9972,7 +10159,7 @@ async function applyInstallersOver(services, project, options = {}) {
|
|
|
9972
10159
|
);
|
|
9973
10160
|
} else if (outcome.outcome === "bun") {
|
|
9974
10161
|
bun++;
|
|
9975
|
-
const svcName =
|
|
10162
|
+
const svcName = import_node_path50.default.basename(svc.dir);
|
|
9976
10163
|
console.log(
|
|
9977
10164
|
`neat: ${svc.dir} detected as Bun
|
|
9978
10165
|
The installer doesn't cover this runtime deterministically.
|
|
@@ -9983,7 +10170,7 @@ async function applyInstallersOver(services, project, options = {}) {
|
|
|
9983
10170
|
);
|
|
9984
10171
|
} else if (outcome.outcome === "deno") {
|
|
9985
10172
|
deno++;
|
|
9986
|
-
const svcName =
|
|
10173
|
+
const svcName = import_node_path50.default.basename(svc.dir);
|
|
9987
10174
|
console.log(
|
|
9988
10175
|
`neat: ${svc.dir} detected as Deno
|
|
9989
10176
|
The installer doesn't cover this runtime deterministically.
|
|
@@ -9994,7 +10181,7 @@ async function applyInstallersOver(services, project, options = {}) {
|
|
|
9994
10181
|
);
|
|
9995
10182
|
} else if (outcome.outcome === "cloudflare-workers") {
|
|
9996
10183
|
cloudflareWorkers++;
|
|
9997
|
-
const svcName =
|
|
10184
|
+
const svcName = import_node_path50.default.basename(svc.dir);
|
|
9998
10185
|
console.log(
|
|
9999
10186
|
`neat: ${svc.dir} detected as Cloudflare Workers
|
|
10000
10187
|
The installer doesn't cover this runtime deterministically.
|
|
@@ -10005,7 +10192,7 @@ async function applyInstallersOver(services, project, options = {}) {
|
|
|
10005
10192
|
);
|
|
10006
10193
|
} else if (outcome.outcome === "electron") {
|
|
10007
10194
|
electron++;
|
|
10008
|
-
const svcName =
|
|
10195
|
+
const svcName = import_node_path50.default.basename(svc.dir);
|
|
10009
10196
|
console.log(
|
|
10010
10197
|
`neat: ${svc.dir} detected as Electron
|
|
10011
10198
|
The installer doesn't cover this runtime deterministically.
|
|
@@ -10086,10 +10273,6 @@ async function fetchDaemonHealth(restPort) {
|
|
|
10086
10273
|
});
|
|
10087
10274
|
});
|
|
10088
10275
|
}
|
|
10089
|
-
async function checkDaemonHealth(restPort) {
|
|
10090
|
-
const body = await fetchDaemonHealth(restPort);
|
|
10091
|
-
return body !== null;
|
|
10092
|
-
}
|
|
10093
10276
|
async function probeProjectHealth(restPort, name) {
|
|
10094
10277
|
return new Promise((resolve) => {
|
|
10095
10278
|
const req = import_node_http.default.get(
|
|
@@ -10165,12 +10348,6 @@ async function isPortFree(port) {
|
|
|
10165
10348
|
server.listen(port, "127.0.0.1");
|
|
10166
10349
|
});
|
|
10167
10350
|
}
|
|
10168
|
-
async function probePortsFree() {
|
|
10169
|
-
for (const port of NEAT_PORTS) {
|
|
10170
|
-
if (!await isPortFree(port)) return { free: false, held: port };
|
|
10171
|
-
}
|
|
10172
|
-
return { free: true };
|
|
10173
|
-
}
|
|
10174
10351
|
function formatPortCollisionMessage(port) {
|
|
10175
10352
|
return [
|
|
10176
10353
|
`neat: port ${port} is in use; the NEAT daemon needs it.`,
|
|
@@ -10178,11 +10355,82 @@ function formatPortCollisionMessage(port) {
|
|
|
10178
10355
|
` \`lsof -i :${port}\` to find the holding process.`
|
|
10179
10356
|
];
|
|
10180
10357
|
}
|
|
10181
|
-
|
|
10182
|
-
|
|
10358
|
+
var PORT_ALLOCATION_ATTEMPTS = 8;
|
|
10359
|
+
var PORT_STRIDE = 1;
|
|
10360
|
+
async function tripleFree(ports) {
|
|
10361
|
+
for (const p of [ports.rest, ports.otlp, ports.web]) {
|
|
10362
|
+
if (!await isPortFree(p)) return false;
|
|
10363
|
+
}
|
|
10364
|
+
return true;
|
|
10365
|
+
}
|
|
10366
|
+
async function allocatePorts() {
|
|
10367
|
+
const [baseRest, baseOtlp, baseWeb] = NEAT_PORTS;
|
|
10368
|
+
for (let i = 0; i < PORT_ALLOCATION_ATTEMPTS; i++) {
|
|
10369
|
+
const candidate = {
|
|
10370
|
+
rest: baseRest + i * PORT_STRIDE,
|
|
10371
|
+
otlp: baseOtlp + i * PORT_STRIDE,
|
|
10372
|
+
web: baseWeb + i * PORT_STRIDE
|
|
10373
|
+
};
|
|
10374
|
+
if (await tripleFree(candidate)) return candidate;
|
|
10375
|
+
}
|
|
10376
|
+
return null;
|
|
10377
|
+
}
|
|
10378
|
+
async function persistedPortsFor(scanPath) {
|
|
10379
|
+
const record = await readDaemonRecord(scanPath);
|
|
10380
|
+
if (!record) return null;
|
|
10381
|
+
return { rest: record.ports.rest, otlp: record.ports.otlp, web: record.ports.web };
|
|
10382
|
+
}
|
|
10383
|
+
async function acquireSpawnLock(scanPath) {
|
|
10384
|
+
const lockPath = import_node_path50.default.join(scanPath, "neat-out", "daemon.spawn.lock");
|
|
10385
|
+
await import_node_fs33.promises.mkdir(import_node_path50.default.dirname(lockPath), { recursive: true });
|
|
10386
|
+
const STALE_LOCK_MS = 6e4;
|
|
10387
|
+
try {
|
|
10388
|
+
const fd = await import_node_fs33.promises.open(lockPath, "wx");
|
|
10389
|
+
await fd.writeFile(`${process.pid}
|
|
10390
|
+
`, "utf8");
|
|
10391
|
+
await fd.close();
|
|
10392
|
+
return async () => {
|
|
10393
|
+
await import_node_fs33.promises.unlink(lockPath).catch(() => {
|
|
10394
|
+
});
|
|
10395
|
+
};
|
|
10396
|
+
} catch (err) {
|
|
10397
|
+
if (err.code !== "EEXIST") return null;
|
|
10398
|
+
try {
|
|
10399
|
+
const stat = await import_node_fs33.promises.stat(lockPath);
|
|
10400
|
+
if (Date.now() - stat.mtimeMs > STALE_LOCK_MS) {
|
|
10401
|
+
await import_node_fs33.promises.unlink(lockPath).catch(() => {
|
|
10402
|
+
});
|
|
10403
|
+
return acquireSpawnLock(scanPath);
|
|
10404
|
+
}
|
|
10405
|
+
} catch {
|
|
10406
|
+
return acquireSpawnLock(scanPath);
|
|
10407
|
+
}
|
|
10408
|
+
return null;
|
|
10409
|
+
}
|
|
10410
|
+
}
|
|
10411
|
+
async function waitForPeerDaemon(restPort, project, timeoutMs) {
|
|
10412
|
+
const deadline = Date.now() + timeoutMs;
|
|
10413
|
+
while (Date.now() < deadline) {
|
|
10414
|
+
if (await healthIsForProject(restPort, project)) return true;
|
|
10415
|
+
await new Promise((r) => setTimeout(r, PROBE_INTERVAL_MS));
|
|
10416
|
+
}
|
|
10417
|
+
return healthIsForProject(restPort, project);
|
|
10418
|
+
}
|
|
10419
|
+
async function healthIsForProject(restPort, project) {
|
|
10420
|
+
const body = await fetchDaemonHealth(restPort);
|
|
10421
|
+
if (body === null) return false;
|
|
10422
|
+
const named = body.project;
|
|
10423
|
+
if (typeof named === "string") return named === project;
|
|
10424
|
+
if (Array.isArray(body.projects)) {
|
|
10425
|
+
return body.projects.some((p) => p.name === project);
|
|
10426
|
+
}
|
|
10427
|
+
return false;
|
|
10428
|
+
}
|
|
10429
|
+
function spawnDaemonDetached(spec) {
|
|
10430
|
+
const here = import_node_path50.default.dirname(new URL(importMetaUrl).pathname);
|
|
10183
10431
|
const candidates = [
|
|
10184
|
-
|
|
10185
|
-
|
|
10432
|
+
import_node_path50.default.join(here, "neatd.cjs"),
|
|
10433
|
+
import_node_path50.default.join(here, "neatd.js")
|
|
10186
10434
|
];
|
|
10187
10435
|
let entry2 = null;
|
|
10188
10436
|
const fsSync = require("fs");
|
|
@@ -10202,6 +10450,13 @@ function spawnDaemonDetached() {
|
|
|
10202
10450
|
if (!hasToken && (!env.HOST || env.HOST.length === 0)) {
|
|
10203
10451
|
env.HOST = "127.0.0.1";
|
|
10204
10452
|
}
|
|
10453
|
+
if (spec) {
|
|
10454
|
+
env.NEAT_PROJECT = spec.project;
|
|
10455
|
+
env.NEAT_PROJECT_PATH = spec.projectPath;
|
|
10456
|
+
env.PORT = String(spec.ports.rest);
|
|
10457
|
+
env.OTEL_PORT = String(spec.ports.otlp);
|
|
10458
|
+
env.NEAT_WEB_PORT = String(spec.ports.web);
|
|
10459
|
+
}
|
|
10205
10460
|
const child = (0, import_node_child_process3.spawn)(process.execPath, [entry2, "start"], {
|
|
10206
10461
|
detached: true,
|
|
10207
10462
|
// stderr inherits the orchestrator's fd so the daemon's
|
|
@@ -10240,7 +10495,7 @@ async function runOrchestrator(opts) {
|
|
|
10240
10495
|
browser: "skipped"
|
|
10241
10496
|
}
|
|
10242
10497
|
};
|
|
10243
|
-
const stat = await
|
|
10498
|
+
const stat = await import_node_fs33.promises.stat(opts.scanPath).catch(() => null);
|
|
10244
10499
|
if (!stat || !stat.isDirectory()) {
|
|
10245
10500
|
console.error(`neat: ${opts.scanPath} is not a directory`);
|
|
10246
10501
|
result.exitCode = 2;
|
|
@@ -10321,48 +10576,75 @@ async function runOrchestrator(opts) {
|
|
|
10321
10576
|
result.exitCode = 1;
|
|
10322
10577
|
}
|
|
10323
10578
|
}
|
|
10324
|
-
const restPort = Number(process.env.PORT ?? 8080);
|
|
10325
10579
|
const timeoutMs = opts.daemonReadyTimeoutMs ?? DEFAULT_DAEMON_READY_TIMEOUT_MS;
|
|
10326
|
-
|
|
10580
|
+
const persistedPorts = await persistedPortsFor(opts.scanPath);
|
|
10581
|
+
let allocated = null;
|
|
10582
|
+
if (persistedPorts && await healthIsForProject(persistedPorts.rest, currentProjectName)) {
|
|
10327
10583
|
result.steps.daemon = "already-running";
|
|
10584
|
+
allocated = persistedPorts;
|
|
10328
10585
|
} else {
|
|
10329
|
-
|
|
10330
|
-
|
|
10331
|
-
|
|
10586
|
+
if (persistedPorts && await isPortFree(persistedPorts.rest) && await tripleFree(persistedPorts)) {
|
|
10587
|
+
allocated = persistedPorts;
|
|
10588
|
+
} else {
|
|
10589
|
+
allocated = await allocatePorts();
|
|
10590
|
+
}
|
|
10591
|
+
if (!allocated) {
|
|
10592
|
+
for (const line of formatPortCollisionMessage(NEAT_PORTS[0])) {
|
|
10332
10593
|
console.error(line);
|
|
10333
10594
|
}
|
|
10334
10595
|
result.exitCode = 3;
|
|
10335
10596
|
return result;
|
|
10336
10597
|
}
|
|
10337
|
-
|
|
10338
|
-
|
|
10339
|
-
|
|
10340
|
-
|
|
10341
|
-
|
|
10342
|
-
|
|
10343
|
-
|
|
10344
|
-
|
|
10345
|
-
|
|
10346
|
-
if (!ready.ready) {
|
|
10347
|
-
console.error(`neat: daemon did not become ready within ${timeoutMs}ms`);
|
|
10348
|
-
if (ready.stillBootstrapping.length > 0) {
|
|
10349
|
-
console.error(
|
|
10350
|
-
`neat: still bootstrapping: ${ready.stillBootstrapping.join(", ")}`
|
|
10351
|
-
);
|
|
10598
|
+
const release = await acquireSpawnLock(opts.scanPath);
|
|
10599
|
+
if (!release) {
|
|
10600
|
+
const reused = await waitForPeerDaemon(allocated.rest, currentProjectName, timeoutMs);
|
|
10601
|
+
if (reused) {
|
|
10602
|
+
result.steps.daemon = "already-running";
|
|
10603
|
+
} else {
|
|
10604
|
+
console.error("neat: another `neat` is spawning this project but its daemon did not come up in time");
|
|
10605
|
+
result.exitCode = 1;
|
|
10606
|
+
return result;
|
|
10352
10607
|
}
|
|
10353
|
-
|
|
10354
|
-
|
|
10608
|
+
} else {
|
|
10609
|
+
try {
|
|
10610
|
+
if (await healthIsForProject(allocated.rest, currentProjectName)) {
|
|
10611
|
+
result.steps.daemon = "already-running";
|
|
10612
|
+
} else {
|
|
10613
|
+
spawnDaemonDetached({
|
|
10614
|
+
project: currentProjectName,
|
|
10615
|
+
projectPath: opts.scanPath,
|
|
10616
|
+
ports: allocated
|
|
10617
|
+
});
|
|
10618
|
+
const ready = await waitForDaemonReady(allocated.rest, timeoutMs);
|
|
10619
|
+
result.steps.daemon = ready.ready ? "spawned" : "timed-out";
|
|
10620
|
+
if (!ready.ready) {
|
|
10621
|
+
console.error(`neat: daemon did not become ready within ${timeoutMs}ms`);
|
|
10622
|
+
if (ready.stillBootstrapping.length > 0) {
|
|
10623
|
+
console.error(`neat: still bootstrapping: ${ready.stillBootstrapping.join(", ")}`);
|
|
10624
|
+
}
|
|
10625
|
+
if (ready.brokenProjects.length > 0) {
|
|
10626
|
+
console.error(`neat: broken projects: ${ready.brokenProjects.join(", ")}`);
|
|
10627
|
+
}
|
|
10628
|
+
result.exitCode = 1;
|
|
10629
|
+
return result;
|
|
10630
|
+
}
|
|
10631
|
+
if (ready.brokenProjects.length > 0) {
|
|
10632
|
+
console.warn(
|
|
10633
|
+
`neat: ${ready.brokenProjects.length} project(s) reported broken: ${ready.brokenProjects.join(", ")}`
|
|
10634
|
+
);
|
|
10635
|
+
}
|
|
10636
|
+
}
|
|
10637
|
+
} catch (err) {
|
|
10638
|
+
console.error(`neat: daemon spawn failed \u2014 ${err.message}`);
|
|
10639
|
+
result.exitCode = 1;
|
|
10640
|
+
return result;
|
|
10641
|
+
} finally {
|
|
10642
|
+
await release();
|
|
10355
10643
|
}
|
|
10356
|
-
result.exitCode = 1;
|
|
10357
|
-
return result;
|
|
10358
|
-
}
|
|
10359
|
-
if (ready.brokenProjects.length > 0) {
|
|
10360
|
-
console.warn(
|
|
10361
|
-
`neat: ${ready.brokenProjects.length} project(s) reported broken: ${ready.brokenProjects.join(", ")}`
|
|
10362
|
-
);
|
|
10363
10644
|
}
|
|
10364
10645
|
}
|
|
10365
|
-
const
|
|
10646
|
+
const webPort = allocated?.web ?? NEAT_PORTS[2];
|
|
10647
|
+
const dashboardUrl = opts.dashboardUrl ?? `http://localhost:${webPort}`;
|
|
10366
10648
|
if (opts.noOpen || !process.stdout.isTTY) {
|
|
10367
10649
|
result.steps.browser = "skipped";
|
|
10368
10650
|
} else {
|
|
@@ -10397,7 +10679,7 @@ function printSummary(result, graph, dashboardUrl) {
|
|
|
10397
10679
|
|
|
10398
10680
|
// src/cli-verbs.ts
|
|
10399
10681
|
init_cjs_shims();
|
|
10400
|
-
var
|
|
10682
|
+
var import_node_path51 = __toESM(require("path"), 1);
|
|
10401
10683
|
|
|
10402
10684
|
// src/cli-client.ts
|
|
10403
10685
|
init_cjs_shims();
|
|
@@ -10426,10 +10708,10 @@ function createHttpClient(baseUrl, bearerToken) {
|
|
|
10426
10708
|
const root = baseUrl.replace(/\/$/, "");
|
|
10427
10709
|
const authHeader = bearerToken && bearerToken.length > 0 ? { authorization: `Bearer ${bearerToken}` } : {};
|
|
10428
10710
|
return {
|
|
10429
|
-
async get(
|
|
10711
|
+
async get(path53) {
|
|
10430
10712
|
let res;
|
|
10431
10713
|
try {
|
|
10432
|
-
res = await fetch(`${root}${
|
|
10714
|
+
res = await fetch(`${root}${path53}`, {
|
|
10433
10715
|
headers: { ...authHeader }
|
|
10434
10716
|
});
|
|
10435
10717
|
} catch (err) {
|
|
@@ -10441,16 +10723,16 @@ function createHttpClient(baseUrl, bearerToken) {
|
|
|
10441
10723
|
const body = await res.text().catch(() => "");
|
|
10442
10724
|
throw new HttpError(
|
|
10443
10725
|
res.status,
|
|
10444
|
-
`${res.status} ${res.statusText} on GET ${
|
|
10726
|
+
`${res.status} ${res.statusText} on GET ${path53}: ${body}`,
|
|
10445
10727
|
body
|
|
10446
10728
|
);
|
|
10447
10729
|
}
|
|
10448
10730
|
return await res.json();
|
|
10449
10731
|
},
|
|
10450
|
-
async post(
|
|
10732
|
+
async post(path53, body) {
|
|
10451
10733
|
let res;
|
|
10452
10734
|
try {
|
|
10453
|
-
res = await fetch(`${root}${
|
|
10735
|
+
res = await fetch(`${root}${path53}`, {
|
|
10454
10736
|
method: "POST",
|
|
10455
10737
|
headers: { "content-type": "application/json", ...authHeader },
|
|
10456
10738
|
body: JSON.stringify(body)
|
|
@@ -10464,7 +10746,7 @@ function createHttpClient(baseUrl, bearerToken) {
|
|
|
10464
10746
|
const text = await res.text().catch(() => "");
|
|
10465
10747
|
throw new HttpError(
|
|
10466
10748
|
res.status,
|
|
10467
|
-
`${res.status} ${res.statusText} on POST ${
|
|
10749
|
+
`${res.status} ${res.statusText} on POST ${path53}: ${text}`,
|
|
10468
10750
|
text
|
|
10469
10751
|
);
|
|
10470
10752
|
}
|
|
@@ -10478,12 +10760,12 @@ function projectPath(project, suffix) {
|
|
|
10478
10760
|
}
|
|
10479
10761
|
async function runRootCause(client, input) {
|
|
10480
10762
|
const qs = input.errorId ? `?errorId=${encodeURIComponent(input.errorId)}` : "";
|
|
10481
|
-
const
|
|
10763
|
+
const path53 = projectPath(
|
|
10482
10764
|
input.project,
|
|
10483
10765
|
`/graph/root-cause/${encodeURIComponent(input.errorNode)}${qs}`
|
|
10484
10766
|
);
|
|
10485
10767
|
try {
|
|
10486
|
-
const result = await client.get(
|
|
10768
|
+
const result = await client.get(path53);
|
|
10487
10769
|
const arrowPath = result.traversalPath.join(" \u2190 ");
|
|
10488
10770
|
const provenances = result.edgeProvenances.length ? result.edgeProvenances.join(", ") : "(direct, no edges traversed)";
|
|
10489
10771
|
const summary = `Root cause for ${input.errorNode} is ${result.rootCauseNode}. ` + result.rootCauseReason + (result.fixRecommendation ? ` Recommended fix: ${result.fixRecommendation}.` : "");
|
|
@@ -10509,12 +10791,12 @@ async function runRootCause(client, input) {
|
|
|
10509
10791
|
}
|
|
10510
10792
|
async function runBlastRadius(client, input) {
|
|
10511
10793
|
const qs = input.depth !== void 0 ? `?depth=${input.depth}` : "";
|
|
10512
|
-
const
|
|
10794
|
+
const path53 = projectPath(
|
|
10513
10795
|
input.project,
|
|
10514
10796
|
`/graph/blast-radius/${encodeURIComponent(input.nodeId)}${qs}`
|
|
10515
10797
|
);
|
|
10516
10798
|
try {
|
|
10517
|
-
const result = await client.get(
|
|
10799
|
+
const result = await client.get(path53);
|
|
10518
10800
|
if (result.totalAffected === 0) {
|
|
10519
10801
|
return {
|
|
10520
10802
|
summary: `${result.origin} has no downstream dependencies. Nothing else would break if it failed.`
|
|
@@ -10548,12 +10830,12 @@ function formatBlastEntry(n) {
|
|
|
10548
10830
|
}
|
|
10549
10831
|
async function runDependencies(client, input) {
|
|
10550
10832
|
const depth = input.depth ?? 3;
|
|
10551
|
-
const
|
|
10833
|
+
const path53 = projectPath(
|
|
10552
10834
|
input.project,
|
|
10553
10835
|
`/graph/dependencies/${encodeURIComponent(input.nodeId)}?depth=${depth}`
|
|
10554
10836
|
);
|
|
10555
10837
|
try {
|
|
10556
|
-
const result = await client.get(
|
|
10838
|
+
const result = await client.get(path53);
|
|
10557
10839
|
if (result.total === 0) {
|
|
10558
10840
|
return {
|
|
10559
10841
|
summary: depth === 1 ? `${input.nodeId} has no direct dependencies in the graph.` : `${input.nodeId} has no dependencies (BFS to depth ${depth}).`
|
|
@@ -10634,9 +10916,9 @@ function formatDuration(ms) {
|
|
|
10634
10916
|
return `${Math.round(h / 24)}d`;
|
|
10635
10917
|
}
|
|
10636
10918
|
async function runIncidents(client, input) {
|
|
10637
|
-
const
|
|
10919
|
+
const path53 = input.nodeId ? projectPath(input.project, `/incidents/${encodeURIComponent(input.nodeId)}`) : projectPath(input.project, "/incidents");
|
|
10638
10920
|
try {
|
|
10639
|
-
const body = await client.get(
|
|
10921
|
+
const body = await client.get(path53);
|
|
10640
10922
|
const events = body.events;
|
|
10641
10923
|
if (events.length === 0) {
|
|
10642
10924
|
return {
|
|
@@ -10926,13 +11208,13 @@ async function resolveProjectEntry(opts) {
|
|
|
10926
11208
|
const cwd = opts.cwd ?? process.cwd();
|
|
10927
11209
|
const resolvedCwd = await normalizeProjectPath(cwd);
|
|
10928
11210
|
for (const entry2 of entries) {
|
|
10929
|
-
if (resolvedCwd === entry2.path || resolvedCwd.startsWith(`${entry2.path}${
|
|
11211
|
+
if (resolvedCwd === entry2.path || resolvedCwd.startsWith(`${entry2.path}${import_node_path51.default.sep}`)) {
|
|
10930
11212
|
return entry2;
|
|
10931
11213
|
}
|
|
10932
11214
|
}
|
|
10933
11215
|
return null;
|
|
10934
11216
|
}
|
|
10935
|
-
async function
|
|
11217
|
+
async function checkDaemonHealth(baseUrl) {
|
|
10936
11218
|
try {
|
|
10937
11219
|
const res = await fetch(`${baseUrl.replace(/\/$/, "")}/health`, {
|
|
10938
11220
|
signal: AbortSignal.timeout(1500)
|
|
@@ -11033,7 +11315,7 @@ async function runSync(opts) {
|
|
|
11033
11315
|
}
|
|
11034
11316
|
} else {
|
|
11035
11317
|
const daemonUrl = opts.daemonUrl ?? process.env.NEAT_API_URL ?? "http://localhost:8080";
|
|
11036
|
-
const healthy = await
|
|
11318
|
+
const healthy = await checkDaemonHealth(daemonUrl);
|
|
11037
11319
|
if (healthy) {
|
|
11038
11320
|
try {
|
|
11039
11321
|
await pushSnapshotToRemote({
|
|
@@ -11107,11 +11389,15 @@ function usage() {
|
|
|
11107
11389
|
console.log(" watch <path> Start neat-core, watch <path>, re-extract on changes.");
|
|
11108
11390
|
console.log(" PORT (default 8080), OTEL_PORT (4318), HOST (0.0.0.0)");
|
|
11109
11391
|
console.log(" control listeners. NEAT_OTLP_GRPC=true also opens 4317.");
|
|
11110
|
-
console.log(" list
|
|
11111
|
-
console.log("
|
|
11112
|
-
console.log("
|
|
11392
|
+
console.log(" list Report the daemons running on this machine (alias: ps).");
|
|
11393
|
+
console.log(" Reads ~/.neat/daemons/ and folds in any registered");
|
|
11394
|
+
console.log(" project no daemon has self-described yet.");
|
|
11395
|
+
console.log(" ps Alias of list.");
|
|
11396
|
+
console.log(" pause <name> Stop a project's daemon until it is started again.");
|
|
11397
|
+
console.log(" resume <name> Bring a paused project back; for a stopped daemon, re-run");
|
|
11398
|
+
console.log(" `neat <path>` to start it.");
|
|
11113
11399
|
console.log(" uninstall <name>");
|
|
11114
|
-
console.log("
|
|
11400
|
+
console.log(" Stop a project's daemon and retire it. Does not touch");
|
|
11115
11401
|
console.log(" neat-out/, policy.json, or any user file.");
|
|
11116
11402
|
console.log(" prune Drop registry entries whose path is gone from disk.");
|
|
11117
11403
|
console.log(" Flags: --json emit the removed list as JSON");
|
|
@@ -11308,6 +11594,14 @@ function printVersion() {
|
|
|
11308
11594
|
process.stdout.write(`${readPackageVersion()}
|
|
11309
11595
|
`);
|
|
11310
11596
|
}
|
|
11597
|
+
function formatMachineProjectRow(r) {
|
|
11598
|
+
if (r.ports) {
|
|
11599
|
+
const where = r.pid !== void 0 ? ` pid=${r.pid}` : "";
|
|
11600
|
+
return `${r.project} ${r.state} rest=${r.ports.rest} otlp=${r.ports.otlp} web=${r.ports.web} ${r.projectPath}${where}`;
|
|
11601
|
+
}
|
|
11602
|
+
const status2 = r.registryStatus ? ` (${r.registryStatus})` : "";
|
|
11603
|
+
return `${r.project} ${r.state}${status2} ${r.projectPath}`;
|
|
11604
|
+
}
|
|
11311
11605
|
function printDiscoveryReport(opts, services) {
|
|
11312
11606
|
const languages = [...new Set(services.map((s) => s.node.language))].sort();
|
|
11313
11607
|
const mode = opts.dryRun ? "dry-run" : opts.apply ? "apply" : "patch-only";
|
|
@@ -11346,7 +11640,7 @@ async function buildPatchSections(services, project) {
|
|
|
11346
11640
|
}
|
|
11347
11641
|
async function runInit(opts) {
|
|
11348
11642
|
const written = [];
|
|
11349
|
-
const stat = await
|
|
11643
|
+
const stat = await import_node_fs34.promises.stat(opts.scanPath).catch(() => null);
|
|
11350
11644
|
if (!stat || !stat.isDirectory()) {
|
|
11351
11645
|
console.error(`neat init: ${opts.scanPath} is not a directory`);
|
|
11352
11646
|
return { exitCode: 2, writtenFiles: written };
|
|
@@ -11355,13 +11649,13 @@ async function runInit(opts) {
|
|
|
11355
11649
|
printDiscoveryReport(opts, services);
|
|
11356
11650
|
const sections = opts.noInstall ? [] : await buildPatchSections(services, opts.project);
|
|
11357
11651
|
const patch = renderPatch(sections);
|
|
11358
|
-
const patchPath =
|
|
11652
|
+
const patchPath = import_node_path52.default.join(opts.scanPath, "neat.patch");
|
|
11359
11653
|
if (opts.dryRun) {
|
|
11360
|
-
await
|
|
11654
|
+
await import_node_fs34.promises.writeFile(patchPath, patch, "utf8");
|
|
11361
11655
|
written.push(patchPath);
|
|
11362
11656
|
console.log(`dry-run: patch written to ${patchPath}`);
|
|
11363
|
-
const gitignorePath =
|
|
11364
|
-
const gitignoreExists = await
|
|
11657
|
+
const gitignorePath = import_node_path52.default.join(opts.scanPath, ".gitignore");
|
|
11658
|
+
const gitignoreExists = await import_node_fs34.promises.stat(gitignorePath).then(() => true).catch(() => false);
|
|
11365
11659
|
const verb = gitignoreExists ? "append" : "create";
|
|
11366
11660
|
console.log(`dry-run: would ${verb} ${gitignorePath} (add neat-out/)`);
|
|
11367
11661
|
console.log("rerun without --dry-run to register and snapshot.");
|
|
@@ -11372,9 +11666,9 @@ async function runInit(opts) {
|
|
|
11372
11666
|
const graph = getGraph(graphKey);
|
|
11373
11667
|
const projectPaths = pathsForProject(
|
|
11374
11668
|
graphKey,
|
|
11375
|
-
|
|
11669
|
+
import_node_path52.default.join(opts.scanPath, "neat-out")
|
|
11376
11670
|
);
|
|
11377
|
-
const errorsPath =
|
|
11671
|
+
const errorsPath = import_node_path52.default.join(import_node_path52.default.dirname(opts.outPath), import_node_path52.default.basename(projectPaths.errorsPath));
|
|
11378
11672
|
const result = await extractFromDirectory(graph, opts.scanPath, { errorsPath });
|
|
11379
11673
|
await saveGraphToDisk(graph, opts.outPath);
|
|
11380
11674
|
written.push(opts.outPath);
|
|
@@ -11453,7 +11747,7 @@ async function runInit(opts) {
|
|
|
11453
11747
|
console.log("Run `npm install` (or your language equivalent) to refresh lockfiles.");
|
|
11454
11748
|
}
|
|
11455
11749
|
} else {
|
|
11456
|
-
await
|
|
11750
|
+
await import_node_fs34.promises.writeFile(patchPath, patch, "utf8");
|
|
11457
11751
|
written.push(patchPath);
|
|
11458
11752
|
}
|
|
11459
11753
|
}
|
|
@@ -11493,9 +11787,9 @@ var CLAUDE_SKILL_CONFIG = {
|
|
|
11493
11787
|
};
|
|
11494
11788
|
function claudeConfigPath() {
|
|
11495
11789
|
const override = process.env.NEAT_CLAUDE_CONFIG;
|
|
11496
|
-
if (override && override.length > 0) return
|
|
11790
|
+
if (override && override.length > 0) return import_node_path52.default.resolve(override);
|
|
11497
11791
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
11498
|
-
return
|
|
11792
|
+
return import_node_path52.default.join(home, ".claude.json");
|
|
11499
11793
|
}
|
|
11500
11794
|
async function runSkill(opts) {
|
|
11501
11795
|
const snippet2 = JSON.stringify(CLAUDE_SKILL_CONFIG, null, 2) + "\n";
|
|
@@ -11507,7 +11801,7 @@ async function runSkill(opts) {
|
|
|
11507
11801
|
const target = claudeConfigPath();
|
|
11508
11802
|
let existing = {};
|
|
11509
11803
|
try {
|
|
11510
|
-
existing = JSON.parse(await
|
|
11804
|
+
existing = JSON.parse(await import_node_fs34.promises.readFile(target, "utf8"));
|
|
11511
11805
|
} catch (err) {
|
|
11512
11806
|
if (err.code !== "ENOENT") {
|
|
11513
11807
|
console.error(`neat skill: failed to read ${target} \u2014 ${err.message}`);
|
|
@@ -11519,8 +11813,8 @@ async function runSkill(opts) {
|
|
|
11519
11813
|
...existing,
|
|
11520
11814
|
mcpServers: { ...mcp, neat: CLAUDE_SKILL_CONFIG.mcpServers.neat }
|
|
11521
11815
|
};
|
|
11522
|
-
await
|
|
11523
|
-
await
|
|
11816
|
+
await import_node_fs34.promises.mkdir(import_node_path52.default.dirname(target), { recursive: true });
|
|
11817
|
+
await import_node_fs34.promises.writeFile(target, JSON.stringify(merged, null, 2) + "\n", "utf8");
|
|
11524
11818
|
console.log(`neat skill: wrote mcpServers.neat to ${target}`);
|
|
11525
11819
|
console.log("restart Claude Code to pick up the new MCP server.");
|
|
11526
11820
|
return { exitCode: 0 };
|
|
@@ -11569,12 +11863,12 @@ async function main() {
|
|
|
11569
11863
|
console.error("neat init: --apply and --dry-run are mutually exclusive");
|
|
11570
11864
|
process.exit(2);
|
|
11571
11865
|
}
|
|
11572
|
-
const scanPath =
|
|
11866
|
+
const scanPath = import_node_path52.default.resolve(target);
|
|
11573
11867
|
const projectExplicit = parsed.project !== null;
|
|
11574
|
-
const projectName = projectExplicit ? project :
|
|
11868
|
+
const projectName = projectExplicit ? project : import_node_path52.default.basename(scanPath);
|
|
11575
11869
|
const projectKey = projectExplicit ? project : DEFAULT_PROJECT;
|
|
11576
|
-
const fallback = pathsForProject(projectKey,
|
|
11577
|
-
const outPath =
|
|
11870
|
+
const fallback = pathsForProject(projectKey, import_node_path52.default.join(scanPath, "neat-out")).snapshotPath;
|
|
11871
|
+
const outPath = import_node_path52.default.resolve(process.env.NEAT_OUT_PATH ?? fallback);
|
|
11578
11872
|
const result = await runInit({
|
|
11579
11873
|
scanPath,
|
|
11580
11874
|
outPath,
|
|
@@ -11595,21 +11889,21 @@ async function main() {
|
|
|
11595
11889
|
usage();
|
|
11596
11890
|
process.exit(2);
|
|
11597
11891
|
}
|
|
11598
|
-
const scanPath =
|
|
11599
|
-
const stat = await
|
|
11892
|
+
const scanPath = import_node_path52.default.resolve(target);
|
|
11893
|
+
const stat = await import_node_fs34.promises.stat(scanPath).catch(() => null);
|
|
11600
11894
|
if (!stat || !stat.isDirectory()) {
|
|
11601
11895
|
console.error(`neat watch: ${scanPath} is not a directory`);
|
|
11602
11896
|
process.exit(2);
|
|
11603
11897
|
}
|
|
11604
|
-
const projectPaths = pathsForProject(project,
|
|
11605
|
-
const outPath =
|
|
11606
|
-
const errorsPath =
|
|
11607
|
-
process.env.NEAT_ERRORS_PATH ??
|
|
11898
|
+
const projectPaths = pathsForProject(project, import_node_path52.default.join(scanPath, "neat-out"));
|
|
11899
|
+
const outPath = import_node_path52.default.resolve(process.env.NEAT_OUT_PATH ?? projectPaths.snapshotPath);
|
|
11900
|
+
const errorsPath = import_node_path52.default.resolve(
|
|
11901
|
+
process.env.NEAT_ERRORS_PATH ?? import_node_path52.default.join(import_node_path52.default.dirname(outPath), import_node_path52.default.basename(projectPaths.errorsPath))
|
|
11608
11902
|
);
|
|
11609
|
-
const staleEventsPath =
|
|
11610
|
-
process.env.NEAT_STALE_EVENTS_PATH ??
|
|
11903
|
+
const staleEventsPath = import_node_path52.default.resolve(
|
|
11904
|
+
process.env.NEAT_STALE_EVENTS_PATH ?? import_node_path52.default.join(import_node_path52.default.dirname(outPath), import_node_path52.default.basename(projectPaths.staleEventsPath))
|
|
11611
11905
|
);
|
|
11612
|
-
const embeddingsCachePath = process.env.NEAT_EMBEDDINGS_CACHE_PATH ?
|
|
11906
|
+
const embeddingsCachePath = process.env.NEAT_EMBEDDINGS_CACHE_PATH ? import_node_path52.default.resolve(process.env.NEAT_EMBEDDINGS_CACHE_PATH) : void 0;
|
|
11613
11907
|
const handle = await startWatch(getGraph(project), {
|
|
11614
11908
|
scanPath,
|
|
11615
11909
|
outPath,
|
|
@@ -11636,16 +11930,14 @@ async function main() {
|
|
|
11636
11930
|
process.on("SIGINT", shutdown);
|
|
11637
11931
|
return;
|
|
11638
11932
|
}
|
|
11639
|
-
if (cmd === "list") {
|
|
11640
|
-
const
|
|
11641
|
-
if (
|
|
11642
|
-
console.log("no projects registered. run `neat init <path>` to register one.");
|
|
11933
|
+
if (cmd === "list" || cmd === "ps") {
|
|
11934
|
+
const rows = await listMachineProjects();
|
|
11935
|
+
if (rows.length === 0) {
|
|
11936
|
+
console.log("no daemons running and no projects registered. run `neat init <path>` to register one.");
|
|
11643
11937
|
return;
|
|
11644
11938
|
}
|
|
11645
|
-
for (const
|
|
11646
|
-
|
|
11647
|
-
const langs = p.languages.length > 0 ? p.languages.join(",") : "-";
|
|
11648
|
-
console.log(`${p.name} ${p.status} ${langs} ${p.path} last-seen=${seen}`);
|
|
11939
|
+
for (const r of rows) {
|
|
11940
|
+
console.log(formatMachineProjectRow(r));
|
|
11649
11941
|
}
|
|
11650
11942
|
return;
|
|
11651
11943
|
}
|
|
@@ -11656,6 +11948,15 @@ async function main() {
|
|
|
11656
11948
|
usage();
|
|
11657
11949
|
process.exit(2);
|
|
11658
11950
|
}
|
|
11951
|
+
const daemon = await findDaemonByProject(name);
|
|
11952
|
+
if (daemon) {
|
|
11953
|
+
if (daemon.live && signalDaemonStop(daemon.record.pid)) {
|
|
11954
|
+
console.log(`paused: ${name} (${daemon.record.projectPath}) \u2014 stopped daemon pid ${daemon.record.pid}`);
|
|
11955
|
+
} else {
|
|
11956
|
+
console.log(`paused: ${name} (${daemon.record.projectPath}) \u2014 daemon was not running`);
|
|
11957
|
+
}
|
|
11958
|
+
return;
|
|
11959
|
+
}
|
|
11659
11960
|
try {
|
|
11660
11961
|
const entry2 = await setStatus(name, "paused");
|
|
11661
11962
|
console.log(`paused: ${entry2.name} (${entry2.path})`);
|
|
@@ -11672,6 +11973,15 @@ async function main() {
|
|
|
11672
11973
|
usage();
|
|
11673
11974
|
process.exit(2);
|
|
11674
11975
|
}
|
|
11976
|
+
const daemon = await findDaemonByProject(name);
|
|
11977
|
+
if (daemon) {
|
|
11978
|
+
if (daemon.live) {
|
|
11979
|
+
console.log(`resume: ${name} (${daemon.record.projectPath}) \u2014 daemon already running`);
|
|
11980
|
+
} else {
|
|
11981
|
+
console.log(`resume: ${name} (${daemon.record.projectPath}) \u2014 run \`neat ${daemon.record.projectPath}\` to start its daemon again`);
|
|
11982
|
+
}
|
|
11983
|
+
return;
|
|
11984
|
+
}
|
|
11675
11985
|
try {
|
|
11676
11986
|
const entry2 = await setStatus(name, "active");
|
|
11677
11987
|
console.log(`resumed: ${entry2.name} (${entry2.path})`);
|
|
@@ -11693,12 +12003,20 @@ async function main() {
|
|
|
11693
12003
|
usage();
|
|
11694
12004
|
process.exit(2);
|
|
11695
12005
|
}
|
|
12006
|
+
const daemon = await findDaemonByProject(name);
|
|
11696
12007
|
const removed = await removeProject(name);
|
|
11697
|
-
if (!removed) {
|
|
12008
|
+
if (!daemon && !removed) {
|
|
11698
12009
|
console.error(`neat uninstall: no project named "${name}"`);
|
|
11699
12010
|
process.exit(1);
|
|
11700
12011
|
}
|
|
11701
|
-
|
|
12012
|
+
const projectPath2 = daemon?.record.projectPath ?? removed?.path ?? "(unknown path)";
|
|
12013
|
+
if (daemon) {
|
|
12014
|
+
if (daemon.live && signalDaemonStop(daemon.record.pid)) {
|
|
12015
|
+
console.log(`uninstall: ${name} \u2014 stopped daemon pid ${daemon.record.pid}`);
|
|
12016
|
+
}
|
|
12017
|
+
await removeDaemonRecord(daemon.source);
|
|
12018
|
+
}
|
|
12019
|
+
console.log(`unregistered: ${name} (${projectPath2})`);
|
|
11702
12020
|
console.log("note: neat-out/, policy.json, and other files at the project path were left in place.");
|
|
11703
12021
|
return;
|
|
11704
12022
|
}
|
|
@@ -11768,11 +12086,11 @@ async function main() {
|
|
|
11768
12086
|
process.exit(1);
|
|
11769
12087
|
}
|
|
11770
12088
|
async function tryOrchestrator(cmd, parsed) {
|
|
11771
|
-
const scanPath =
|
|
11772
|
-
const stat = await
|
|
12089
|
+
const scanPath = import_node_path52.default.resolve(cmd);
|
|
12090
|
+
const stat = await import_node_fs34.promises.stat(scanPath).catch(() => null);
|
|
11773
12091
|
if (!stat || !stat.isDirectory()) return null;
|
|
11774
12092
|
const projectExplicit = parsed.project !== null;
|
|
11775
|
-
const projectName = projectExplicit ? parsed.project :
|
|
12093
|
+
const projectName = projectExplicit ? parsed.project : import_node_path52.default.basename(scanPath);
|
|
11776
12094
|
const result = await runOrchestrator({
|
|
11777
12095
|
scanPath,
|
|
11778
12096
|
project: projectName,
|