@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.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
startDaemon
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-CB2UK4EH.js";
|
|
5
5
|
import {
|
|
6
6
|
listProjects,
|
|
7
7
|
registryPath
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-NTQHMXWE.js";
|
|
9
9
|
import {
|
|
10
10
|
BindAuthorityError,
|
|
11
11
|
__require
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-KYRIQIPG.js";
|
|
13
13
|
|
|
14
14
|
// src/neatd.ts
|
|
15
15
|
import { promises as fs } from "fs";
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
reshapeGrpcRequest,
|
|
3
3
|
startOtelGrpcReceiver
|
|
4
|
-
} from "./chunk-
|
|
5
|
-
import "./chunk-
|
|
4
|
+
} from "./chunk-D5PIJFBE.js";
|
|
5
|
+
import "./chunk-KYRIQIPG.js";
|
|
6
6
|
export {
|
|
7
7
|
reshapeGrpcRequest,
|
|
8
8
|
startOtelGrpcReceiver
|
|
9
9
|
};
|
|
10
|
-
//# sourceMappingURL=otel-grpc-
|
|
10
|
+
//# sourceMappingURL=otel-grpc-QTX2YQJZ.js.map
|
package/dist/server.cjs
CHANGED
|
@@ -2278,8 +2278,36 @@ var IGNORED_DIRS = /* @__PURE__ */ new Set([
|
|
|
2278
2278
|
".turbo",
|
|
2279
2279
|
"dist",
|
|
2280
2280
|
"build",
|
|
2281
|
-
".next"
|
|
2281
|
+
".next",
|
|
2282
|
+
// Python virtualenv shapes (issue #344). Walking into a venv pulls in the
|
|
2283
|
+
// entire CPython stdlib + every installed package as if it were first-party
|
|
2284
|
+
// service code — 20k+ files, none of which the user wrote. The shape names
|
|
2285
|
+
// cover the three common venv tools (`venv` / `python -m venv`,
|
|
2286
|
+
// `virtualenv`) and the tox + PEP 582 conventions.
|
|
2287
|
+
".venv",
|
|
2288
|
+
"venv",
|
|
2289
|
+
"__pypackages__",
|
|
2290
|
+
".tox",
|
|
2291
|
+
// `site-packages` shows up nested inside venvs that don't carry one of the
|
|
2292
|
+
// outer names (system Python on macOS, in-place pyenv layouts). Listing it
|
|
2293
|
+
// here means we stop the walk at the boundary even when the wrapper dir
|
|
2294
|
+
// wasn't recognisable.
|
|
2295
|
+
"site-packages"
|
|
2282
2296
|
]);
|
|
2297
|
+
var PYVENV_MARKER_CACHE = /* @__PURE__ */ new Map();
|
|
2298
|
+
async function isPythonVenvDir(dir) {
|
|
2299
|
+
const cached = PYVENV_MARKER_CACHE.get(dir);
|
|
2300
|
+
if (cached !== void 0) return cached;
|
|
2301
|
+
try {
|
|
2302
|
+
const stat = await import_node_fs4.promises.stat(import_node_path4.default.join(dir, "pyvenv.cfg"));
|
|
2303
|
+
const ok = stat.isFile();
|
|
2304
|
+
PYVENV_MARKER_CACHE.set(dir, ok);
|
|
2305
|
+
return ok;
|
|
2306
|
+
} catch {
|
|
2307
|
+
PYVENV_MARKER_CACHE.set(dir, false);
|
|
2308
|
+
return false;
|
|
2309
|
+
}
|
|
2310
|
+
}
|
|
2283
2311
|
function isConfigFile(name) {
|
|
2284
2312
|
const ext = import_node_path4.default.extname(name);
|
|
2285
2313
|
if (CONFIG_FILE_EXTENSIONS.has(ext)) return { match: true, fileType: ext.slice(1) };
|
|
@@ -2616,6 +2644,7 @@ async function walkDirs(start, scanPath, options, visit) {
|
|
|
2616
2644
|
const rel = import_node_path8.default.relative(scanPath, child).split(import_node_path8.default.sep).join("/");
|
|
2617
2645
|
if (rel && options.ig.ignores(rel + "/")) continue;
|
|
2618
2646
|
}
|
|
2647
|
+
if (await isPythonVenvDir(child)) continue;
|
|
2619
2648
|
await visit(child);
|
|
2620
2649
|
await recurse(child, depth + 1);
|
|
2621
2650
|
}
|
|
@@ -2894,7 +2923,9 @@ async function walkYamlFiles(start, depth = 0, max = 5) {
|
|
|
2894
2923
|
for (const entry of entries) {
|
|
2895
2924
|
if (entry.isDirectory()) {
|
|
2896
2925
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
2897
|
-
|
|
2926
|
+
const child = import_node_path9.default.join(start, entry.name);
|
|
2927
|
+
if (await isPythonVenvDir(child)) continue;
|
|
2928
|
+
out.push(...await walkYamlFiles(child, depth + 1, max));
|
|
2898
2929
|
} else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path9.default.extname(entry.name))) {
|
|
2899
2930
|
out.push(import_node_path9.default.join(start, entry.name));
|
|
2900
2931
|
}
|
|
@@ -3576,7 +3607,9 @@ async function walkConfigFiles(dir) {
|
|
|
3576
3607
|
for (const entry of entries) {
|
|
3577
3608
|
const full = import_node_path18.default.join(current, entry.name);
|
|
3578
3609
|
if (entry.isDirectory()) {
|
|
3579
|
-
if (
|
|
3610
|
+
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
3611
|
+
if (await isPythonVenvDir(full)) continue;
|
|
3612
|
+
await walk(full);
|
|
3580
3613
|
} else if (entry.isFile() && isConfigFile(entry.name).match) {
|
|
3581
3614
|
out.push(full);
|
|
3582
3615
|
}
|
|
@@ -3644,7 +3677,9 @@ async function walkSourceFiles(dir) {
|
|
|
3644
3677
|
for (const entry of entries) {
|
|
3645
3678
|
const full = import_node_path19.default.join(current, entry.name);
|
|
3646
3679
|
if (entry.isDirectory()) {
|
|
3647
|
-
if (
|
|
3680
|
+
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
3681
|
+
if (await isPythonVenvDir(full)) continue;
|
|
3682
|
+
await walk(full);
|
|
3648
3683
|
} else if (entry.isFile() && SERVICE_FILE_EXTENSIONS.has(import_node_path19.default.extname(entry.name))) {
|
|
3649
3684
|
out.push(full);
|
|
3650
3685
|
}
|
|
@@ -4280,7 +4315,9 @@ async function walkTfFiles(start, depth = 0, max = 5) {
|
|
|
4280
4315
|
for (const entry of entries) {
|
|
4281
4316
|
if (entry.isDirectory()) {
|
|
4282
4317
|
if (IGNORED_DIRS.has(entry.name) || entry.name === ".terraform") continue;
|
|
4283
|
-
|
|
4318
|
+
const child = import_node_path27.default.join(start, entry.name);
|
|
4319
|
+
if (await isPythonVenvDir(child)) continue;
|
|
4320
|
+
out.push(...await walkTfFiles(child, depth + 1, max));
|
|
4284
4321
|
} else if (entry.isFile() && entry.name.endsWith(".tf")) {
|
|
4285
4322
|
out.push(import_node_path27.default.join(start, entry.name));
|
|
4286
4323
|
}
|
|
@@ -4328,7 +4365,9 @@ async function walkYamlFiles2(start, depth = 0, max = 5) {
|
|
|
4328
4365
|
for (const entry of entries) {
|
|
4329
4366
|
if (entry.isDirectory()) {
|
|
4330
4367
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
4331
|
-
|
|
4368
|
+
const child = import_node_path28.default.join(start, entry.name);
|
|
4369
|
+
if (await isPythonVenvDir(child)) continue;
|
|
4370
|
+
out.push(...await walkYamlFiles2(child, depth + 1, max));
|
|
4332
4371
|
} else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path28.default.extname(entry.name))) {
|
|
4333
4372
|
out.push(import_node_path28.default.join(start, entry.name));
|
|
4334
4373
|
}
|
|
@@ -4817,10 +4856,19 @@ function projectFromReq(req) {
|
|
|
4817
4856
|
const params = req.params;
|
|
4818
4857
|
return params.project ?? DEFAULT_PROJECT;
|
|
4819
4858
|
}
|
|
4820
|
-
function resolveProject(registry, req, reply) {
|
|
4859
|
+
function resolveProject(registry, req, reply, bootstrap) {
|
|
4821
4860
|
const name = projectFromReq(req);
|
|
4822
4861
|
const ctx = registry.get(name);
|
|
4823
4862
|
if (!ctx) {
|
|
4863
|
+
const phase = bootstrap?.status(name);
|
|
4864
|
+
if (phase === "bootstrapping") {
|
|
4865
|
+
void reply.code(503).send({ ready: false, project: name, status: "bootstrapping" });
|
|
4866
|
+
return null;
|
|
4867
|
+
}
|
|
4868
|
+
if (phase === "broken") {
|
|
4869
|
+
void reply.code(503).send({ ready: false, project: name, status: "broken" });
|
|
4870
|
+
return null;
|
|
4871
|
+
}
|
|
4824
4872
|
void reply.code(404).send({ error: "project not found", project: name });
|
|
4825
4873
|
return null;
|
|
4826
4874
|
}
|
|
@@ -4850,36 +4898,38 @@ function buildLegacyRegistry(opts) {
|
|
|
4850
4898
|
function registerRoutes(scope, ctx) {
|
|
4851
4899
|
const { registry, startedAt, errorsPathFor, staleEventsPathFor } = ctx;
|
|
4852
4900
|
scope.get("/events", (req, reply) => {
|
|
4853
|
-
const proj = resolveProject(registry, req, reply);
|
|
4901
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap);
|
|
4854
4902
|
if (!proj) return;
|
|
4855
4903
|
handleSse(req, reply, { project: proj.name });
|
|
4856
4904
|
});
|
|
4857
|
-
scope
|
|
4858
|
-
|
|
4859
|
-
|
|
4860
|
-
|
|
4861
|
-
|
|
4862
|
-
|
|
4863
|
-
|
|
4864
|
-
|
|
4865
|
-
|
|
4866
|
-
|
|
4867
|
-
|
|
4868
|
-
|
|
4869
|
-
|
|
4870
|
-
|
|
4871
|
-
|
|
4872
|
-
|
|
4873
|
-
|
|
4905
|
+
if (ctx.scope === "project") {
|
|
4906
|
+
scope.get("/health", async (req, reply) => {
|
|
4907
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap);
|
|
4908
|
+
if (!proj) return;
|
|
4909
|
+
const uptimeMs = Date.now() - startedAt;
|
|
4910
|
+
return {
|
|
4911
|
+
ok: true,
|
|
4912
|
+
project: proj.name,
|
|
4913
|
+
uptimeMs,
|
|
4914
|
+
// Legacy fields kept additively. The web shell's StatusBar reads
|
|
4915
|
+
// nodeCount / edgeCount; ADR-061's HealthResponseSchema validates
|
|
4916
|
+
// the canonical triple and lets the extras pass through.
|
|
4917
|
+
uptime: Math.floor(uptimeMs / 1e3),
|
|
4918
|
+
nodeCount: proj.graph.order,
|
|
4919
|
+
edgeCount: proj.graph.size,
|
|
4920
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
|
|
4921
|
+
};
|
|
4922
|
+
});
|
|
4923
|
+
}
|
|
4874
4924
|
scope.get("/graph", async (req, reply) => {
|
|
4875
|
-
const proj = resolveProject(registry, req, reply);
|
|
4925
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap);
|
|
4876
4926
|
if (!proj) return;
|
|
4877
4927
|
return serializeGraph(proj.graph);
|
|
4878
4928
|
});
|
|
4879
4929
|
scope.get(
|
|
4880
4930
|
"/graph/node/:id",
|
|
4881
4931
|
async (req, reply) => {
|
|
4882
|
-
const proj = resolveProject(registry, req, reply);
|
|
4932
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap);
|
|
4883
4933
|
if (!proj) return;
|
|
4884
4934
|
const { id } = req.params;
|
|
4885
4935
|
if (!proj.graph.hasNode(id)) {
|
|
@@ -4891,7 +4941,7 @@ function registerRoutes(scope, ctx) {
|
|
|
4891
4941
|
scope.get(
|
|
4892
4942
|
"/graph/edges/:id",
|
|
4893
4943
|
async (req, reply) => {
|
|
4894
|
-
const proj = resolveProject(registry, req, reply);
|
|
4944
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap);
|
|
4895
4945
|
if (!proj) return;
|
|
4896
4946
|
const { id } = req.params;
|
|
4897
4947
|
if (!proj.graph.hasNode(id)) {
|
|
@@ -4903,7 +4953,7 @@ function registerRoutes(scope, ctx) {
|
|
|
4903
4953
|
}
|
|
4904
4954
|
);
|
|
4905
4955
|
scope.get("/graph/dependencies/:nodeId", async (req, reply) => {
|
|
4906
|
-
const proj = resolveProject(registry, req, reply);
|
|
4956
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap);
|
|
4907
4957
|
if (!proj) return;
|
|
4908
4958
|
const { nodeId } = req.params;
|
|
4909
4959
|
if (!proj.graph.hasNode(nodeId)) {
|
|
@@ -4918,7 +4968,7 @@ function registerRoutes(scope, ctx) {
|
|
|
4918
4968
|
return getTransitiveDependencies(proj.graph, nodeId, depth);
|
|
4919
4969
|
});
|
|
4920
4970
|
scope.get("/graph/divergences", async (req, reply) => {
|
|
4921
|
-
const proj = resolveProject(registry, req, reply);
|
|
4971
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap);
|
|
4922
4972
|
if (!proj) return;
|
|
4923
4973
|
let typeFilter;
|
|
4924
4974
|
if (req.query.type) {
|
|
@@ -4953,7 +5003,7 @@ function registerRoutes(scope, ctx) {
|
|
|
4953
5003
|
});
|
|
4954
5004
|
});
|
|
4955
5005
|
scope.get("/incidents", async (req, reply) => {
|
|
4956
|
-
const proj = resolveProject(registry, req, reply);
|
|
5006
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap);
|
|
4957
5007
|
if (!proj) return;
|
|
4958
5008
|
const epath = errorsPathFor(proj);
|
|
4959
5009
|
if (!epath) return { count: 0, total: 0, events: [] };
|
|
@@ -4965,7 +5015,7 @@ function registerRoutes(scope, ctx) {
|
|
|
4965
5015
|
return { count: sliced.length, total, events: sliced };
|
|
4966
5016
|
});
|
|
4967
5017
|
scope.get("/stale-events", async (req, reply) => {
|
|
4968
|
-
const proj = resolveProject(registry, req, reply);
|
|
5018
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap);
|
|
4969
5019
|
if (!proj) return;
|
|
4970
5020
|
const spath = staleEventsPathFor(proj);
|
|
4971
5021
|
if (!spath) return { count: 0, total: 0, events: [] };
|
|
@@ -4980,7 +5030,7 @@ function registerRoutes(scope, ctx) {
|
|
|
4980
5030
|
scope.get(
|
|
4981
5031
|
"/incidents/:nodeId",
|
|
4982
5032
|
async (req, reply) => {
|
|
4983
|
-
const proj = resolveProject(registry, req, reply);
|
|
5033
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap);
|
|
4984
5034
|
if (!proj) return;
|
|
4985
5035
|
const { nodeId } = req.params;
|
|
4986
5036
|
if (!proj.graph.hasNode(nodeId)) {
|
|
@@ -4996,7 +5046,7 @@ function registerRoutes(scope, ctx) {
|
|
|
4996
5046
|
}
|
|
4997
5047
|
);
|
|
4998
5048
|
scope.get("/graph/root-cause/:nodeId", async (req, reply) => {
|
|
4999
|
-
const proj = resolveProject(registry, req, reply);
|
|
5049
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap);
|
|
5000
5050
|
if (!proj) return;
|
|
5001
5051
|
const { nodeId } = req.params;
|
|
5002
5052
|
if (!proj.graph.hasNode(nodeId)) {
|
|
@@ -5016,7 +5066,7 @@ function registerRoutes(scope, ctx) {
|
|
|
5016
5066
|
return result;
|
|
5017
5067
|
});
|
|
5018
5068
|
scope.get("/graph/blast-radius/:nodeId", async (req, reply) => {
|
|
5019
|
-
const proj = resolveProject(registry, req, reply);
|
|
5069
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap);
|
|
5020
5070
|
if (!proj) return;
|
|
5021
5071
|
const { nodeId } = req.params;
|
|
5022
5072
|
if (!proj.graph.hasNode(nodeId)) {
|
|
@@ -5029,7 +5079,7 @@ function registerRoutes(scope, ctx) {
|
|
|
5029
5079
|
return getBlastRadius(proj.graph, nodeId, depth);
|
|
5030
5080
|
});
|
|
5031
5081
|
scope.get("/search", async (req, reply) => {
|
|
5032
|
-
const proj = resolveProject(registry, req, reply);
|
|
5082
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap);
|
|
5033
5083
|
if (!proj) return;
|
|
5034
5084
|
const raw = (req.query.q ?? "").trim();
|
|
5035
5085
|
if (!raw) return reply.code(400).send({ error: "query parameter `q` is required" });
|
|
@@ -5060,7 +5110,7 @@ function registerRoutes(scope, ctx) {
|
|
|
5060
5110
|
scope.get(
|
|
5061
5111
|
"/graph/diff",
|
|
5062
5112
|
async (req, reply) => {
|
|
5063
|
-
const proj = resolveProject(registry, req, reply);
|
|
5113
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap);
|
|
5064
5114
|
if (!proj) return;
|
|
5065
5115
|
const against = req.query.against;
|
|
5066
5116
|
if (!against) {
|
|
@@ -5075,7 +5125,7 @@ function registerRoutes(scope, ctx) {
|
|
|
5075
5125
|
}
|
|
5076
5126
|
);
|
|
5077
5127
|
scope.post("/snapshot", async (req, reply) => {
|
|
5078
|
-
const proj = resolveProject(registry, req, reply);
|
|
5128
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap);
|
|
5079
5129
|
if (!proj) return;
|
|
5080
5130
|
const body = req.body;
|
|
5081
5131
|
if (!body || typeof body !== "object" || !body.snapshot) {
|
|
@@ -5104,7 +5154,7 @@ function registerRoutes(scope, ctx) {
|
|
|
5104
5154
|
}
|
|
5105
5155
|
});
|
|
5106
5156
|
scope.post("/graph/scan", async (req, reply) => {
|
|
5107
|
-
const proj = resolveProject(registry, req, reply);
|
|
5157
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap);
|
|
5108
5158
|
if (!proj) return;
|
|
5109
5159
|
if (!proj.scanPath) {
|
|
5110
5160
|
return reply.code(409).send({ error: "scan path not configured for this project", project: proj.name });
|
|
@@ -5120,7 +5170,7 @@ function registerRoutes(scope, ctx) {
|
|
|
5120
5170
|
};
|
|
5121
5171
|
});
|
|
5122
5172
|
scope.get("/policies", async (req, reply) => {
|
|
5123
|
-
const proj = resolveProject(registry, req, reply);
|
|
5173
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap);
|
|
5124
5174
|
if (!proj) return;
|
|
5125
5175
|
const policyPath = ctx.policyFilePathFor(proj);
|
|
5126
5176
|
if (!policyPath) {
|
|
@@ -5137,7 +5187,7 @@ function registerRoutes(scope, ctx) {
|
|
|
5137
5187
|
}
|
|
5138
5188
|
});
|
|
5139
5189
|
scope.get("/policies/violations", async (req, reply) => {
|
|
5140
|
-
const proj = resolveProject(registry, req, reply);
|
|
5190
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap);
|
|
5141
5191
|
if (!proj) return;
|
|
5142
5192
|
const log = new PolicyViolationsLog(proj.paths.policyViolationsPath);
|
|
5143
5193
|
let violations = await log.readAll();
|
|
@@ -5157,7 +5207,7 @@ function registerRoutes(scope, ctx) {
|
|
|
5157
5207
|
return { violations };
|
|
5158
5208
|
});
|
|
5159
5209
|
scope.post("/policies/check", async (req, reply) => {
|
|
5160
|
-
const proj = resolveProject(registry, req, reply);
|
|
5210
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap);
|
|
5161
5211
|
if (!proj) return;
|
|
5162
5212
|
const parsed = import_types22.PoliciesCheckBodySchema.safeParse(req.body ?? {});
|
|
5163
5213
|
if (!parsed.success) {
|
|
@@ -5228,10 +5278,36 @@ async function buildApi(opts) {
|
|
|
5228
5278
|
const routeCtx = {
|
|
5229
5279
|
registry,
|
|
5230
5280
|
startedAt,
|
|
5281
|
+
scope: "root",
|
|
5231
5282
|
errorsPathFor,
|
|
5232
5283
|
staleEventsPathFor,
|
|
5233
|
-
policyFilePathFor
|
|
5284
|
+
policyFilePathFor,
|
|
5285
|
+
bootstrap: opts.bootstrap
|
|
5234
5286
|
};
|
|
5287
|
+
app.get("/health", async () => {
|
|
5288
|
+
const uptimeMs = Date.now() - startedAt;
|
|
5289
|
+
const bootstrapList = opts.bootstrap?.list() ?? [];
|
|
5290
|
+
const byName = new Map(bootstrapList.map((p) => [p.name, p]));
|
|
5291
|
+
const names = /* @__PURE__ */ new Set([
|
|
5292
|
+
...registry.list(),
|
|
5293
|
+
...bootstrapList.map((p) => p.name)
|
|
5294
|
+
]);
|
|
5295
|
+
const projects = [...names].sort().map((name) => {
|
|
5296
|
+
const proj = registry.get(name);
|
|
5297
|
+
const tracked = byName.get(name);
|
|
5298
|
+
return {
|
|
5299
|
+
name,
|
|
5300
|
+
nodeCount: proj?.graph.order ?? 0,
|
|
5301
|
+
edgeCount: proj?.graph.size ?? 0,
|
|
5302
|
+
...tracked ? { status: tracked.status, elapsedMs: tracked.elapsedMs } : {}
|
|
5303
|
+
};
|
|
5304
|
+
});
|
|
5305
|
+
return {
|
|
5306
|
+
ok: true,
|
|
5307
|
+
uptimeMs,
|
|
5308
|
+
projects
|
|
5309
|
+
};
|
|
5310
|
+
});
|
|
5235
5311
|
app.get("/projects", async (_req, reply) => {
|
|
5236
5312
|
try {
|
|
5237
5313
|
return await listProjects();
|
|
@@ -5259,7 +5335,7 @@ async function buildApi(opts) {
|
|
|
5259
5335
|
registerRoutes(app, routeCtx);
|
|
5260
5336
|
await app.register(
|
|
5261
5337
|
async (scope) => {
|
|
5262
|
-
registerRoutes(scope, routeCtx);
|
|
5338
|
+
registerRoutes(scope, { ...routeCtx, scope: "project" });
|
|
5263
5339
|
},
|
|
5264
5340
|
{ prefix: "/projects/:project" }
|
|
5265
5341
|
);
|