@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/index.cjs
CHANGED
|
@@ -473,8 +473,10 @@ async function buildOtelReceiver(opts) {
|
|
|
473
473
|
for (const s of spans) projectQueue.push({ project, span: s });
|
|
474
474
|
projectDrainPromise = projectDrainPromise.then(() => drainProject());
|
|
475
475
|
};
|
|
476
|
+
const offersProjectRouting = opts.onProjectSpan !== void 0;
|
|
476
477
|
const legacyEndpointWarned = /* @__PURE__ */ new Set();
|
|
477
478
|
function warnLegacyEndpoint(serviceName) {
|
|
479
|
+
if (!offersProjectRouting) return;
|
|
478
480
|
if (legacyEndpointWarned.has(serviceName)) return;
|
|
479
481
|
legacyEndpointWarned.add(serviceName);
|
|
480
482
|
console.warn(
|
|
@@ -7029,6 +7031,7 @@ init_otel_grpc();
|
|
|
7029
7031
|
init_cjs_shims();
|
|
7030
7032
|
var import_node_fs25 = require("fs");
|
|
7031
7033
|
var import_node_path42 = __toESM(require("path"), 1);
|
|
7034
|
+
var import_node_module = require("module");
|
|
7032
7035
|
init_otel();
|
|
7033
7036
|
init_auth();
|
|
7034
7037
|
|
|
@@ -7054,6 +7057,59 @@ function unroutedErrorsPath(neatHome2) {
|
|
|
7054
7057
|
}
|
|
7055
7058
|
|
|
7056
7059
|
// src/daemon.ts
|
|
7060
|
+
function daemonJsonPath(scanPath) {
|
|
7061
|
+
return import_node_path42.default.join(scanPath, "neat-out", "daemon.json");
|
|
7062
|
+
}
|
|
7063
|
+
function daemonsDiscoveryDir(home) {
|
|
7064
|
+
const base = home && home.length > 0 ? home : neatHomeFromEnv();
|
|
7065
|
+
return import_node_path42.default.join(base, "daemons");
|
|
7066
|
+
}
|
|
7067
|
+
function daemonDiscoveryPath(project, home) {
|
|
7068
|
+
return import_node_path42.default.join(daemonsDiscoveryDir(home), `${sanitizeDiscoveryName(project)}.json`);
|
|
7069
|
+
}
|
|
7070
|
+
function sanitizeDiscoveryName(project) {
|
|
7071
|
+
return project.replace(/[^A-Za-z0-9._-]/g, "_");
|
|
7072
|
+
}
|
|
7073
|
+
function neatHomeFromEnv() {
|
|
7074
|
+
const env = process.env.NEAT_HOME;
|
|
7075
|
+
if (env && env.length > 0) return import_node_path42.default.resolve(env);
|
|
7076
|
+
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
7077
|
+
return import_node_path42.default.join(home, ".neat");
|
|
7078
|
+
}
|
|
7079
|
+
function resolveNeatVersion() {
|
|
7080
|
+
if (process.env.NEAT_LOCAL_VERSION && process.env.NEAT_LOCAL_VERSION.length > 0) {
|
|
7081
|
+
return process.env.NEAT_LOCAL_VERSION;
|
|
7082
|
+
}
|
|
7083
|
+
try {
|
|
7084
|
+
const req = (0, import_node_module.createRequire)(importMetaUrl);
|
|
7085
|
+
const pkg = req("../package.json");
|
|
7086
|
+
return typeof pkg.version === "string" ? pkg.version : "0.0.0";
|
|
7087
|
+
} catch {
|
|
7088
|
+
return "0.0.0";
|
|
7089
|
+
}
|
|
7090
|
+
}
|
|
7091
|
+
async function writeDaemonRecord(record, home) {
|
|
7092
|
+
const body = JSON.stringify(record, null, 2) + "\n";
|
|
7093
|
+
await writeAtomically(daemonJsonPath(record.projectPath), body);
|
|
7094
|
+
try {
|
|
7095
|
+
await writeAtomically(daemonDiscoveryPath(record.project, home), body);
|
|
7096
|
+
} catch (err) {
|
|
7097
|
+
console.warn(
|
|
7098
|
+
`neatd: could not write discovery copy for "${record.project}" \u2014 ${err.message}`
|
|
7099
|
+
);
|
|
7100
|
+
}
|
|
7101
|
+
}
|
|
7102
|
+
async function clearDaemonRecord(record, home) {
|
|
7103
|
+
try {
|
|
7104
|
+
const stopped = { ...record, status: "stopped" };
|
|
7105
|
+
await writeAtomically(daemonJsonPath(record.projectPath), JSON.stringify(stopped, null, 2) + "\n");
|
|
7106
|
+
} catch {
|
|
7107
|
+
}
|
|
7108
|
+
try {
|
|
7109
|
+
await import_node_fs25.promises.unlink(daemonDiscoveryPath(record.project, home));
|
|
7110
|
+
} catch {
|
|
7111
|
+
}
|
|
7112
|
+
}
|
|
7057
7113
|
function teardownSlot(slot) {
|
|
7058
7114
|
try {
|
|
7059
7115
|
slot.stopPersist();
|
|
@@ -7170,6 +7226,23 @@ function resolveOtlpPort(opts) {
|
|
|
7170
7226
|
}
|
|
7171
7227
|
return 4318;
|
|
7172
7228
|
}
|
|
7229
|
+
function resolveWebPort() {
|
|
7230
|
+
const env = process.env.NEAT_WEB_PORT;
|
|
7231
|
+
if (env && env.length > 0) {
|
|
7232
|
+
const n = Number.parseInt(env, 10);
|
|
7233
|
+
if (Number.isFinite(n)) return n;
|
|
7234
|
+
}
|
|
7235
|
+
return 6328;
|
|
7236
|
+
}
|
|
7237
|
+
function portFromListenAddress(address, fallback) {
|
|
7238
|
+
try {
|
|
7239
|
+
const port = new URL(address).port;
|
|
7240
|
+
const n = Number.parseInt(port, 10);
|
|
7241
|
+
if (Number.isFinite(n) && n > 0) return n;
|
|
7242
|
+
} catch {
|
|
7243
|
+
}
|
|
7244
|
+
return fallback;
|
|
7245
|
+
}
|
|
7173
7246
|
function resolveHost(opts, authTokenSet) {
|
|
7174
7247
|
if (opts.host && opts.host.length > 0) return opts.host;
|
|
7175
7248
|
const env = process.env.HOST;
|
|
@@ -7180,13 +7253,24 @@ function resolveHost(opts, authTokenSet) {
|
|
|
7180
7253
|
async function startDaemon(opts = {}) {
|
|
7181
7254
|
const home = neatHomeFor(opts);
|
|
7182
7255
|
const regPath = registryPath();
|
|
7183
|
-
|
|
7184
|
-
|
|
7185
|
-
|
|
7256
|
+
const projectArg = typeof opts.project === "string" && opts.project.length > 0 ? opts.project : process.env.NEAT_PROJECT && process.env.NEAT_PROJECT.length > 0 ? process.env.NEAT_PROJECT : null;
|
|
7257
|
+
const projectPathArg = opts.projectPath && opts.projectPath.length > 0 ? opts.projectPath : process.env.NEAT_PROJECT_PATH && process.env.NEAT_PROJECT_PATH.length > 0 ? process.env.NEAT_PROJECT_PATH : null;
|
|
7258
|
+
const singleProject = projectArg;
|
|
7259
|
+
const singleProjectPath = singleProject && projectPathArg ? import_node_path42.default.resolve(projectPathArg) : null;
|
|
7260
|
+
if (singleProject && !singleProjectPath) {
|
|
7186
7261
|
throw new Error(
|
|
7187
|
-
`neatd:
|
|
7262
|
+
`neatd: project "${singleProject}" given without a projectPath; pass NEAT_PROJECT_PATH alongside NEAT_PROJECT.`
|
|
7188
7263
|
);
|
|
7189
7264
|
}
|
|
7265
|
+
if (!singleProject) {
|
|
7266
|
+
try {
|
|
7267
|
+
await import_node_fs25.promises.access(regPath);
|
|
7268
|
+
} catch {
|
|
7269
|
+
throw new Error(
|
|
7270
|
+
`neatd: registry not found at ${regPath}. Run \`neat init <path>\` to register a project before starting the daemon.`
|
|
7271
|
+
);
|
|
7272
|
+
}
|
|
7273
|
+
}
|
|
7190
7274
|
const pidPath = import_node_path42.default.join(home, "neatd.pid");
|
|
7191
7275
|
await writeAtomically(pidPath, `${process.pid}
|
|
7192
7276
|
`);
|
|
@@ -7275,21 +7359,37 @@ async function startDaemon(opts = {}) {
|
|
|
7275
7359
|
});
|
|
7276
7360
|
}
|
|
7277
7361
|
}
|
|
7362
|
+
async function enumerateProjects() {
|
|
7363
|
+
if (singleProject && singleProjectPath) {
|
|
7364
|
+
return [
|
|
7365
|
+
{
|
|
7366
|
+
name: singleProject,
|
|
7367
|
+
path: singleProjectPath,
|
|
7368
|
+
registeredAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7369
|
+
languages: [],
|
|
7370
|
+
status: "active"
|
|
7371
|
+
}
|
|
7372
|
+
];
|
|
7373
|
+
}
|
|
7374
|
+
return listProjects();
|
|
7375
|
+
}
|
|
7278
7376
|
async function loadAll() {
|
|
7279
|
-
|
|
7280
|
-
|
|
7281
|
-
|
|
7282
|
-
|
|
7283
|
-
|
|
7284
|
-
|
|
7285
|
-
|
|
7286
|
-
|
|
7287
|
-
|
|
7377
|
+
if (!singleProject) {
|
|
7378
|
+
try {
|
|
7379
|
+
const pruned = await pruneRegistry();
|
|
7380
|
+
for (const entry of pruned) {
|
|
7381
|
+
console.log(
|
|
7382
|
+
`neatd: pruned project "${entry.name}" \u2014 registered path ${entry.path} is gone`
|
|
7383
|
+
);
|
|
7384
|
+
slots.delete(entry.name);
|
|
7385
|
+
bootstrapStatus.delete(entry.name);
|
|
7386
|
+
bootstrapStartedAt.delete(entry.name);
|
|
7387
|
+
}
|
|
7388
|
+
} catch (err) {
|
|
7389
|
+
console.warn(`neatd: registry prune skipped \u2014 ${err.message}`);
|
|
7288
7390
|
}
|
|
7289
|
-
} catch (err) {
|
|
7290
|
-
console.warn(`neatd: registry prune skipped \u2014 ${err.message}`);
|
|
7291
7391
|
}
|
|
7292
|
-
const projects = await
|
|
7392
|
+
const projects = await enumerateProjects();
|
|
7293
7393
|
const seen = /* @__PURE__ */ new Set();
|
|
7294
7394
|
const pending = [];
|
|
7295
7395
|
for (const entry of projects) {
|
|
@@ -7314,7 +7414,7 @@ async function startDaemon(opts = {}) {
|
|
|
7314
7414
|
}
|
|
7315
7415
|
await Promise.allSettled(pending);
|
|
7316
7416
|
}
|
|
7317
|
-
const initialEntries = await
|
|
7417
|
+
const initialEntries = await enumerateProjects().catch(() => []);
|
|
7318
7418
|
for (const entry of initialEntries) {
|
|
7319
7419
|
bootstrapStatus.set(entry.name, "bootstrapping");
|
|
7320
7420
|
bootstrapStartedAt.set(entry.name, Date.now());
|
|
@@ -7324,6 +7424,7 @@ async function startDaemon(opts = {}) {
|
|
|
7324
7424
|
let otlpApp = null;
|
|
7325
7425
|
let restAddress = "";
|
|
7326
7426
|
let otlpAddress = "";
|
|
7427
|
+
let daemonRecord = null;
|
|
7327
7428
|
if (bind) {
|
|
7328
7429
|
const auth = readAuthEnv();
|
|
7329
7430
|
const host = resolveHost(opts, Boolean(auth.authToken));
|
|
@@ -7348,6 +7449,20 @@ async function startDaemon(opts = {}) {
|
|
|
7348
7449
|
}
|
|
7349
7450
|
}
|
|
7350
7451
|
});
|
|
7452
|
+
if (singleProject) {
|
|
7453
|
+
restApp.addHook("onSend", async (req, _reply, payload) => {
|
|
7454
|
+
if (req.url.split("?")[0] !== "/health") return payload;
|
|
7455
|
+
if (typeof payload !== "string") return payload;
|
|
7456
|
+
try {
|
|
7457
|
+
const body = JSON.parse(payload);
|
|
7458
|
+
if (typeof body !== "object" || body === null) return payload;
|
|
7459
|
+
body.project = singleProject;
|
|
7460
|
+
return JSON.stringify(body);
|
|
7461
|
+
} catch {
|
|
7462
|
+
return payload;
|
|
7463
|
+
}
|
|
7464
|
+
});
|
|
7465
|
+
}
|
|
7351
7466
|
restAddress = await restApp.listen({ port: restPort, host });
|
|
7352
7467
|
console.log(`neatd: REST listening on ${restAddress}`);
|
|
7353
7468
|
} catch (err) {
|
|
@@ -7363,6 +7478,25 @@ async function startDaemon(opts = {}) {
|
|
|
7363
7478
|
);
|
|
7364
7479
|
}
|
|
7365
7480
|
async function resolveTargetSlot(serviceName, traceId) {
|
|
7481
|
+
if (singleProject) {
|
|
7482
|
+
let slot2 = slots.get(singleProject);
|
|
7483
|
+
if (!slot2) {
|
|
7484
|
+
slot2 = await tryRecoverSlot({
|
|
7485
|
+
name: singleProject,
|
|
7486
|
+
path: singleProjectPath,
|
|
7487
|
+
registeredAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7488
|
+
languages: [],
|
|
7489
|
+
status: "active"
|
|
7490
|
+
});
|
|
7491
|
+
} else if (slot2.status === "broken") {
|
|
7492
|
+
slot2 = await tryRecoverSlot(slot2.entry);
|
|
7493
|
+
}
|
|
7494
|
+
if (!slot2 || slot2.status !== "active") {
|
|
7495
|
+
warnDroppedSpan(singleProject, slot2?.errorReason ?? "unknown");
|
|
7496
|
+
return null;
|
|
7497
|
+
}
|
|
7498
|
+
return slot2;
|
|
7499
|
+
}
|
|
7366
7500
|
const liveEntries = await listProjects().catch(() => []);
|
|
7367
7501
|
const target = routeSpanToProject(serviceName, liveEntries);
|
|
7368
7502
|
let slot = slots.get(target) ?? slots.get(DEFAULT_PROJECT);
|
|
@@ -7465,6 +7599,42 @@ async function startDaemon(opts = {}) {
|
|
|
7465
7599
|
`neatd: failed to bind OTLP on port ${otlpPort} \u2014 ${err.message}`
|
|
7466
7600
|
);
|
|
7467
7601
|
}
|
|
7602
|
+
if (singleProject && singleProjectPath) {
|
|
7603
|
+
const ports = {
|
|
7604
|
+
rest: portFromListenAddress(restAddress, restPort),
|
|
7605
|
+
otlp: portFromListenAddress(otlpAddress, otlpPort),
|
|
7606
|
+
// The daemon doesn't bind the web port itself (neatd spawns the web
|
|
7607
|
+
// child); it records the allocated value passed through so the
|
|
7608
|
+
// dashboard and `neat ps` agree on where to look.
|
|
7609
|
+
web: typeof opts.webPort === "number" ? opts.webPort : resolveWebPort()
|
|
7610
|
+
};
|
|
7611
|
+
daemonRecord = {
|
|
7612
|
+
project: singleProject,
|
|
7613
|
+
projectPath: singleProjectPath,
|
|
7614
|
+
pid: process.pid,
|
|
7615
|
+
status: "running",
|
|
7616
|
+
ports,
|
|
7617
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7618
|
+
neatVersion: resolveNeatVersion()
|
|
7619
|
+
};
|
|
7620
|
+
try {
|
|
7621
|
+
await writeDaemonRecord(daemonRecord, home);
|
|
7622
|
+
console.log(
|
|
7623
|
+
`neatd: project "${singleProject}" \u2192 REST ${ports.rest} / OTLP ${ports.otlp} / web ${ports.web} (daemon.json written)`
|
|
7624
|
+
);
|
|
7625
|
+
} catch (err) {
|
|
7626
|
+
for (const slot of slots.values()) teardownSlot(slot);
|
|
7627
|
+
if (restApp) await restApp.close().catch(() => {
|
|
7628
|
+
});
|
|
7629
|
+
if (otlpApp) await otlpApp.close().catch(() => {
|
|
7630
|
+
});
|
|
7631
|
+
await import_node_fs25.promises.unlink(pidPath).catch(() => {
|
|
7632
|
+
});
|
|
7633
|
+
throw new Error(
|
|
7634
|
+
`neatd: failed to write daemon.json for "${singleProject}" \u2014 ${err.message}`
|
|
7635
|
+
);
|
|
7636
|
+
}
|
|
7637
|
+
}
|
|
7468
7638
|
}
|
|
7469
7639
|
const initialBootstrap = loadAll().catch((err) => {
|
|
7470
7640
|
console.warn(`neatd: initial bootstrap pass failed \u2014 ${err.message}`);
|
|
@@ -7504,7 +7674,7 @@ async function startDaemon(opts = {}) {
|
|
|
7504
7674
|
const REGISTRY_RELOAD_DEBOUNCE_MS = 500;
|
|
7505
7675
|
let registryWatcher = null;
|
|
7506
7676
|
let reloadTimer = null;
|
|
7507
|
-
try {
|
|
7677
|
+
if (!singleProject) try {
|
|
7508
7678
|
const regDir = import_node_path42.default.dirname(regPath);
|
|
7509
7679
|
const regBase = import_node_path42.default.basename(regPath);
|
|
7510
7680
|
registryWatcher = (0, import_node_fs25.watch)(regDir, (_eventType, filename) => {
|
|
@@ -7547,6 +7717,9 @@ async function startDaemon(opts = {}) {
|
|
|
7547
7717
|
for (const slot of slots.values()) {
|
|
7548
7718
|
teardownSlot(slot);
|
|
7549
7719
|
}
|
|
7720
|
+
if (daemonRecord) {
|
|
7721
|
+
await clearDaemonRecord(daemonRecord, home);
|
|
7722
|
+
}
|
|
7550
7723
|
await import_node_fs25.promises.unlink(pidPath).catch(() => {
|
|
7551
7724
|
});
|
|
7552
7725
|
};
|
|
@@ -7558,7 +7731,8 @@ async function startDaemon(opts = {}) {
|
|
|
7558
7731
|
restAddress,
|
|
7559
7732
|
otlpAddress,
|
|
7560
7733
|
bootstrap: tracker,
|
|
7561
|
-
initialBootstrap
|
|
7734
|
+
initialBootstrap,
|
|
7735
|
+
daemonRecord
|
|
7562
7736
|
};
|
|
7563
7737
|
}
|
|
7564
7738
|
// Annotate the CommonJS export names for ESM import in node:
|