@neat.is/core 0.4.0 → 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-N6RPINEJ.js → chunk-CB2UK4EH.js} +138 -41
- package/dist/chunk-CB2UK4EH.js.map +1 -0
- package/dist/{chunk-IXIFJKMM.js → chunk-D5PIJFBE.js} +2 -2
- package/dist/{chunk-4V23KYOP.js → chunk-KYRIQIPG.js} +17 -9
- package/dist/chunk-KYRIQIPG.js.map +1 -0
- package/dist/{chunk-UPW4CMOH.js → chunk-NTQHMXWE.js} +122 -46
- package/dist/chunk-NTQHMXWE.js.map +1 -0
- package/dist/cli.cjs +312 -87
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.d.cts +2 -1
- package/dist/cli.d.ts +2 -1
- package/dist/cli.js +196 -47
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +267 -93
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +19 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +4 -4
- package/dist/neatd.cjs +276 -102
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +3 -3
- package/dist/{otel-grpc-GGZHR7VM.js → otel-grpc-QTX2YQJZ.js} +3 -3
- package/dist/server.cjs +120 -44
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +3 -3
- package/package.json +2 -2
- package/dist/chunk-4V23KYOP.js.map +0 -1
- package/dist/chunk-N6RPINEJ.js.map +0 -1
- package/dist/chunk-UPW4CMOH.js.map +0 -1
- /package/dist/{chunk-IXIFJKMM.js.map → chunk-D5PIJFBE.js.map} +0 -0
- /package/dist/{otel-grpc-GGZHR7VM.js.map → otel-grpc-QTX2YQJZ.js.map} +0 -0
package/dist/neatd.cjs
CHANGED
|
@@ -57,9 +57,9 @@ function mountBearerAuth(app, opts) {
|
|
|
57
57
|
const suffixes = [...DEFAULT_UNAUTH_SUFFIXES, ...opts.extraUnauthenticatedSuffixes ?? []];
|
|
58
58
|
const publicRead = opts.publicRead === true;
|
|
59
59
|
app.addHook("preHandler", (req2, reply, done) => {
|
|
60
|
-
const
|
|
60
|
+
const path40 = (req2.url.split("?")[0] ?? "").replace(/\/+$/, "");
|
|
61
61
|
for (const suffix of suffixes) {
|
|
62
|
-
if (
|
|
62
|
+
if (path40 === suffix || path40.endsWith(suffix)) {
|
|
63
63
|
done();
|
|
64
64
|
return;
|
|
65
65
|
}
|
|
@@ -512,14 +512,14 @@ var init_otel = __esm({
|
|
|
512
512
|
|
|
513
513
|
// src/neatd.ts
|
|
514
514
|
init_cjs_shims();
|
|
515
|
-
var
|
|
516
|
-
var
|
|
515
|
+
var import_node_fs23 = require("fs");
|
|
516
|
+
var import_node_path39 = __toESM(require("path"), 1);
|
|
517
517
|
var import_node_module = require("module");
|
|
518
518
|
|
|
519
519
|
// src/daemon.ts
|
|
520
520
|
init_cjs_shims();
|
|
521
|
-
var
|
|
522
|
-
var
|
|
521
|
+
var import_node_fs22 = require("fs");
|
|
522
|
+
var import_node_path37 = __toESM(require("path"), 1);
|
|
523
523
|
|
|
524
524
|
// src/graph.ts
|
|
525
525
|
init_cjs_shims();
|
|
@@ -979,19 +979,19 @@ function confidenceFromMix(edges, now = Date.now()) {
|
|
|
979
979
|
function longestIncomingWalk(graph, start, maxDepth) {
|
|
980
980
|
let best = { path: [start], edges: [] };
|
|
981
981
|
const visited = /* @__PURE__ */ new Set([start]);
|
|
982
|
-
function step(node,
|
|
983
|
-
if (
|
|
984
|
-
best = { path: [...
|
|
982
|
+
function step(node, path40, edges) {
|
|
983
|
+
if (path40.length > best.path.length) {
|
|
984
|
+
best = { path: [...path40], edges: [...edges] };
|
|
985
985
|
}
|
|
986
|
-
if (
|
|
986
|
+
if (path40.length - 1 >= maxDepth) return;
|
|
987
987
|
const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
|
|
988
988
|
for (const [srcId, edge] of incoming) {
|
|
989
989
|
if (visited.has(srcId)) continue;
|
|
990
990
|
visited.add(srcId);
|
|
991
|
-
|
|
991
|
+
path40.push(srcId);
|
|
992
992
|
edges.push(edge);
|
|
993
|
-
step(srcId,
|
|
994
|
-
|
|
993
|
+
step(srcId, path40, edges);
|
|
994
|
+
path40.pop();
|
|
995
995
|
edges.pop();
|
|
996
996
|
visited.delete(srcId);
|
|
997
997
|
}
|
|
@@ -1980,8 +1980,36 @@ var IGNORED_DIRS = /* @__PURE__ */ new Set([
|
|
|
1980
1980
|
".turbo",
|
|
1981
1981
|
"dist",
|
|
1982
1982
|
"build",
|
|
1983
|
-
".next"
|
|
1983
|
+
".next",
|
|
1984
|
+
// Python virtualenv shapes (issue #344). Walking into a venv pulls in the
|
|
1985
|
+
// entire CPython stdlib + every installed package as if it were first-party
|
|
1986
|
+
// service code — 20k+ files, none of which the user wrote. The shape names
|
|
1987
|
+
// cover the three common venv tools (`venv` / `python -m venv`,
|
|
1988
|
+
// `virtualenv`) and the tox + PEP 582 conventions.
|
|
1989
|
+
".venv",
|
|
1990
|
+
"venv",
|
|
1991
|
+
"__pypackages__",
|
|
1992
|
+
".tox",
|
|
1993
|
+
// `site-packages` shows up nested inside venvs that don't carry one of the
|
|
1994
|
+
// outer names (system Python on macOS, in-place pyenv layouts). Listing it
|
|
1995
|
+
// here means we stop the walk at the boundary even when the wrapper dir
|
|
1996
|
+
// wasn't recognisable.
|
|
1997
|
+
"site-packages"
|
|
1984
1998
|
]);
|
|
1999
|
+
var PYVENV_MARKER_CACHE = /* @__PURE__ */ new Map();
|
|
2000
|
+
async function isPythonVenvDir(dir) {
|
|
2001
|
+
const cached = PYVENV_MARKER_CACHE.get(dir);
|
|
2002
|
+
if (cached !== void 0) return cached;
|
|
2003
|
+
try {
|
|
2004
|
+
const stat = await import_node_fs4.promises.stat(import_node_path4.default.join(dir, "pyvenv.cfg"));
|
|
2005
|
+
const ok = stat.isFile();
|
|
2006
|
+
PYVENV_MARKER_CACHE.set(dir, ok);
|
|
2007
|
+
return ok;
|
|
2008
|
+
} catch {
|
|
2009
|
+
PYVENV_MARKER_CACHE.set(dir, false);
|
|
2010
|
+
return false;
|
|
2011
|
+
}
|
|
2012
|
+
}
|
|
1985
2013
|
function isConfigFile(name) {
|
|
1986
2014
|
const ext = import_node_path4.default.extname(name);
|
|
1987
2015
|
if (CONFIG_FILE_EXTENSIONS.has(ext)) return { match: true, fileType: ext.slice(1) };
|
|
@@ -2318,6 +2346,7 @@ async function walkDirs(start, scanPath, options, visit) {
|
|
|
2318
2346
|
const rel = import_node_path8.default.relative(scanPath, child).split(import_node_path8.default.sep).join("/");
|
|
2319
2347
|
if (rel && options.ig.ignores(rel + "/")) continue;
|
|
2320
2348
|
}
|
|
2349
|
+
if (await isPythonVenvDir(child)) continue;
|
|
2321
2350
|
await visit(child);
|
|
2322
2351
|
await recurse(child, depth + 1);
|
|
2323
2352
|
}
|
|
@@ -2596,7 +2625,9 @@ async function walkYamlFiles(start, depth = 0, max = 5) {
|
|
|
2596
2625
|
for (const entry2 of entries) {
|
|
2597
2626
|
if (entry2.isDirectory()) {
|
|
2598
2627
|
if (IGNORED_DIRS.has(entry2.name)) continue;
|
|
2599
|
-
|
|
2628
|
+
const child = import_node_path9.default.join(start, entry2.name);
|
|
2629
|
+
if (await isPythonVenvDir(child)) continue;
|
|
2630
|
+
out.push(...await walkYamlFiles(child, depth + 1, max));
|
|
2600
2631
|
} else if (entry2.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path9.default.extname(entry2.name))) {
|
|
2601
2632
|
out.push(import_node_path9.default.join(start, entry2.name));
|
|
2602
2633
|
}
|
|
@@ -3278,7 +3309,9 @@ async function walkConfigFiles(dir) {
|
|
|
3278
3309
|
for (const entry2 of entries) {
|
|
3279
3310
|
const full = import_node_path18.default.join(current, entry2.name);
|
|
3280
3311
|
if (entry2.isDirectory()) {
|
|
3281
|
-
if (
|
|
3312
|
+
if (IGNORED_DIRS.has(entry2.name)) continue;
|
|
3313
|
+
if (await isPythonVenvDir(full)) continue;
|
|
3314
|
+
await walk(full);
|
|
3282
3315
|
} else if (entry2.isFile() && isConfigFile(entry2.name).match) {
|
|
3283
3316
|
out.push(full);
|
|
3284
3317
|
}
|
|
@@ -3346,7 +3379,9 @@ async function walkSourceFiles(dir) {
|
|
|
3346
3379
|
for (const entry2 of entries) {
|
|
3347
3380
|
const full = import_node_path19.default.join(current, entry2.name);
|
|
3348
3381
|
if (entry2.isDirectory()) {
|
|
3349
|
-
if (
|
|
3382
|
+
if (IGNORED_DIRS.has(entry2.name)) continue;
|
|
3383
|
+
if (await isPythonVenvDir(full)) continue;
|
|
3384
|
+
await walk(full);
|
|
3350
3385
|
} else if (entry2.isFile() && SERVICE_FILE_EXTENSIONS.has(import_node_path19.default.extname(entry2.name))) {
|
|
3351
3386
|
out.push(full);
|
|
3352
3387
|
}
|
|
@@ -3982,7 +4017,9 @@ async function walkTfFiles(start, depth = 0, max = 5) {
|
|
|
3982
4017
|
for (const entry2 of entries) {
|
|
3983
4018
|
if (entry2.isDirectory()) {
|
|
3984
4019
|
if (IGNORED_DIRS.has(entry2.name) || entry2.name === ".terraform") continue;
|
|
3985
|
-
|
|
4020
|
+
const child = import_node_path27.default.join(start, entry2.name);
|
|
4021
|
+
if (await isPythonVenvDir(child)) continue;
|
|
4022
|
+
out.push(...await walkTfFiles(child, depth + 1, max));
|
|
3986
4023
|
} else if (entry2.isFile() && entry2.name.endsWith(".tf")) {
|
|
3987
4024
|
out.push(import_node_path27.default.join(start, entry2.name));
|
|
3988
4025
|
}
|
|
@@ -4030,7 +4067,9 @@ async function walkYamlFiles2(start, depth = 0, max = 5) {
|
|
|
4030
4067
|
for (const entry2 of entries) {
|
|
4031
4068
|
if (entry2.isDirectory()) {
|
|
4032
4069
|
if (IGNORED_DIRS.has(entry2.name)) continue;
|
|
4033
|
-
|
|
4070
|
+
const child = import_node_path28.default.join(start, entry2.name);
|
|
4071
|
+
if (await isPythonVenvDir(child)) continue;
|
|
4072
|
+
out.push(...await walkYamlFiles2(child, depth + 1, max));
|
|
4034
4073
|
} else if (entry2.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path28.default.extname(entry2.name))) {
|
|
4035
4074
|
out.push(import_node_path28.default.join(start, entry2.name));
|
|
4036
4075
|
}
|
|
@@ -4831,10 +4870,19 @@ function projectFromReq(req2) {
|
|
|
4831
4870
|
const params = req2.params;
|
|
4832
4871
|
return params.project ?? DEFAULT_PROJECT;
|
|
4833
4872
|
}
|
|
4834
|
-
function resolveProject(registry, req2, reply) {
|
|
4873
|
+
function resolveProject(registry, req2, reply, bootstrap) {
|
|
4835
4874
|
const name = projectFromReq(req2);
|
|
4836
4875
|
const ctx = registry.get(name);
|
|
4837
4876
|
if (!ctx) {
|
|
4877
|
+
const phase = bootstrap?.status(name);
|
|
4878
|
+
if (phase === "bootstrapping") {
|
|
4879
|
+
void reply.code(503).send({ ready: false, project: name, status: "bootstrapping" });
|
|
4880
|
+
return null;
|
|
4881
|
+
}
|
|
4882
|
+
if (phase === "broken") {
|
|
4883
|
+
void reply.code(503).send({ ready: false, project: name, status: "broken" });
|
|
4884
|
+
return null;
|
|
4885
|
+
}
|
|
4838
4886
|
void reply.code(404).send({ error: "project not found", project: name });
|
|
4839
4887
|
return null;
|
|
4840
4888
|
}
|
|
@@ -4864,36 +4912,38 @@ function buildLegacyRegistry(opts) {
|
|
|
4864
4912
|
function registerRoutes(scope, ctx) {
|
|
4865
4913
|
const { registry, startedAt, errorsPathFor, staleEventsPathFor } = ctx;
|
|
4866
4914
|
scope.get("/events", (req2, reply) => {
|
|
4867
|
-
const proj = resolveProject(registry, req2, reply);
|
|
4915
|
+
const proj = resolveProject(registry, req2, reply, ctx.bootstrap);
|
|
4868
4916
|
if (!proj) return;
|
|
4869
4917
|
handleSse(req2, reply, { project: proj.name });
|
|
4870
4918
|
});
|
|
4871
|
-
scope
|
|
4872
|
-
|
|
4873
|
-
|
|
4874
|
-
|
|
4875
|
-
|
|
4876
|
-
|
|
4877
|
-
|
|
4878
|
-
|
|
4879
|
-
|
|
4880
|
-
|
|
4881
|
-
|
|
4882
|
-
|
|
4883
|
-
|
|
4884
|
-
|
|
4885
|
-
|
|
4886
|
-
|
|
4887
|
-
|
|
4919
|
+
if (ctx.scope === "project") {
|
|
4920
|
+
scope.get("/health", async (req2, reply) => {
|
|
4921
|
+
const proj = resolveProject(registry, req2, reply, ctx.bootstrap);
|
|
4922
|
+
if (!proj) return;
|
|
4923
|
+
const uptimeMs = Date.now() - startedAt;
|
|
4924
|
+
return {
|
|
4925
|
+
ok: true,
|
|
4926
|
+
project: proj.name,
|
|
4927
|
+
uptimeMs,
|
|
4928
|
+
// Legacy fields kept additively. The web shell's StatusBar reads
|
|
4929
|
+
// nodeCount / edgeCount; ADR-061's HealthResponseSchema validates
|
|
4930
|
+
// the canonical triple and lets the extras pass through.
|
|
4931
|
+
uptime: Math.floor(uptimeMs / 1e3),
|
|
4932
|
+
nodeCount: proj.graph.order,
|
|
4933
|
+
edgeCount: proj.graph.size,
|
|
4934
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
|
|
4935
|
+
};
|
|
4936
|
+
});
|
|
4937
|
+
}
|
|
4888
4938
|
scope.get("/graph", async (req2, reply) => {
|
|
4889
|
-
const proj = resolveProject(registry, req2, reply);
|
|
4939
|
+
const proj = resolveProject(registry, req2, reply, ctx.bootstrap);
|
|
4890
4940
|
if (!proj) return;
|
|
4891
4941
|
return serializeGraph(proj.graph);
|
|
4892
4942
|
});
|
|
4893
4943
|
scope.get(
|
|
4894
4944
|
"/graph/node/:id",
|
|
4895
4945
|
async (req2, reply) => {
|
|
4896
|
-
const proj = resolveProject(registry, req2, reply);
|
|
4946
|
+
const proj = resolveProject(registry, req2, reply, ctx.bootstrap);
|
|
4897
4947
|
if (!proj) return;
|
|
4898
4948
|
const { id } = req2.params;
|
|
4899
4949
|
if (!proj.graph.hasNode(id)) {
|
|
@@ -4905,7 +4955,7 @@ function registerRoutes(scope, ctx) {
|
|
|
4905
4955
|
scope.get(
|
|
4906
4956
|
"/graph/edges/:id",
|
|
4907
4957
|
async (req2, reply) => {
|
|
4908
|
-
const proj = resolveProject(registry, req2, reply);
|
|
4958
|
+
const proj = resolveProject(registry, req2, reply, ctx.bootstrap);
|
|
4909
4959
|
if (!proj) return;
|
|
4910
4960
|
const { id } = req2.params;
|
|
4911
4961
|
if (!proj.graph.hasNode(id)) {
|
|
@@ -4917,7 +4967,7 @@ function registerRoutes(scope, ctx) {
|
|
|
4917
4967
|
}
|
|
4918
4968
|
);
|
|
4919
4969
|
scope.get("/graph/dependencies/:nodeId", async (req2, reply) => {
|
|
4920
|
-
const proj = resolveProject(registry, req2, reply);
|
|
4970
|
+
const proj = resolveProject(registry, req2, reply, ctx.bootstrap);
|
|
4921
4971
|
if (!proj) return;
|
|
4922
4972
|
const { nodeId } = req2.params;
|
|
4923
4973
|
if (!proj.graph.hasNode(nodeId)) {
|
|
@@ -4932,7 +4982,7 @@ function registerRoutes(scope, ctx) {
|
|
|
4932
4982
|
return getTransitiveDependencies(proj.graph, nodeId, depth);
|
|
4933
4983
|
});
|
|
4934
4984
|
scope.get("/graph/divergences", async (req2, reply) => {
|
|
4935
|
-
const proj = resolveProject(registry, req2, reply);
|
|
4985
|
+
const proj = resolveProject(registry, req2, reply, ctx.bootstrap);
|
|
4936
4986
|
if (!proj) return;
|
|
4937
4987
|
let typeFilter;
|
|
4938
4988
|
if (req2.query.type) {
|
|
@@ -4967,7 +5017,7 @@ function registerRoutes(scope, ctx) {
|
|
|
4967
5017
|
});
|
|
4968
5018
|
});
|
|
4969
5019
|
scope.get("/incidents", async (req2, reply) => {
|
|
4970
|
-
const proj = resolveProject(registry, req2, reply);
|
|
5020
|
+
const proj = resolveProject(registry, req2, reply, ctx.bootstrap);
|
|
4971
5021
|
if (!proj) return;
|
|
4972
5022
|
const epath = errorsPathFor(proj);
|
|
4973
5023
|
if (!epath) return { count: 0, total: 0, events: [] };
|
|
@@ -4979,7 +5029,7 @@ function registerRoutes(scope, ctx) {
|
|
|
4979
5029
|
return { count: sliced.length, total, events: sliced };
|
|
4980
5030
|
});
|
|
4981
5031
|
scope.get("/stale-events", async (req2, reply) => {
|
|
4982
|
-
const proj = resolveProject(registry, req2, reply);
|
|
5032
|
+
const proj = resolveProject(registry, req2, reply, ctx.bootstrap);
|
|
4983
5033
|
if (!proj) return;
|
|
4984
5034
|
const spath = staleEventsPathFor(proj);
|
|
4985
5035
|
if (!spath) return { count: 0, total: 0, events: [] };
|
|
@@ -4994,7 +5044,7 @@ function registerRoutes(scope, ctx) {
|
|
|
4994
5044
|
scope.get(
|
|
4995
5045
|
"/incidents/:nodeId",
|
|
4996
5046
|
async (req2, reply) => {
|
|
4997
|
-
const proj = resolveProject(registry, req2, reply);
|
|
5047
|
+
const proj = resolveProject(registry, req2, reply, ctx.bootstrap);
|
|
4998
5048
|
if (!proj) return;
|
|
4999
5049
|
const { nodeId } = req2.params;
|
|
5000
5050
|
if (!proj.graph.hasNode(nodeId)) {
|
|
@@ -5010,7 +5060,7 @@ function registerRoutes(scope, ctx) {
|
|
|
5010
5060
|
}
|
|
5011
5061
|
);
|
|
5012
5062
|
scope.get("/graph/root-cause/:nodeId", async (req2, reply) => {
|
|
5013
|
-
const proj = resolveProject(registry, req2, reply);
|
|
5063
|
+
const proj = resolveProject(registry, req2, reply, ctx.bootstrap);
|
|
5014
5064
|
if (!proj) return;
|
|
5015
5065
|
const { nodeId } = req2.params;
|
|
5016
5066
|
if (!proj.graph.hasNode(nodeId)) {
|
|
@@ -5030,7 +5080,7 @@ function registerRoutes(scope, ctx) {
|
|
|
5030
5080
|
return result;
|
|
5031
5081
|
});
|
|
5032
5082
|
scope.get("/graph/blast-radius/:nodeId", async (req2, reply) => {
|
|
5033
|
-
const proj = resolveProject(registry, req2, reply);
|
|
5083
|
+
const proj = resolveProject(registry, req2, reply, ctx.bootstrap);
|
|
5034
5084
|
if (!proj) return;
|
|
5035
5085
|
const { nodeId } = req2.params;
|
|
5036
5086
|
if (!proj.graph.hasNode(nodeId)) {
|
|
@@ -5043,7 +5093,7 @@ function registerRoutes(scope, ctx) {
|
|
|
5043
5093
|
return getBlastRadius(proj.graph, nodeId, depth);
|
|
5044
5094
|
});
|
|
5045
5095
|
scope.get("/search", async (req2, reply) => {
|
|
5046
|
-
const proj = resolveProject(registry, req2, reply);
|
|
5096
|
+
const proj = resolveProject(registry, req2, reply, ctx.bootstrap);
|
|
5047
5097
|
if (!proj) return;
|
|
5048
5098
|
const raw = (req2.query.q ?? "").trim();
|
|
5049
5099
|
if (!raw) return reply.code(400).send({ error: "query parameter `q` is required" });
|
|
@@ -5074,7 +5124,7 @@ function registerRoutes(scope, ctx) {
|
|
|
5074
5124
|
scope.get(
|
|
5075
5125
|
"/graph/diff",
|
|
5076
5126
|
async (req2, reply) => {
|
|
5077
|
-
const proj = resolveProject(registry, req2, reply);
|
|
5127
|
+
const proj = resolveProject(registry, req2, reply, ctx.bootstrap);
|
|
5078
5128
|
if (!proj) return;
|
|
5079
5129
|
const against = req2.query.against;
|
|
5080
5130
|
if (!against) {
|
|
@@ -5089,7 +5139,7 @@ function registerRoutes(scope, ctx) {
|
|
|
5089
5139
|
}
|
|
5090
5140
|
);
|
|
5091
5141
|
scope.post("/snapshot", async (req2, reply) => {
|
|
5092
|
-
const proj = resolveProject(registry, req2, reply);
|
|
5142
|
+
const proj = resolveProject(registry, req2, reply, ctx.bootstrap);
|
|
5093
5143
|
if (!proj) return;
|
|
5094
5144
|
const body = req2.body;
|
|
5095
5145
|
if (!body || typeof body !== "object" || !body.snapshot) {
|
|
@@ -5118,7 +5168,7 @@ function registerRoutes(scope, ctx) {
|
|
|
5118
5168
|
}
|
|
5119
5169
|
});
|
|
5120
5170
|
scope.post("/graph/scan", async (req2, reply) => {
|
|
5121
|
-
const proj = resolveProject(registry, req2, reply);
|
|
5171
|
+
const proj = resolveProject(registry, req2, reply, ctx.bootstrap);
|
|
5122
5172
|
if (!proj) return;
|
|
5123
5173
|
if (!proj.scanPath) {
|
|
5124
5174
|
return reply.code(409).send({ error: "scan path not configured for this project", project: proj.name });
|
|
@@ -5134,7 +5184,7 @@ function registerRoutes(scope, ctx) {
|
|
|
5134
5184
|
};
|
|
5135
5185
|
});
|
|
5136
5186
|
scope.get("/policies", async (req2, reply) => {
|
|
5137
|
-
const proj = resolveProject(registry, req2, reply);
|
|
5187
|
+
const proj = resolveProject(registry, req2, reply, ctx.bootstrap);
|
|
5138
5188
|
if (!proj) return;
|
|
5139
5189
|
const policyPath = ctx.policyFilePathFor(proj);
|
|
5140
5190
|
if (!policyPath) {
|
|
@@ -5151,7 +5201,7 @@ function registerRoutes(scope, ctx) {
|
|
|
5151
5201
|
}
|
|
5152
5202
|
});
|
|
5153
5203
|
scope.get("/policies/violations", async (req2, reply) => {
|
|
5154
|
-
const proj = resolveProject(registry, req2, reply);
|
|
5204
|
+
const proj = resolveProject(registry, req2, reply, ctx.bootstrap);
|
|
5155
5205
|
if (!proj) return;
|
|
5156
5206
|
const log = new PolicyViolationsLog(proj.paths.policyViolationsPath);
|
|
5157
5207
|
let violations = await log.readAll();
|
|
@@ -5171,7 +5221,7 @@ function registerRoutes(scope, ctx) {
|
|
|
5171
5221
|
return { violations };
|
|
5172
5222
|
});
|
|
5173
5223
|
scope.post("/policies/check", async (req2, reply) => {
|
|
5174
|
-
const proj = resolveProject(registry, req2, reply);
|
|
5224
|
+
const proj = resolveProject(registry, req2, reply, ctx.bootstrap);
|
|
5175
5225
|
if (!proj) return;
|
|
5176
5226
|
const parsed = import_types22.PoliciesCheckBodySchema.safeParse(req2.body ?? {});
|
|
5177
5227
|
if (!parsed.success) {
|
|
@@ -5242,10 +5292,36 @@ async function buildApi(opts) {
|
|
|
5242
5292
|
const routeCtx = {
|
|
5243
5293
|
registry,
|
|
5244
5294
|
startedAt,
|
|
5295
|
+
scope: "root",
|
|
5245
5296
|
errorsPathFor,
|
|
5246
5297
|
staleEventsPathFor,
|
|
5247
|
-
policyFilePathFor
|
|
5298
|
+
policyFilePathFor,
|
|
5299
|
+
bootstrap: opts.bootstrap
|
|
5248
5300
|
};
|
|
5301
|
+
app.get("/health", async () => {
|
|
5302
|
+
const uptimeMs = Date.now() - startedAt;
|
|
5303
|
+
const bootstrapList = opts.bootstrap?.list() ?? [];
|
|
5304
|
+
const byName = new Map(bootstrapList.map((p) => [p.name, p]));
|
|
5305
|
+
const names = /* @__PURE__ */ new Set([
|
|
5306
|
+
...registry.list(),
|
|
5307
|
+
...bootstrapList.map((p) => p.name)
|
|
5308
|
+
]);
|
|
5309
|
+
const projects = [...names].sort().map((name) => {
|
|
5310
|
+
const proj = registry.get(name);
|
|
5311
|
+
const tracked = byName.get(name);
|
|
5312
|
+
return {
|
|
5313
|
+
name,
|
|
5314
|
+
nodeCount: proj?.graph.order ?? 0,
|
|
5315
|
+
edgeCount: proj?.graph.size ?? 0,
|
|
5316
|
+
...tracked ? { status: tracked.status, elapsedMs: tracked.elapsedMs } : {}
|
|
5317
|
+
};
|
|
5318
|
+
});
|
|
5319
|
+
return {
|
|
5320
|
+
ok: true,
|
|
5321
|
+
uptimeMs,
|
|
5322
|
+
projects
|
|
5323
|
+
};
|
|
5324
|
+
});
|
|
5249
5325
|
app.get("/projects", async (_req, reply) => {
|
|
5250
5326
|
try {
|
|
5251
5327
|
return await listProjects();
|
|
@@ -5273,7 +5349,7 @@ async function buildApi(opts) {
|
|
|
5273
5349
|
registerRoutes(app, routeCtx);
|
|
5274
5350
|
await app.register(
|
|
5275
5351
|
async (scope) => {
|
|
5276
|
-
registerRoutes(scope, routeCtx);
|
|
5352
|
+
registerRoutes(scope, { ...routeCtx, scope: "project" });
|
|
5277
5353
|
},
|
|
5278
5354
|
{ prefix: "/projects/:project" }
|
|
5279
5355
|
);
|
|
@@ -5283,12 +5359,35 @@ async function buildApi(opts) {
|
|
|
5283
5359
|
// src/daemon.ts
|
|
5284
5360
|
init_otel();
|
|
5285
5361
|
init_auth();
|
|
5362
|
+
|
|
5363
|
+
// src/unrouted.ts
|
|
5364
|
+
init_cjs_shims();
|
|
5365
|
+
var import_node_fs21 = require("fs");
|
|
5366
|
+
var import_node_path36 = __toESM(require("path"), 1);
|
|
5367
|
+
function buildUnroutedSpanRecord(serviceName, traceId, now = /* @__PURE__ */ new Date()) {
|
|
5368
|
+
return {
|
|
5369
|
+
timestamp: now.toISOString(),
|
|
5370
|
+
reason: "no-project-match",
|
|
5371
|
+
service_name: serviceName ?? null,
|
|
5372
|
+
traceId: traceId ?? null
|
|
5373
|
+
};
|
|
5374
|
+
}
|
|
5375
|
+
async function appendUnroutedSpan(neatHome3, record) {
|
|
5376
|
+
const target = import_node_path36.default.join(neatHome3, "errors.ndjson");
|
|
5377
|
+
await import_node_fs21.promises.mkdir(neatHome3, { recursive: true });
|
|
5378
|
+
await import_node_fs21.promises.appendFile(target, JSON.stringify(record) + "\n", "utf8");
|
|
5379
|
+
}
|
|
5380
|
+
function unroutedErrorsPath(neatHome3) {
|
|
5381
|
+
return import_node_path36.default.join(neatHome3, "errors.ndjson");
|
|
5382
|
+
}
|
|
5383
|
+
|
|
5384
|
+
// src/daemon.ts
|
|
5286
5385
|
function neatHomeFor(opts) {
|
|
5287
|
-
if (opts.neatHome && opts.neatHome.length > 0) return
|
|
5386
|
+
if (opts.neatHome && opts.neatHome.length > 0) return import_node_path37.default.resolve(opts.neatHome);
|
|
5288
5387
|
const env = process.env.NEAT_HOME;
|
|
5289
|
-
if (env && env.length > 0) return
|
|
5388
|
+
if (env && env.length > 0) return import_node_path37.default.resolve(env);
|
|
5290
5389
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
5291
|
-
return
|
|
5390
|
+
return import_node_path37.default.join(home, ".neat");
|
|
5292
5391
|
}
|
|
5293
5392
|
function routeSpanToProject(serviceName, projects) {
|
|
5294
5393
|
if (!serviceName) return DEFAULT_PROJECT;
|
|
@@ -5323,9 +5422,9 @@ function isTokenContained(needle, haystack) {
|
|
|
5323
5422
|
return tokens.includes(needle);
|
|
5324
5423
|
}
|
|
5325
5424
|
async function bootstrapProject(entry2) {
|
|
5326
|
-
const paths = pathsForProject(entry2.name,
|
|
5425
|
+
const paths = pathsForProject(entry2.name, import_node_path37.default.join(entry2.path, "neat-out"));
|
|
5327
5426
|
try {
|
|
5328
|
-
const stat = await
|
|
5427
|
+
const stat = await import_node_fs22.promises.stat(entry2.path);
|
|
5329
5428
|
if (!stat.isDirectory()) {
|
|
5330
5429
|
throw new Error(`registered path ${entry2.path} is not a directory`);
|
|
5331
5430
|
}
|
|
@@ -5380,27 +5479,30 @@ function resolveOtlpPort(opts) {
|
|
|
5380
5479
|
}
|
|
5381
5480
|
return 4318;
|
|
5382
5481
|
}
|
|
5383
|
-
function resolveHost(opts) {
|
|
5482
|
+
function resolveHost(opts, authTokenSet) {
|
|
5384
5483
|
if (opts.host && opts.host.length > 0) return opts.host;
|
|
5385
5484
|
const env = process.env.HOST;
|
|
5386
5485
|
if (env && env.length > 0) return env;
|
|
5486
|
+
if (!authTokenSet) return "127.0.0.1";
|
|
5387
5487
|
return "0.0.0.0";
|
|
5388
5488
|
}
|
|
5389
5489
|
async function startDaemon(opts = {}) {
|
|
5390
5490
|
const home = neatHomeFor(opts);
|
|
5391
5491
|
const regPath = registryPath();
|
|
5392
5492
|
try {
|
|
5393
|
-
await
|
|
5493
|
+
await import_node_fs22.promises.access(regPath);
|
|
5394
5494
|
} catch {
|
|
5395
5495
|
throw new Error(
|
|
5396
5496
|
`neatd: registry not found at ${regPath}. Run \`neat init <path>\` to register a project before starting the daemon.`
|
|
5397
5497
|
);
|
|
5398
5498
|
}
|
|
5399
|
-
const pidPath =
|
|
5499
|
+
const pidPath = import_node_path37.default.join(home, "neatd.pid");
|
|
5400
5500
|
await writeAtomically(pidPath, `${process.pid}
|
|
5401
5501
|
`);
|
|
5402
5502
|
const slots = /* @__PURE__ */ new Map();
|
|
5403
5503
|
const registry = new Projects();
|
|
5504
|
+
const bootstrapStatus = /* @__PURE__ */ new Map();
|
|
5505
|
+
const bootstrapStartedAt = /* @__PURE__ */ new Map();
|
|
5404
5506
|
const DROP_WARN_INTERVAL_MS = 6e4;
|
|
5405
5507
|
const lastDropWarnAt = /* @__PURE__ */ new Map();
|
|
5406
5508
|
function warnDroppedSpan(project, reason) {
|
|
@@ -5412,6 +5514,22 @@ async function startDaemon(opts = {}) {
|
|
|
5412
5514
|
`[neatd] dropping span for project "${project}" \u2014 project status: broken (${reason}). Run \`neatd reload\` to retry bootstrap.`
|
|
5413
5515
|
);
|
|
5414
5516
|
}
|
|
5517
|
+
const unroutedPath = unroutedErrorsPath(home);
|
|
5518
|
+
const lastUnroutedWarnAt = /* @__PURE__ */ new Map();
|
|
5519
|
+
async function recordUnroutedSpan(serviceName, traceId) {
|
|
5520
|
+
const key = serviceName ?? "<missing>";
|
|
5521
|
+
const now = Date.now();
|
|
5522
|
+
try {
|
|
5523
|
+
await appendUnroutedSpan(home, buildUnroutedSpanRecord(serviceName, traceId, new Date(now)));
|
|
5524
|
+
} catch {
|
|
5525
|
+
}
|
|
5526
|
+
const prev = lastUnroutedWarnAt.get(key) ?? 0;
|
|
5527
|
+
if (now - prev < DROP_WARN_INTERVAL_MS) return;
|
|
5528
|
+
lastUnroutedWarnAt.set(key, now);
|
|
5529
|
+
console.warn(
|
|
5530
|
+
`[neatd] dropping span \u2014 service.name "${key}" matches no registered project and no \`default\` project exists. See ${unroutedPath}.`
|
|
5531
|
+
);
|
|
5532
|
+
}
|
|
5415
5533
|
function upsertRegistryFromSlot(slot) {
|
|
5416
5534
|
if (slot.status !== "active") return;
|
|
5417
5535
|
registry.set(slot.entry.name, {
|
|
@@ -5440,34 +5558,43 @@ async function startDaemon(opts = {}) {
|
|
|
5440
5558
|
return slots.get(entry2.name);
|
|
5441
5559
|
}
|
|
5442
5560
|
}
|
|
5561
|
+
async function bootstrapOne(entry2) {
|
|
5562
|
+
bootstrapStatus.set(entry2.name, "bootstrapping");
|
|
5563
|
+
bootstrapStartedAt.set(entry2.name, Date.now());
|
|
5564
|
+
try {
|
|
5565
|
+
const slot = await bootstrapProject(entry2);
|
|
5566
|
+
slots.set(entry2.name, slot);
|
|
5567
|
+
upsertRegistryFromSlot(slot);
|
|
5568
|
+
bootstrapStatus.set(entry2.name, slot.status === "broken" ? "broken" : "active");
|
|
5569
|
+
if (slot.status === "broken") {
|
|
5570
|
+
console.warn(`neatd: project "${entry2.name}" broken \u2014 ${slot.errorReason}`);
|
|
5571
|
+
} else {
|
|
5572
|
+
console.log(`neatd: project "${entry2.name}" active (${entry2.path})`);
|
|
5573
|
+
}
|
|
5574
|
+
} catch (err) {
|
|
5575
|
+
bootstrapStatus.set(entry2.name, "broken");
|
|
5576
|
+
console.warn(
|
|
5577
|
+
`neatd: project "${entry2.name}" failed to bootstrap \u2014 ${err.message}`
|
|
5578
|
+
);
|
|
5579
|
+
await setStatus(entry2.name, "broken").catch(() => {
|
|
5580
|
+
});
|
|
5581
|
+
}
|
|
5582
|
+
}
|
|
5443
5583
|
async function loadAll() {
|
|
5444
5584
|
const projects = await listProjects();
|
|
5445
5585
|
const seen = /* @__PURE__ */ new Set();
|
|
5586
|
+
const pending = [];
|
|
5446
5587
|
for (const entry2 of projects) {
|
|
5447
5588
|
seen.add(entry2.name);
|
|
5448
5589
|
const existing = slots.get(entry2.name);
|
|
5449
5590
|
if (existing) {
|
|
5450
5591
|
if (existing.status === "broken") {
|
|
5451
|
-
|
|
5592
|
+
pending.push(tryRecoverSlot(entry2).then(() => {
|
|
5593
|
+
}));
|
|
5452
5594
|
}
|
|
5453
5595
|
continue;
|
|
5454
5596
|
}
|
|
5455
|
-
|
|
5456
|
-
const slot = await bootstrapProject(entry2);
|
|
5457
|
-
slots.set(entry2.name, slot);
|
|
5458
|
-
upsertRegistryFromSlot(slot);
|
|
5459
|
-
if (slot.status === "broken") {
|
|
5460
|
-
console.warn(`neatd: project "${entry2.name}" broken \u2014 ${slot.errorReason}`);
|
|
5461
|
-
} else {
|
|
5462
|
-
console.log(`neatd: project "${entry2.name}" active (${entry2.path})`);
|
|
5463
|
-
}
|
|
5464
|
-
} catch (err) {
|
|
5465
|
-
console.warn(
|
|
5466
|
-
`neatd: project "${entry2.name}" failed to bootstrap \u2014 ${err.message}`
|
|
5467
|
-
);
|
|
5468
|
-
await setStatus(entry2.name, "broken").catch(() => {
|
|
5469
|
-
});
|
|
5470
|
-
}
|
|
5597
|
+
pending.push(bootstrapOne(entry2));
|
|
5471
5598
|
}
|
|
5472
5599
|
for (const [name, slot] of [...slots.entries()]) {
|
|
5473
5600
|
if (seen.has(name)) continue;
|
|
@@ -5476,27 +5603,45 @@ async function startDaemon(opts = {}) {
|
|
|
5476
5603
|
} catch {
|
|
5477
5604
|
}
|
|
5478
5605
|
slots.delete(name);
|
|
5606
|
+
bootstrapStatus.delete(name);
|
|
5607
|
+
bootstrapStartedAt.delete(name);
|
|
5479
5608
|
console.log(`neatd: project "${name}" removed from registry \u2014 stopped`);
|
|
5480
5609
|
}
|
|
5610
|
+
await Promise.allSettled(pending);
|
|
5611
|
+
}
|
|
5612
|
+
const initialEntries = await listProjects().catch(() => []);
|
|
5613
|
+
for (const entry2 of initialEntries) {
|
|
5614
|
+
bootstrapStatus.set(entry2.name, "bootstrapping");
|
|
5615
|
+
bootstrapStartedAt.set(entry2.name, Date.now());
|
|
5481
5616
|
}
|
|
5482
|
-
await loadAll();
|
|
5483
5617
|
const bind = opts.bindListeners !== false;
|
|
5484
5618
|
let restApp = null;
|
|
5485
5619
|
let otlpApp = null;
|
|
5486
5620
|
let restAddress = "";
|
|
5487
5621
|
let otlpAddress = "";
|
|
5488
5622
|
if (bind) {
|
|
5489
|
-
const
|
|
5623
|
+
const auth = readAuthEnv();
|
|
5624
|
+
const host = resolveHost(opts, Boolean(auth.authToken));
|
|
5490
5625
|
const restPort = resolveRestPort(opts);
|
|
5491
5626
|
const otlpPort = resolveOtlpPort(opts);
|
|
5492
|
-
const auth = readAuthEnv();
|
|
5493
5627
|
assertBindAuthority(host, auth.authToken);
|
|
5494
5628
|
try {
|
|
5495
5629
|
restApp = await buildApi({
|
|
5496
5630
|
projects: registry,
|
|
5497
5631
|
authToken: auth.authToken,
|
|
5498
5632
|
trustProxy: auth.trustProxy,
|
|
5499
|
-
publicRead: auth.publicRead
|
|
5633
|
+
publicRead: auth.publicRead,
|
|
5634
|
+
bootstrap: {
|
|
5635
|
+
status: (name) => bootstrapStatus.get(name),
|
|
5636
|
+
list: () => {
|
|
5637
|
+
const now = Date.now();
|
|
5638
|
+
return [...bootstrapStatus.entries()].map(([name, status2]) => ({
|
|
5639
|
+
name,
|
|
5640
|
+
status: status2,
|
|
5641
|
+
elapsedMs: now - (bootstrapStartedAt.get(name) ?? now)
|
|
5642
|
+
}));
|
|
5643
|
+
}
|
|
5644
|
+
}
|
|
5500
5645
|
});
|
|
5501
5646
|
restAddress = await restApp.listen({ port: restPort, host });
|
|
5502
5647
|
console.log(`neatd: REST listening on ${restAddress}`);
|
|
@@ -5509,17 +5654,20 @@ async function startDaemon(opts = {}) {
|
|
|
5509
5654
|
}
|
|
5510
5655
|
if (restApp) await restApp.close().catch(() => {
|
|
5511
5656
|
});
|
|
5512
|
-
await
|
|
5657
|
+
await import_node_fs22.promises.unlink(pidPath).catch(() => {
|
|
5513
5658
|
});
|
|
5514
5659
|
throw new Error(
|
|
5515
5660
|
`neatd: failed to bind REST on port ${restPort} \u2014 ${err.message}`
|
|
5516
5661
|
);
|
|
5517
5662
|
}
|
|
5518
|
-
async function resolveTargetSlot(serviceName) {
|
|
5663
|
+
async function resolveTargetSlot(serviceName, traceId) {
|
|
5519
5664
|
const liveEntries = await listProjects().catch(() => []);
|
|
5520
5665
|
const target = routeSpanToProject(serviceName, liveEntries);
|
|
5521
5666
|
let slot = slots.get(target) ?? slots.get(DEFAULT_PROJECT);
|
|
5522
|
-
if (!slot)
|
|
5667
|
+
if (!slot) {
|
|
5668
|
+
await recordUnroutedSpan(serviceName, traceId);
|
|
5669
|
+
return null;
|
|
5670
|
+
}
|
|
5523
5671
|
if (slot.status === "broken") {
|
|
5524
5672
|
const entry2 = liveEntries.find((e) => e.name === slot.entry.name);
|
|
5525
5673
|
if (entry2) {
|
|
@@ -5537,7 +5685,7 @@ async function startDaemon(opts = {}) {
|
|
|
5537
5685
|
authToken: auth.otelToken,
|
|
5538
5686
|
trustProxy: auth.trustProxy,
|
|
5539
5687
|
onSpan: async (span) => {
|
|
5540
|
-
const slot = await resolveTargetSlot(span.service);
|
|
5688
|
+
const slot = await resolveTargetSlot(span.service, span.traceId);
|
|
5541
5689
|
if (!slot) return;
|
|
5542
5690
|
await handleSpan(
|
|
5543
5691
|
{
|
|
@@ -5551,7 +5699,7 @@ async function startDaemon(opts = {}) {
|
|
|
5551
5699
|
);
|
|
5552
5700
|
},
|
|
5553
5701
|
onErrorSpanSync: async (span) => {
|
|
5554
|
-
const slot = await resolveTargetSlot(span.service);
|
|
5702
|
+
const slot = await resolveTargetSlot(span.service, span.traceId);
|
|
5555
5703
|
if (!slot) return;
|
|
5556
5704
|
await makeErrorSpanWriter(slot.paths.errorsPath)(span);
|
|
5557
5705
|
}
|
|
@@ -5569,14 +5717,17 @@ async function startDaemon(opts = {}) {
|
|
|
5569
5717
|
});
|
|
5570
5718
|
if (otlpApp) await otlpApp.close().catch(() => {
|
|
5571
5719
|
});
|
|
5572
|
-
await
|
|
5720
|
+
await import_node_fs22.promises.unlink(pidPath).catch(() => {
|
|
5573
5721
|
});
|
|
5574
5722
|
throw new Error(
|
|
5575
5723
|
`neatd: failed to bind OTLP on port ${otlpPort} \u2014 ${err.message}`
|
|
5576
5724
|
);
|
|
5577
5725
|
}
|
|
5578
5726
|
}
|
|
5579
|
-
|
|
5727
|
+
const initialBootstrap = loadAll().catch((err) => {
|
|
5728
|
+
console.warn(`neatd: initial bootstrap pass failed \u2014 ${err.message}`);
|
|
5729
|
+
});
|
|
5730
|
+
let reloading = initialBootstrap;
|
|
5580
5731
|
const reload = async () => {
|
|
5581
5732
|
if (reloading) return reloading;
|
|
5582
5733
|
reloading = (async () => {
|
|
@@ -5588,6 +5739,20 @@ async function startDaemon(opts = {}) {
|
|
|
5588
5739
|
})();
|
|
5589
5740
|
return reloading;
|
|
5590
5741
|
};
|
|
5742
|
+
void initialBootstrap.finally(() => {
|
|
5743
|
+
if (reloading === initialBootstrap) reloading = null;
|
|
5744
|
+
});
|
|
5745
|
+
const tracker = {
|
|
5746
|
+
status: (name) => bootstrapStatus.get(name),
|
|
5747
|
+
list: () => {
|
|
5748
|
+
const now = Date.now();
|
|
5749
|
+
return [...bootstrapStatus.entries()].map(([name, status2]) => ({
|
|
5750
|
+
name,
|
|
5751
|
+
status: status2,
|
|
5752
|
+
elapsedMs: now - (bootstrapStartedAt.get(name) ?? now)
|
|
5753
|
+
}));
|
|
5754
|
+
}
|
|
5755
|
+
};
|
|
5591
5756
|
const sighupHandler = () => {
|
|
5592
5757
|
void reload().catch((err) => {
|
|
5593
5758
|
console.warn(`neatd: SIGHUP reload failed \u2014 ${err.message}`);
|
|
@@ -5609,10 +5774,19 @@ async function startDaemon(opts = {}) {
|
|
|
5609
5774
|
} catch {
|
|
5610
5775
|
}
|
|
5611
5776
|
}
|
|
5612
|
-
await
|
|
5777
|
+
await import_node_fs22.promises.unlink(pidPath).catch(() => {
|
|
5613
5778
|
});
|
|
5614
5779
|
};
|
|
5615
|
-
return {
|
|
5780
|
+
return {
|
|
5781
|
+
slots,
|
|
5782
|
+
reload,
|
|
5783
|
+
stop,
|
|
5784
|
+
pidPath,
|
|
5785
|
+
restAddress,
|
|
5786
|
+
otlpAddress,
|
|
5787
|
+
bootstrap: tracker,
|
|
5788
|
+
initialBootstrap
|
|
5789
|
+
};
|
|
5616
5790
|
}
|
|
5617
5791
|
|
|
5618
5792
|
// src/neatd.ts
|
|
@@ -5622,7 +5796,7 @@ init_auth();
|
|
|
5622
5796
|
init_cjs_shims();
|
|
5623
5797
|
var import_node_child_process = require("child_process");
|
|
5624
5798
|
var import_node_net = __toESM(require("net"), 1);
|
|
5625
|
-
var
|
|
5799
|
+
var import_node_path38 = __toESM(require("path"), 1);
|
|
5626
5800
|
var DEFAULT_WEB_PORT = 6328;
|
|
5627
5801
|
async function assertPortFree(port) {
|
|
5628
5802
|
await new Promise((resolve, reject) => {
|
|
@@ -5650,10 +5824,10 @@ function resolveWebPackageDir() {
|
|
|
5650
5824
|
eval("require")
|
|
5651
5825
|
);
|
|
5652
5826
|
const pkgJsonPath = req.resolve("@neat.is/web/package.json");
|
|
5653
|
-
return
|
|
5827
|
+
return import_node_path38.default.dirname(pkgJsonPath);
|
|
5654
5828
|
}
|
|
5655
5829
|
function resolveStandaloneServerEntry(webDir) {
|
|
5656
|
-
return
|
|
5830
|
+
return import_node_path38.default.join(webDir, ".next/standalone/packages/web/server.js");
|
|
5657
5831
|
}
|
|
5658
5832
|
async function spawnWebUI(restPort) {
|
|
5659
5833
|
const portRaw = process.env.NEAT_WEB_PORT;
|
|
@@ -5678,7 +5852,7 @@ async function spawnWebUI(restPort) {
|
|
|
5678
5852
|
NEAT_API_URL: process.env.NEAT_API_URL ?? `http://localhost:${restPort}`
|
|
5679
5853
|
};
|
|
5680
5854
|
const child = (0, import_node_child_process.spawn)(process.execPath, [serverEntry], {
|
|
5681
|
-
cwd:
|
|
5855
|
+
cwd: import_node_path38.default.dirname(serverEntry),
|
|
5682
5856
|
env,
|
|
5683
5857
|
stdio: ["ignore", "inherit", "inherit"],
|
|
5684
5858
|
detached: false
|
|
@@ -5791,14 +5965,14 @@ function localVersion() {
|
|
|
5791
5965
|
}
|
|
5792
5966
|
function neatHome2() {
|
|
5793
5967
|
if (process.env.NEAT_HOME && process.env.NEAT_HOME.length > 0) {
|
|
5794
|
-
return
|
|
5968
|
+
return import_node_path39.default.resolve(process.env.NEAT_HOME);
|
|
5795
5969
|
}
|
|
5796
5970
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
5797
|
-
return
|
|
5971
|
+
return import_node_path39.default.join(home, ".neat");
|
|
5798
5972
|
}
|
|
5799
5973
|
async function readPid() {
|
|
5800
5974
|
try {
|
|
5801
|
-
const raw = await
|
|
5975
|
+
const raw = await import_node_fs23.promises.readFile(import_node_path39.default.join(neatHome2(), "neatd.pid"), "utf8");
|
|
5802
5976
|
const n = Number.parseInt(raw.trim(), 10);
|
|
5803
5977
|
return Number.isFinite(n) ? n : null;
|
|
5804
5978
|
} catch {
|