@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/neatd.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(
|
|
@@ -592,14 +594,15 @@ var init_otel = __esm({
|
|
|
592
594
|
|
|
593
595
|
// src/neatd.ts
|
|
594
596
|
init_cjs_shims();
|
|
595
|
-
var
|
|
597
|
+
var import_node_fs27 = require("fs");
|
|
596
598
|
var import_node_path44 = __toESM(require("path"), 1);
|
|
597
|
-
var
|
|
599
|
+
var import_node_module2 = require("module");
|
|
598
600
|
|
|
599
601
|
// src/daemon.ts
|
|
600
602
|
init_cjs_shims();
|
|
601
603
|
var import_node_fs25 = require("fs");
|
|
602
604
|
var import_node_path42 = __toESM(require("path"), 1);
|
|
605
|
+
var import_node_module = require("module");
|
|
603
606
|
|
|
604
607
|
// src/graph.ts
|
|
605
608
|
init_cjs_shims();
|
|
@@ -6850,6 +6853,59 @@ function unroutedErrorsPath(neatHome3) {
|
|
|
6850
6853
|
}
|
|
6851
6854
|
|
|
6852
6855
|
// src/daemon.ts
|
|
6856
|
+
function daemonJsonPath(scanPath) {
|
|
6857
|
+
return import_node_path42.default.join(scanPath, "neat-out", "daemon.json");
|
|
6858
|
+
}
|
|
6859
|
+
function daemonsDiscoveryDir(home) {
|
|
6860
|
+
const base = home && home.length > 0 ? home : neatHomeFromEnv();
|
|
6861
|
+
return import_node_path42.default.join(base, "daemons");
|
|
6862
|
+
}
|
|
6863
|
+
function daemonDiscoveryPath(project, home) {
|
|
6864
|
+
return import_node_path42.default.join(daemonsDiscoveryDir(home), `${sanitizeDiscoveryName(project)}.json`);
|
|
6865
|
+
}
|
|
6866
|
+
function sanitizeDiscoveryName(project) {
|
|
6867
|
+
return project.replace(/[^A-Za-z0-9._-]/g, "_");
|
|
6868
|
+
}
|
|
6869
|
+
function neatHomeFromEnv() {
|
|
6870
|
+
const env = process.env.NEAT_HOME;
|
|
6871
|
+
if (env && env.length > 0) return import_node_path42.default.resolve(env);
|
|
6872
|
+
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
6873
|
+
return import_node_path42.default.join(home, ".neat");
|
|
6874
|
+
}
|
|
6875
|
+
function resolveNeatVersion() {
|
|
6876
|
+
if (process.env.NEAT_LOCAL_VERSION && process.env.NEAT_LOCAL_VERSION.length > 0) {
|
|
6877
|
+
return process.env.NEAT_LOCAL_VERSION;
|
|
6878
|
+
}
|
|
6879
|
+
try {
|
|
6880
|
+
const req2 = (0, import_node_module.createRequire)(importMetaUrl);
|
|
6881
|
+
const pkg = req2("../package.json");
|
|
6882
|
+
return typeof pkg.version === "string" ? pkg.version : "0.0.0";
|
|
6883
|
+
} catch {
|
|
6884
|
+
return "0.0.0";
|
|
6885
|
+
}
|
|
6886
|
+
}
|
|
6887
|
+
async function writeDaemonRecord(record, home) {
|
|
6888
|
+
const body = JSON.stringify(record, null, 2) + "\n";
|
|
6889
|
+
await writeAtomically(daemonJsonPath(record.projectPath), body);
|
|
6890
|
+
try {
|
|
6891
|
+
await writeAtomically(daemonDiscoveryPath(record.project, home), body);
|
|
6892
|
+
} catch (err) {
|
|
6893
|
+
console.warn(
|
|
6894
|
+
`neatd: could not write discovery copy for "${record.project}" \u2014 ${err.message}`
|
|
6895
|
+
);
|
|
6896
|
+
}
|
|
6897
|
+
}
|
|
6898
|
+
async function clearDaemonRecord(record, home) {
|
|
6899
|
+
try {
|
|
6900
|
+
const stopped = { ...record, status: "stopped" };
|
|
6901
|
+
await writeAtomically(daemonJsonPath(record.projectPath), JSON.stringify(stopped, null, 2) + "\n");
|
|
6902
|
+
} catch {
|
|
6903
|
+
}
|
|
6904
|
+
try {
|
|
6905
|
+
await import_node_fs25.promises.unlink(daemonDiscoveryPath(record.project, home));
|
|
6906
|
+
} catch {
|
|
6907
|
+
}
|
|
6908
|
+
}
|
|
6853
6909
|
function teardownSlot(slot) {
|
|
6854
6910
|
try {
|
|
6855
6911
|
slot.stopPersist();
|
|
@@ -6966,6 +7022,23 @@ function resolveOtlpPort(opts) {
|
|
|
6966
7022
|
}
|
|
6967
7023
|
return 4318;
|
|
6968
7024
|
}
|
|
7025
|
+
function resolveWebPort() {
|
|
7026
|
+
const env = process.env.NEAT_WEB_PORT;
|
|
7027
|
+
if (env && env.length > 0) {
|
|
7028
|
+
const n = Number.parseInt(env, 10);
|
|
7029
|
+
if (Number.isFinite(n)) return n;
|
|
7030
|
+
}
|
|
7031
|
+
return 6328;
|
|
7032
|
+
}
|
|
7033
|
+
function portFromListenAddress(address, fallback) {
|
|
7034
|
+
try {
|
|
7035
|
+
const port = new URL(address).port;
|
|
7036
|
+
const n = Number.parseInt(port, 10);
|
|
7037
|
+
if (Number.isFinite(n) && n > 0) return n;
|
|
7038
|
+
} catch {
|
|
7039
|
+
}
|
|
7040
|
+
return fallback;
|
|
7041
|
+
}
|
|
6969
7042
|
function resolveHost(opts, authTokenSet) {
|
|
6970
7043
|
if (opts.host && opts.host.length > 0) return opts.host;
|
|
6971
7044
|
const env = process.env.HOST;
|
|
@@ -6976,13 +7049,24 @@ function resolveHost(opts, authTokenSet) {
|
|
|
6976
7049
|
async function startDaemon(opts = {}) {
|
|
6977
7050
|
const home = neatHomeFor(opts);
|
|
6978
7051
|
const regPath = registryPath();
|
|
6979
|
-
|
|
6980
|
-
|
|
6981
|
-
|
|
7052
|
+
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;
|
|
7053
|
+
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;
|
|
7054
|
+
const singleProject = projectArg;
|
|
7055
|
+
const singleProjectPath = singleProject && projectPathArg ? import_node_path42.default.resolve(projectPathArg) : null;
|
|
7056
|
+
if (singleProject && !singleProjectPath) {
|
|
6982
7057
|
throw new Error(
|
|
6983
|
-
`neatd:
|
|
7058
|
+
`neatd: project "${singleProject}" given without a projectPath; pass NEAT_PROJECT_PATH alongside NEAT_PROJECT.`
|
|
6984
7059
|
);
|
|
6985
7060
|
}
|
|
7061
|
+
if (!singleProject) {
|
|
7062
|
+
try {
|
|
7063
|
+
await import_node_fs25.promises.access(regPath);
|
|
7064
|
+
} catch {
|
|
7065
|
+
throw new Error(
|
|
7066
|
+
`neatd: registry not found at ${regPath}. Run \`neat init <path>\` to register a project before starting the daemon.`
|
|
7067
|
+
);
|
|
7068
|
+
}
|
|
7069
|
+
}
|
|
6986
7070
|
const pidPath = import_node_path42.default.join(home, "neatd.pid");
|
|
6987
7071
|
await writeAtomically(pidPath, `${process.pid}
|
|
6988
7072
|
`);
|
|
@@ -7071,21 +7155,37 @@ async function startDaemon(opts = {}) {
|
|
|
7071
7155
|
});
|
|
7072
7156
|
}
|
|
7073
7157
|
}
|
|
7158
|
+
async function enumerateProjects() {
|
|
7159
|
+
if (singleProject && singleProjectPath) {
|
|
7160
|
+
return [
|
|
7161
|
+
{
|
|
7162
|
+
name: singleProject,
|
|
7163
|
+
path: singleProjectPath,
|
|
7164
|
+
registeredAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7165
|
+
languages: [],
|
|
7166
|
+
status: "active"
|
|
7167
|
+
}
|
|
7168
|
+
];
|
|
7169
|
+
}
|
|
7170
|
+
return listProjects();
|
|
7171
|
+
}
|
|
7074
7172
|
async function loadAll() {
|
|
7075
|
-
|
|
7076
|
-
|
|
7077
|
-
|
|
7078
|
-
|
|
7079
|
-
|
|
7080
|
-
|
|
7081
|
-
|
|
7082
|
-
|
|
7083
|
-
|
|
7173
|
+
if (!singleProject) {
|
|
7174
|
+
try {
|
|
7175
|
+
const pruned = await pruneRegistry();
|
|
7176
|
+
for (const entry2 of pruned) {
|
|
7177
|
+
console.log(
|
|
7178
|
+
`neatd: pruned project "${entry2.name}" \u2014 registered path ${entry2.path} is gone`
|
|
7179
|
+
);
|
|
7180
|
+
slots.delete(entry2.name);
|
|
7181
|
+
bootstrapStatus.delete(entry2.name);
|
|
7182
|
+
bootstrapStartedAt.delete(entry2.name);
|
|
7183
|
+
}
|
|
7184
|
+
} catch (err) {
|
|
7185
|
+
console.warn(`neatd: registry prune skipped \u2014 ${err.message}`);
|
|
7084
7186
|
}
|
|
7085
|
-
} catch (err) {
|
|
7086
|
-
console.warn(`neatd: registry prune skipped \u2014 ${err.message}`);
|
|
7087
7187
|
}
|
|
7088
|
-
const projects = await
|
|
7188
|
+
const projects = await enumerateProjects();
|
|
7089
7189
|
const seen = /* @__PURE__ */ new Set();
|
|
7090
7190
|
const pending = [];
|
|
7091
7191
|
for (const entry2 of projects) {
|
|
@@ -7110,7 +7210,7 @@ async function startDaemon(opts = {}) {
|
|
|
7110
7210
|
}
|
|
7111
7211
|
await Promise.allSettled(pending);
|
|
7112
7212
|
}
|
|
7113
|
-
const initialEntries = await
|
|
7213
|
+
const initialEntries = await enumerateProjects().catch(() => []);
|
|
7114
7214
|
for (const entry2 of initialEntries) {
|
|
7115
7215
|
bootstrapStatus.set(entry2.name, "bootstrapping");
|
|
7116
7216
|
bootstrapStartedAt.set(entry2.name, Date.now());
|
|
@@ -7120,6 +7220,7 @@ async function startDaemon(opts = {}) {
|
|
|
7120
7220
|
let otlpApp = null;
|
|
7121
7221
|
let restAddress = "";
|
|
7122
7222
|
let otlpAddress = "";
|
|
7223
|
+
let daemonRecord = null;
|
|
7123
7224
|
if (bind) {
|
|
7124
7225
|
const auth = readAuthEnv();
|
|
7125
7226
|
const host = resolveHost(opts, Boolean(auth.authToken));
|
|
@@ -7144,6 +7245,20 @@ async function startDaemon(opts = {}) {
|
|
|
7144
7245
|
}
|
|
7145
7246
|
}
|
|
7146
7247
|
});
|
|
7248
|
+
if (singleProject) {
|
|
7249
|
+
restApp.addHook("onSend", async (req2, _reply, payload) => {
|
|
7250
|
+
if (req2.url.split("?")[0] !== "/health") return payload;
|
|
7251
|
+
if (typeof payload !== "string") return payload;
|
|
7252
|
+
try {
|
|
7253
|
+
const body = JSON.parse(payload);
|
|
7254
|
+
if (typeof body !== "object" || body === null) return payload;
|
|
7255
|
+
body.project = singleProject;
|
|
7256
|
+
return JSON.stringify(body);
|
|
7257
|
+
} catch {
|
|
7258
|
+
return payload;
|
|
7259
|
+
}
|
|
7260
|
+
});
|
|
7261
|
+
}
|
|
7147
7262
|
restAddress = await restApp.listen({ port: restPort, host });
|
|
7148
7263
|
console.log(`neatd: REST listening on ${restAddress}`);
|
|
7149
7264
|
} catch (err) {
|
|
@@ -7159,6 +7274,25 @@ async function startDaemon(opts = {}) {
|
|
|
7159
7274
|
);
|
|
7160
7275
|
}
|
|
7161
7276
|
async function resolveTargetSlot(serviceName, traceId) {
|
|
7277
|
+
if (singleProject) {
|
|
7278
|
+
let slot2 = slots.get(singleProject);
|
|
7279
|
+
if (!slot2) {
|
|
7280
|
+
slot2 = await tryRecoverSlot({
|
|
7281
|
+
name: singleProject,
|
|
7282
|
+
path: singleProjectPath,
|
|
7283
|
+
registeredAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7284
|
+
languages: [],
|
|
7285
|
+
status: "active"
|
|
7286
|
+
});
|
|
7287
|
+
} else if (slot2.status === "broken") {
|
|
7288
|
+
slot2 = await tryRecoverSlot(slot2.entry);
|
|
7289
|
+
}
|
|
7290
|
+
if (!slot2 || slot2.status !== "active") {
|
|
7291
|
+
warnDroppedSpan(singleProject, slot2?.errorReason ?? "unknown");
|
|
7292
|
+
return null;
|
|
7293
|
+
}
|
|
7294
|
+
return slot2;
|
|
7295
|
+
}
|
|
7162
7296
|
const liveEntries = await listProjects().catch(() => []);
|
|
7163
7297
|
const target = routeSpanToProject(serviceName, liveEntries);
|
|
7164
7298
|
let slot = slots.get(target) ?? slots.get(DEFAULT_PROJECT);
|
|
@@ -7261,6 +7395,42 @@ async function startDaemon(opts = {}) {
|
|
|
7261
7395
|
`neatd: failed to bind OTLP on port ${otlpPort} \u2014 ${err.message}`
|
|
7262
7396
|
);
|
|
7263
7397
|
}
|
|
7398
|
+
if (singleProject && singleProjectPath) {
|
|
7399
|
+
const ports = {
|
|
7400
|
+
rest: portFromListenAddress(restAddress, restPort),
|
|
7401
|
+
otlp: portFromListenAddress(otlpAddress, otlpPort),
|
|
7402
|
+
// The daemon doesn't bind the web port itself (neatd spawns the web
|
|
7403
|
+
// child); it records the allocated value passed through so the
|
|
7404
|
+
// dashboard and `neat ps` agree on where to look.
|
|
7405
|
+
web: typeof opts.webPort === "number" ? opts.webPort : resolveWebPort()
|
|
7406
|
+
};
|
|
7407
|
+
daemonRecord = {
|
|
7408
|
+
project: singleProject,
|
|
7409
|
+
projectPath: singleProjectPath,
|
|
7410
|
+
pid: process.pid,
|
|
7411
|
+
status: "running",
|
|
7412
|
+
ports,
|
|
7413
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7414
|
+
neatVersion: resolveNeatVersion()
|
|
7415
|
+
};
|
|
7416
|
+
try {
|
|
7417
|
+
await writeDaemonRecord(daemonRecord, home);
|
|
7418
|
+
console.log(
|
|
7419
|
+
`neatd: project "${singleProject}" \u2192 REST ${ports.rest} / OTLP ${ports.otlp} / web ${ports.web} (daemon.json written)`
|
|
7420
|
+
);
|
|
7421
|
+
} catch (err) {
|
|
7422
|
+
for (const slot of slots.values()) teardownSlot(slot);
|
|
7423
|
+
if (restApp) await restApp.close().catch(() => {
|
|
7424
|
+
});
|
|
7425
|
+
if (otlpApp) await otlpApp.close().catch(() => {
|
|
7426
|
+
});
|
|
7427
|
+
await import_node_fs25.promises.unlink(pidPath).catch(() => {
|
|
7428
|
+
});
|
|
7429
|
+
throw new Error(
|
|
7430
|
+
`neatd: failed to write daemon.json for "${singleProject}" \u2014 ${err.message}`
|
|
7431
|
+
);
|
|
7432
|
+
}
|
|
7433
|
+
}
|
|
7264
7434
|
}
|
|
7265
7435
|
const initialBootstrap = loadAll().catch((err) => {
|
|
7266
7436
|
console.warn(`neatd: initial bootstrap pass failed \u2014 ${err.message}`);
|
|
@@ -7300,7 +7470,7 @@ async function startDaemon(opts = {}) {
|
|
|
7300
7470
|
const REGISTRY_RELOAD_DEBOUNCE_MS = 500;
|
|
7301
7471
|
let registryWatcher = null;
|
|
7302
7472
|
let reloadTimer = null;
|
|
7303
|
-
try {
|
|
7473
|
+
if (!singleProject) try {
|
|
7304
7474
|
const regDir = import_node_path42.default.dirname(regPath);
|
|
7305
7475
|
const regBase = import_node_path42.default.basename(regPath);
|
|
7306
7476
|
registryWatcher = (0, import_node_fs25.watch)(regDir, (_eventType, filename) => {
|
|
@@ -7343,6 +7513,9 @@ async function startDaemon(opts = {}) {
|
|
|
7343
7513
|
for (const slot of slots.values()) {
|
|
7344
7514
|
teardownSlot(slot);
|
|
7345
7515
|
}
|
|
7516
|
+
if (daemonRecord) {
|
|
7517
|
+
await clearDaemonRecord(daemonRecord, home);
|
|
7518
|
+
}
|
|
7346
7519
|
await import_node_fs25.promises.unlink(pidPath).catch(() => {
|
|
7347
7520
|
});
|
|
7348
7521
|
};
|
|
@@ -7354,7 +7527,8 @@ async function startDaemon(opts = {}) {
|
|
|
7354
7527
|
restAddress,
|
|
7355
7528
|
otlpAddress,
|
|
7356
7529
|
bootstrap: tracker,
|
|
7357
|
-
initialBootstrap
|
|
7530
|
+
initialBootstrap,
|
|
7531
|
+
daemonRecord
|
|
7358
7532
|
};
|
|
7359
7533
|
}
|
|
7360
7534
|
|
|
@@ -7364,9 +7538,40 @@ init_auth();
|
|
|
7364
7538
|
// src/web-spawn.ts
|
|
7365
7539
|
init_cjs_shims();
|
|
7366
7540
|
var import_node_child_process2 = require("child_process");
|
|
7541
|
+
var import_node_fs26 = require("fs");
|
|
7367
7542
|
var import_node_net = __toESM(require("net"), 1);
|
|
7368
7543
|
var import_node_path43 = __toESM(require("path"), 1);
|
|
7369
7544
|
var DEFAULT_WEB_PORT = 6328;
|
|
7545
|
+
var DEFAULT_REST_PORT = 8080;
|
|
7546
|
+
function asValidPort(value) {
|
|
7547
|
+
const n = typeof value === "number" ? value : Number.parseInt(String(value ?? ""), 10);
|
|
7548
|
+
return Number.isInteger(n) && n > 0 && n <= 65535 ? n : null;
|
|
7549
|
+
}
|
|
7550
|
+
function projectRoot() {
|
|
7551
|
+
const fromEnv = process.env.NEAT_SCAN_PATH;
|
|
7552
|
+
return import_node_path43.default.resolve(fromEnv && fromEnv.length > 0 ? fromEnv : process.cwd());
|
|
7553
|
+
}
|
|
7554
|
+
async function readDaemonPorts(root) {
|
|
7555
|
+
try {
|
|
7556
|
+
const raw = await import_node_fs26.promises.readFile(import_node_path43.default.join(root, "neat-out", "daemon.json"), "utf8");
|
|
7557
|
+
const parsed = JSON.parse(raw);
|
|
7558
|
+
const ports = parsed?.ports ?? {};
|
|
7559
|
+
return { web: asValidPort(ports.web), rest: asValidPort(ports.rest) };
|
|
7560
|
+
} catch {
|
|
7561
|
+
return { web: null, rest: null };
|
|
7562
|
+
}
|
|
7563
|
+
}
|
|
7564
|
+
function resolveWebPorts(args) {
|
|
7565
|
+
const { daemonWeb, daemonRest, webPortEnv, apiUrlEnv, restPortArg } = args;
|
|
7566
|
+
const portFromEnv = webPortEnv && webPortEnv.length > 0 ? Number.parseInt(webPortEnv, 10) : null;
|
|
7567
|
+
if (portFromEnv !== null && (!Number.isFinite(portFromEnv) || portFromEnv <= 0 || portFromEnv > 65535)) {
|
|
7568
|
+
throw new Error(`neatd: invalid NEAT_WEB_PORT="${webPortEnv}"`);
|
|
7569
|
+
}
|
|
7570
|
+
const webPort = daemonWeb ?? portFromEnv ?? DEFAULT_WEB_PORT;
|
|
7571
|
+
const restPort = daemonRest ?? asValidPort(restPortArg) ?? DEFAULT_REST_PORT;
|
|
7572
|
+
const apiUrl = apiUrlEnv ?? `http://localhost:${restPort}`;
|
|
7573
|
+
return { webPort, apiUrl };
|
|
7574
|
+
}
|
|
7370
7575
|
async function assertPortFree(port) {
|
|
7371
7576
|
await new Promise((resolve, reject) => {
|
|
7372
7577
|
const tester = import_node_net.default.createServer();
|
|
@@ -7398,42 +7603,98 @@ function resolveWebPackageDir() {
|
|
|
7398
7603
|
function resolveStandaloneServerEntry(webDir) {
|
|
7399
7604
|
return import_node_path43.default.join(webDir, ".next/standalone/packages/web/server.js");
|
|
7400
7605
|
}
|
|
7401
|
-
async function
|
|
7402
|
-
|
|
7403
|
-
|
|
7404
|
-
|
|
7405
|
-
|
|
7406
|
-
|
|
7606
|
+
async function pickInternalPort() {
|
|
7607
|
+
return new Promise((resolve, reject) => {
|
|
7608
|
+
const srv = import_node_net.default.createServer();
|
|
7609
|
+
srv.once("error", reject);
|
|
7610
|
+
srv.listen(0, "127.0.0.1", () => {
|
|
7611
|
+
const addr = srv.address();
|
|
7612
|
+
if (addr && typeof addr === "object") {
|
|
7613
|
+
const port = addr.port;
|
|
7614
|
+
srv.close(() => resolve(port));
|
|
7615
|
+
} else {
|
|
7616
|
+
srv.close(() => reject(new Error("neatd: could not pick an internal web port")));
|
|
7617
|
+
}
|
|
7618
|
+
});
|
|
7619
|
+
});
|
|
7620
|
+
}
|
|
7621
|
+
async function spawnWebUI(restPort, opts = {}) {
|
|
7622
|
+
const root = projectRoot();
|
|
7623
|
+
const fromDaemon = await readDaemonPorts(root);
|
|
7624
|
+
const { webPort: port, apiUrl } = resolveWebPorts({
|
|
7625
|
+
daemonWeb: fromDaemon.web,
|
|
7626
|
+
daemonRest: fromDaemon.rest,
|
|
7627
|
+
webPortEnv: process.env.NEAT_WEB_PORT,
|
|
7628
|
+
apiUrlEnv: process.env.NEAT_API_URL,
|
|
7629
|
+
restPortArg: restPort
|
|
7630
|
+
});
|
|
7407
7631
|
await assertPortFree(port);
|
|
7408
|
-
const
|
|
7409
|
-
|
|
7410
|
-
|
|
7411
|
-
|
|
7412
|
-
|
|
7413
|
-
|
|
7414
|
-
|
|
7415
|
-
|
|
7632
|
+
const serverEntry = opts.serverEntry ?? resolveStandaloneServerEntry(resolveWebPackageDir());
|
|
7633
|
+
if (!opts.skipBuildCheck) {
|
|
7634
|
+
try {
|
|
7635
|
+
require.resolve(serverEntry);
|
|
7636
|
+
} catch {
|
|
7637
|
+
throw new Error(
|
|
7638
|
+
`neatd: web UI standalone build missing at ${serverEntry}. The published @neat.is/web tarball should include it; if you're running from a monorepo checkout, run \`npm run build --workspace @neat.is/web\` first, or set NEAT_WEB_DISABLED=1 to skip the web UI.`
|
|
7639
|
+
);
|
|
7640
|
+
}
|
|
7416
7641
|
}
|
|
7417
|
-
|
|
7418
|
-
|
|
7419
|
-
|
|
7420
|
-
|
|
7421
|
-
|
|
7422
|
-
|
|
7423
|
-
|
|
7424
|
-
|
|
7425
|
-
|
|
7426
|
-
|
|
7427
|
-
|
|
7642
|
+
let child = null;
|
|
7643
|
+
let internalPort = 0;
|
|
7644
|
+
let starting = null;
|
|
7645
|
+
let stopped = false;
|
|
7646
|
+
function ensureStarted() {
|
|
7647
|
+
if (starting) return starting;
|
|
7648
|
+
starting = (async () => {
|
|
7649
|
+
internalPort = await pickInternalPort();
|
|
7650
|
+
const env = {
|
|
7651
|
+
...process.env,
|
|
7652
|
+
PORT: String(internalPort),
|
|
7653
|
+
HOSTNAME: "127.0.0.1",
|
|
7654
|
+
NEAT_API_URL: apiUrl
|
|
7655
|
+
};
|
|
7656
|
+
child = (0, import_node_child_process2.spawn)(process.execPath, [serverEntry], {
|
|
7657
|
+
cwd: import_node_path43.default.dirname(serverEntry),
|
|
7658
|
+
env,
|
|
7659
|
+
stdio: ["ignore", "inherit", "inherit"],
|
|
7660
|
+
detached: false
|
|
7661
|
+
});
|
|
7662
|
+
child.on("error", (err) => {
|
|
7663
|
+
console.error(`neatd: web UI spawn error \u2014 ${err.message}`);
|
|
7664
|
+
});
|
|
7665
|
+
await waitForListening(internalPort);
|
|
7666
|
+
console.log(`neatd: web UI ready on http://localhost:${port}`);
|
|
7667
|
+
})();
|
|
7668
|
+
return starting;
|
|
7669
|
+
}
|
|
7670
|
+
const front = import_node_net.default.createServer((socket) => {
|
|
7671
|
+
socket.on("error", () => socket.destroy());
|
|
7672
|
+
ensureStarted().then(() => {
|
|
7673
|
+
if (stopped) {
|
|
7674
|
+
socket.destroy();
|
|
7675
|
+
return;
|
|
7676
|
+
}
|
|
7677
|
+
const upstream = import_node_net.default.connect(internalPort, "127.0.0.1");
|
|
7678
|
+
upstream.on("error", () => socket.destroy());
|
|
7679
|
+
socket.pipe(upstream);
|
|
7680
|
+
upstream.pipe(socket);
|
|
7681
|
+
socket.on("close", () => upstream.destroy());
|
|
7682
|
+
upstream.on("close", () => socket.destroy());
|
|
7683
|
+
}).catch((err) => {
|
|
7684
|
+
console.error(`neatd: web UI failed to start \u2014 ${err.message}`);
|
|
7685
|
+
socket.destroy();
|
|
7686
|
+
});
|
|
7428
7687
|
});
|
|
7429
|
-
|
|
7430
|
-
|
|
7688
|
+
await new Promise((resolve, reject) => {
|
|
7689
|
+
front.once("error", reject);
|
|
7690
|
+
front.listen(port, "127.0.0.1", () => resolve());
|
|
7431
7691
|
});
|
|
7432
|
-
console.log(`neatd: web UI listening on http://localhost:${port}`);
|
|
7433
|
-
let stopped = false;
|
|
7692
|
+
console.log(`neatd: web UI listening on http://localhost:${port} (starts on first open)`);
|
|
7434
7693
|
async function stop() {
|
|
7435
|
-
if (stopped
|
|
7694
|
+
if (stopped) return;
|
|
7436
7695
|
stopped = true;
|
|
7696
|
+
await new Promise((resolve) => front.close(() => resolve()));
|
|
7697
|
+
if (!child || !child.pid) return;
|
|
7437
7698
|
try {
|
|
7438
7699
|
child.kill("SIGTERM");
|
|
7439
7700
|
} catch {
|
|
@@ -7441,18 +7702,46 @@ async function spawnWebUI(restPort) {
|
|
|
7441
7702
|
await new Promise((resolve) => {
|
|
7442
7703
|
const t = setTimeout(() => {
|
|
7443
7704
|
try {
|
|
7444
|
-
child
|
|
7705
|
+
child?.kill("SIGKILL");
|
|
7445
7706
|
} catch {
|
|
7446
7707
|
}
|
|
7447
7708
|
resolve();
|
|
7448
7709
|
}, 3e3);
|
|
7449
|
-
child
|
|
7710
|
+
child?.once("exit", () => {
|
|
7450
7711
|
clearTimeout(t);
|
|
7451
7712
|
resolve();
|
|
7452
7713
|
});
|
|
7453
7714
|
});
|
|
7454
7715
|
}
|
|
7455
|
-
return {
|
|
7716
|
+
return {
|
|
7717
|
+
get child() {
|
|
7718
|
+
return child;
|
|
7719
|
+
},
|
|
7720
|
+
port,
|
|
7721
|
+
started: () => child !== null,
|
|
7722
|
+
stop
|
|
7723
|
+
};
|
|
7724
|
+
}
|
|
7725
|
+
async function waitForListening(port, timeoutMs = 15e3) {
|
|
7726
|
+
const deadline = Date.now() + timeoutMs;
|
|
7727
|
+
for (; ; ) {
|
|
7728
|
+
const ok = await new Promise((resolve) => {
|
|
7729
|
+
const s = import_node_net.default.connect(port, "127.0.0.1");
|
|
7730
|
+
s.once("connect", () => {
|
|
7731
|
+
s.destroy();
|
|
7732
|
+
resolve(true);
|
|
7733
|
+
});
|
|
7734
|
+
s.once("error", () => {
|
|
7735
|
+
s.destroy();
|
|
7736
|
+
resolve(false);
|
|
7737
|
+
});
|
|
7738
|
+
});
|
|
7739
|
+
if (ok) return;
|
|
7740
|
+
if (Date.now() > deadline) {
|
|
7741
|
+
throw new Error(`neatd: web UI did not start listening on :${port} within ${timeoutMs}ms`);
|
|
7742
|
+
}
|
|
7743
|
+
await new Promise((r) => setTimeout(r, 150));
|
|
7744
|
+
}
|
|
7456
7745
|
}
|
|
7457
7746
|
|
|
7458
7747
|
// src/version-skew.ts
|
|
@@ -7525,7 +7814,7 @@ function localVersion() {
|
|
|
7525
7814
|
return process.env.NEAT_LOCAL_VERSION;
|
|
7526
7815
|
}
|
|
7527
7816
|
try {
|
|
7528
|
-
const req2 = (0,
|
|
7817
|
+
const req2 = (0, import_node_module2.createRequire)(importMetaUrl);
|
|
7529
7818
|
const pkg = req2("../package.json");
|
|
7530
7819
|
return typeof pkg.version === "string" ? pkg.version : "0.0.0";
|
|
7531
7820
|
} catch {
|
|
@@ -7541,7 +7830,7 @@ function neatHome2() {
|
|
|
7541
7830
|
}
|
|
7542
7831
|
async function readPid() {
|
|
7543
7832
|
try {
|
|
7544
|
-
const raw = await
|
|
7833
|
+
const raw = await import_node_fs27.promises.readFile(import_node_path44.default.join(neatHome2(), "neatd.pid"), "utf8");
|
|
7545
7834
|
const n = Number.parseInt(raw.trim(), 10);
|
|
7546
7835
|
return Number.isFinite(n) ? n : null;
|
|
7547
7836
|
} catch {
|
|
@@ -7555,10 +7844,19 @@ function restPortFromEnv() {
|
|
|
7555
7844
|
const raw = process.env.PORT;
|
|
7556
7845
|
return raw && raw.length > 0 ? Number.parseInt(raw, 10) : 8080;
|
|
7557
7846
|
}
|
|
7847
|
+
function webPortFromEnv() {
|
|
7848
|
+
const raw = process.env.NEAT_WEB_PORT;
|
|
7849
|
+
if (!raw || raw.length === 0) return void 0;
|
|
7850
|
+
const n = Number.parseInt(raw, 10);
|
|
7851
|
+
return Number.isFinite(n) ? n : void 0;
|
|
7852
|
+
}
|
|
7558
7853
|
async function cmdStart() {
|
|
7854
|
+
const project = process.env.NEAT_PROJECT;
|
|
7855
|
+
const projectPath = process.env.NEAT_PROJECT_PATH;
|
|
7856
|
+
const startOpts = project && project.length > 0 ? { project, projectPath, webPort: webPortFromEnv() } : {};
|
|
7559
7857
|
let handle;
|
|
7560
7858
|
try {
|
|
7561
|
-
handle = await startDaemon();
|
|
7859
|
+
handle = await startDaemon(startOpts);
|
|
7562
7860
|
} catch (err) {
|
|
7563
7861
|
if (err instanceof BindAuthorityError) {
|
|
7564
7862
|
console.error(`neatd: ${err.message}`);
|