@neat.is/core 0.4.18 → 0.4.19
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-T3X6XJPU.js → chunk-Q5EDVWKZ.js} +68 -38
- package/dist/{chunk-T3X6XJPU.js.map → chunk-Q5EDVWKZ.js.map} +1 -1
- package/dist/{chunk-GXWBMJDJ.js → chunk-ZV7GHZ2D.js} +17 -18
- package/dist/chunk-ZV7GHZ2D.js.map +1 -0
- package/dist/cli.cjs +67 -37
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +2 -2
- package/dist/index.cjs +81 -53
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +9 -1
- package/dist/index.d.ts +9 -1
- package/dist/index.js +2 -2
- package/dist/neatd.cjs +89 -54
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +10 -3
- package/dist/neatd.js.map +1 -1
- package/dist/server.cjs +67 -37
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +1 -1
- package/package.json +2 -2
- package/dist/chunk-GXWBMJDJ.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
readDaemonRecord
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-ZV7GHZ2D.js";
|
|
5
5
|
import {
|
|
6
6
|
buildSearchIndex
|
|
7
7
|
} from "./chunk-XOOCA5T7.js";
|
|
@@ -52,7 +52,7 @@ import {
|
|
|
52
52
|
signalDaemonStop,
|
|
53
53
|
startPersistLoop,
|
|
54
54
|
startStalenessLoop
|
|
55
|
-
} from "./chunk-
|
|
55
|
+
} from "./chunk-Q5EDVWKZ.js";
|
|
56
56
|
import {
|
|
57
57
|
startOtelGrpcReceiver
|
|
58
58
|
} from "./chunk-MTXF77TN.js";
|
package/dist/index.cjs
CHANGED
|
@@ -5349,7 +5349,9 @@ async function loadGraphFromDisk(graph, outPath) {
|
|
|
5349
5349
|
graph.clear();
|
|
5350
5350
|
graph.import(payload.graph);
|
|
5351
5351
|
}
|
|
5352
|
-
function startPersistLoop(graph, outPath,
|
|
5352
|
+
function startPersistLoop(graph, outPath, opts = {}) {
|
|
5353
|
+
const intervalMs = opts.intervalMs ?? 6e4;
|
|
5354
|
+
const exitOnSignal = opts.exitOnSignal ?? true;
|
|
5353
5355
|
let stopped = false;
|
|
5354
5356
|
const tick = async () => {
|
|
5355
5357
|
if (stopped) return;
|
|
@@ -5362,7 +5364,7 @@ function startPersistLoop(graph, outPath, intervalMs = 6e4) {
|
|
|
5362
5364
|
const interval = setInterval(() => {
|
|
5363
5365
|
void tick();
|
|
5364
5366
|
}, intervalMs);
|
|
5365
|
-
const onSignal = (signal) => {
|
|
5367
|
+
const onSignal = exitOnSignal ? (signal) => {
|
|
5366
5368
|
void (async () => {
|
|
5367
5369
|
try {
|
|
5368
5370
|
await saveGraphToDisk(graph, outPath);
|
|
@@ -5372,14 +5374,18 @@ function startPersistLoop(graph, outPath, intervalMs = 6e4) {
|
|
|
5372
5374
|
process.exit(0);
|
|
5373
5375
|
}
|
|
5374
5376
|
})();
|
|
5375
|
-
};
|
|
5376
|
-
|
|
5377
|
-
|
|
5377
|
+
} : null;
|
|
5378
|
+
if (onSignal) {
|
|
5379
|
+
process.on("SIGTERM", onSignal);
|
|
5380
|
+
process.on("SIGINT", onSignal);
|
|
5381
|
+
}
|
|
5378
5382
|
return () => {
|
|
5379
5383
|
stopped = true;
|
|
5380
5384
|
clearInterval(interval);
|
|
5381
|
-
|
|
5382
|
-
|
|
5385
|
+
if (onSignal) {
|
|
5386
|
+
process.off("SIGTERM", onSignal);
|
|
5387
|
+
process.off("SIGINT", onSignal);
|
|
5388
|
+
}
|
|
5383
5389
|
};
|
|
5384
5390
|
}
|
|
5385
5391
|
|
|
@@ -6430,12 +6436,16 @@ function serializeGraph(graph) {
|
|
|
6430
6436
|
});
|
|
6431
6437
|
return { nodes, edges };
|
|
6432
6438
|
}
|
|
6433
|
-
function projectFromReq(req) {
|
|
6439
|
+
function projectFromReq(req, singleProject) {
|
|
6434
6440
|
const params = req.params;
|
|
6435
|
-
|
|
6441
|
+
const named = params.project;
|
|
6442
|
+
if (singleProject) {
|
|
6443
|
+
return named === void 0 || named === DEFAULT_PROJECT ? singleProject : named;
|
|
6444
|
+
}
|
|
6445
|
+
return named ?? DEFAULT_PROJECT;
|
|
6436
6446
|
}
|
|
6437
|
-
function resolveProject(registry, req, reply, bootstrap) {
|
|
6438
|
-
const name = projectFromReq(req);
|
|
6447
|
+
function resolveProject(registry, req, reply, bootstrap, singleProject) {
|
|
6448
|
+
const name = projectFromReq(req, singleProject);
|
|
6439
6449
|
const ctx = registry.get(name);
|
|
6440
6450
|
if (!ctx) {
|
|
6441
6451
|
const phase = bootstrap?.status(name);
|
|
@@ -6476,13 +6486,13 @@ function buildLegacyRegistry(opts) {
|
|
|
6476
6486
|
function registerRoutes(scope, ctx) {
|
|
6477
6487
|
const { registry, startedAt, errorsPathFor, staleEventsPathFor } = ctx;
|
|
6478
6488
|
scope.get("/events", (req, reply) => {
|
|
6479
|
-
const proj = resolveProject(registry, req, reply, ctx.bootstrap);
|
|
6489
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
|
|
6480
6490
|
if (!proj) return;
|
|
6481
6491
|
handleSse(req, reply, { project: proj.name });
|
|
6482
6492
|
});
|
|
6483
6493
|
if (ctx.scope === "project") {
|
|
6484
6494
|
scope.get("/health", async (req, reply) => {
|
|
6485
|
-
const proj = resolveProject(registry, req, reply, ctx.bootstrap);
|
|
6495
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
|
|
6486
6496
|
if (!proj) return;
|
|
6487
6497
|
const uptimeMs = Date.now() - startedAt;
|
|
6488
6498
|
return {
|
|
@@ -6500,14 +6510,14 @@ function registerRoutes(scope, ctx) {
|
|
|
6500
6510
|
});
|
|
6501
6511
|
}
|
|
6502
6512
|
scope.get("/graph", async (req, reply) => {
|
|
6503
|
-
const proj = resolveProject(registry, req, reply, ctx.bootstrap);
|
|
6513
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
|
|
6504
6514
|
if (!proj) return;
|
|
6505
6515
|
return serializeGraph(proj.graph);
|
|
6506
6516
|
});
|
|
6507
6517
|
scope.get(
|
|
6508
6518
|
"/graph/node/:id",
|
|
6509
6519
|
async (req, reply) => {
|
|
6510
|
-
const proj = resolveProject(registry, req, reply, ctx.bootstrap);
|
|
6520
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
|
|
6511
6521
|
if (!proj) return;
|
|
6512
6522
|
const { id } = req.params;
|
|
6513
6523
|
if (!proj.graph.hasNode(id)) {
|
|
@@ -6519,7 +6529,7 @@ function registerRoutes(scope, ctx) {
|
|
|
6519
6529
|
scope.get(
|
|
6520
6530
|
"/graph/edges/:id",
|
|
6521
6531
|
async (req, reply) => {
|
|
6522
|
-
const proj = resolveProject(registry, req, reply, ctx.bootstrap);
|
|
6532
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
|
|
6523
6533
|
if (!proj) return;
|
|
6524
6534
|
const { id } = req.params;
|
|
6525
6535
|
if (!proj.graph.hasNode(id)) {
|
|
@@ -6531,7 +6541,7 @@ function registerRoutes(scope, ctx) {
|
|
|
6531
6541
|
}
|
|
6532
6542
|
);
|
|
6533
6543
|
scope.get("/graph/dependencies/:nodeId", async (req, reply) => {
|
|
6534
|
-
const proj = resolveProject(registry, req, reply, ctx.bootstrap);
|
|
6544
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
|
|
6535
6545
|
if (!proj) return;
|
|
6536
6546
|
const { nodeId } = req.params;
|
|
6537
6547
|
if (!proj.graph.hasNode(nodeId)) {
|
|
@@ -6546,7 +6556,7 @@ function registerRoutes(scope, ctx) {
|
|
|
6546
6556
|
return getTransitiveDependencies(proj.graph, nodeId, depth);
|
|
6547
6557
|
});
|
|
6548
6558
|
scope.get("/graph/divergences", async (req, reply) => {
|
|
6549
|
-
const proj = resolveProject(registry, req, reply, ctx.bootstrap);
|
|
6559
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
|
|
6550
6560
|
if (!proj) return;
|
|
6551
6561
|
let typeFilter;
|
|
6552
6562
|
if (req.query.type) {
|
|
@@ -6581,7 +6591,7 @@ function registerRoutes(scope, ctx) {
|
|
|
6581
6591
|
});
|
|
6582
6592
|
});
|
|
6583
6593
|
scope.get("/incidents", async (req, reply) => {
|
|
6584
|
-
const proj = resolveProject(registry, req, reply, ctx.bootstrap);
|
|
6594
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
|
|
6585
6595
|
if (!proj) return;
|
|
6586
6596
|
const epath = errorsPathFor(proj);
|
|
6587
6597
|
if (!epath) return { count: 0, total: 0, events: [] };
|
|
@@ -6593,7 +6603,7 @@ function registerRoutes(scope, ctx) {
|
|
|
6593
6603
|
return { count: sliced.length, total, events: sliced };
|
|
6594
6604
|
});
|
|
6595
6605
|
scope.get("/stale-events", async (req, reply) => {
|
|
6596
|
-
const proj = resolveProject(registry, req, reply, ctx.bootstrap);
|
|
6606
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
|
|
6597
6607
|
if (!proj) return;
|
|
6598
6608
|
const spath = staleEventsPathFor(proj);
|
|
6599
6609
|
if (!spath) return { count: 0, total: 0, events: [] };
|
|
@@ -6608,7 +6618,7 @@ function registerRoutes(scope, ctx) {
|
|
|
6608
6618
|
scope.get(
|
|
6609
6619
|
"/incidents/:nodeId",
|
|
6610
6620
|
async (req, reply) => {
|
|
6611
|
-
const proj = resolveProject(registry, req, reply, ctx.bootstrap);
|
|
6621
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
|
|
6612
6622
|
if (!proj) return;
|
|
6613
6623
|
const { nodeId } = req.params;
|
|
6614
6624
|
if (!proj.graph.hasNode(nodeId)) {
|
|
@@ -6624,7 +6634,7 @@ function registerRoutes(scope, ctx) {
|
|
|
6624
6634
|
}
|
|
6625
6635
|
);
|
|
6626
6636
|
scope.get("/graph/root-cause/:nodeId", async (req, reply) => {
|
|
6627
|
-
const proj = resolveProject(registry, req, reply, ctx.bootstrap);
|
|
6637
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
|
|
6628
6638
|
if (!proj) return;
|
|
6629
6639
|
const { nodeId } = req.params;
|
|
6630
6640
|
if (!proj.graph.hasNode(nodeId)) {
|
|
@@ -6644,7 +6654,7 @@ function registerRoutes(scope, ctx) {
|
|
|
6644
6654
|
return result;
|
|
6645
6655
|
});
|
|
6646
6656
|
scope.get("/graph/blast-radius/:nodeId", async (req, reply) => {
|
|
6647
|
-
const proj = resolveProject(registry, req, reply, ctx.bootstrap);
|
|
6657
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
|
|
6648
6658
|
if (!proj) return;
|
|
6649
6659
|
const { nodeId } = req.params;
|
|
6650
6660
|
if (!proj.graph.hasNode(nodeId)) {
|
|
@@ -6657,7 +6667,7 @@ function registerRoutes(scope, ctx) {
|
|
|
6657
6667
|
return getBlastRadius(proj.graph, nodeId, depth);
|
|
6658
6668
|
});
|
|
6659
6669
|
scope.get("/search", async (req, reply) => {
|
|
6660
|
-
const proj = resolveProject(registry, req, reply, ctx.bootstrap);
|
|
6670
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
|
|
6661
6671
|
if (!proj) return;
|
|
6662
6672
|
const raw = (req.query.q ?? "").trim();
|
|
6663
6673
|
if (!raw) return reply.code(400).send({ error: "query parameter `q` is required" });
|
|
@@ -6688,7 +6698,7 @@ function registerRoutes(scope, ctx) {
|
|
|
6688
6698
|
scope.get(
|
|
6689
6699
|
"/graph/diff",
|
|
6690
6700
|
async (req, reply) => {
|
|
6691
|
-
const proj = resolveProject(registry, req, reply, ctx.bootstrap);
|
|
6701
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
|
|
6692
6702
|
if (!proj) return;
|
|
6693
6703
|
const against = req.query.against;
|
|
6694
6704
|
if (!against) {
|
|
@@ -6703,7 +6713,7 @@ function registerRoutes(scope, ctx) {
|
|
|
6703
6713
|
}
|
|
6704
6714
|
);
|
|
6705
6715
|
scope.post("/snapshot", async (req, reply) => {
|
|
6706
|
-
const proj = resolveProject(registry, req, reply, ctx.bootstrap);
|
|
6716
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
|
|
6707
6717
|
if (!proj) return;
|
|
6708
6718
|
const body = req.body;
|
|
6709
6719
|
if (!body || typeof body !== "object" || !body.snapshot) {
|
|
@@ -6732,7 +6742,7 @@ function registerRoutes(scope, ctx) {
|
|
|
6732
6742
|
}
|
|
6733
6743
|
});
|
|
6734
6744
|
scope.post("/graph/scan", async (req, reply) => {
|
|
6735
|
-
const proj = resolveProject(registry, req, reply, ctx.bootstrap);
|
|
6745
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
|
|
6736
6746
|
if (!proj) return;
|
|
6737
6747
|
if (!proj.scanPath) {
|
|
6738
6748
|
return reply.code(409).send({ error: "scan path not configured for this project", project: proj.name });
|
|
@@ -6748,7 +6758,7 @@ function registerRoutes(scope, ctx) {
|
|
|
6748
6758
|
};
|
|
6749
6759
|
});
|
|
6750
6760
|
scope.get("/policies", async (req, reply) => {
|
|
6751
|
-
const proj = resolveProject(registry, req, reply, ctx.bootstrap);
|
|
6761
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
|
|
6752
6762
|
if (!proj) return;
|
|
6753
6763
|
const policyPath = ctx.policyFilePathFor(proj);
|
|
6754
6764
|
if (!policyPath) {
|
|
@@ -6765,7 +6775,7 @@ function registerRoutes(scope, ctx) {
|
|
|
6765
6775
|
}
|
|
6766
6776
|
});
|
|
6767
6777
|
scope.get("/policies/violations", async (req, reply) => {
|
|
6768
|
-
const proj = resolveProject(registry, req, reply, ctx.bootstrap);
|
|
6778
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
|
|
6769
6779
|
if (!proj) return;
|
|
6770
6780
|
const log = new PolicyViolationsLog(proj.paths.policyViolationsPath);
|
|
6771
6781
|
let violations = await log.readAll();
|
|
@@ -6785,7 +6795,7 @@ function registerRoutes(scope, ctx) {
|
|
|
6785
6795
|
return { violations };
|
|
6786
6796
|
});
|
|
6787
6797
|
scope.post("/policies/check", async (req, reply) => {
|
|
6788
|
-
const proj = resolveProject(registry, req, reply, ctx.bootstrap);
|
|
6798
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
|
|
6789
6799
|
if (!proj) return;
|
|
6790
6800
|
const parsed = import_types25.PoliciesCheckBodySchema.safeParse(req.body ?? {});
|
|
6791
6801
|
if (!parsed.success) {
|
|
@@ -6821,7 +6831,7 @@ function registerRoutes(scope, ctx) {
|
|
|
6821
6831
|
};
|
|
6822
6832
|
});
|
|
6823
6833
|
scope.get("/extend/list-uninstrumented", async (req, reply) => {
|
|
6824
|
-
const proj = resolveProject(registry, req, reply, ctx.bootstrap);
|
|
6834
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
|
|
6825
6835
|
if (!proj) return;
|
|
6826
6836
|
if (!proj.scanPath) {
|
|
6827
6837
|
return reply.code(409).send({ error: "scan path not configured for this project", project: proj.name });
|
|
@@ -6834,7 +6844,7 @@ function registerRoutes(scope, ctx) {
|
|
|
6834
6844
|
}
|
|
6835
6845
|
});
|
|
6836
6846
|
scope.get("/extend/lookup", async (req, reply) => {
|
|
6837
|
-
const proj = resolveProject(registry, req, reply, ctx.bootstrap);
|
|
6847
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
|
|
6838
6848
|
if (!proj) return;
|
|
6839
6849
|
const { library, version } = req.query;
|
|
6840
6850
|
if (!library) {
|
|
@@ -6847,7 +6857,7 @@ function registerRoutes(scope, ctx) {
|
|
|
6847
6857
|
return result;
|
|
6848
6858
|
});
|
|
6849
6859
|
scope.get("/extend/describe", async (req, reply) => {
|
|
6850
|
-
const proj = resolveProject(registry, req, reply, ctx.bootstrap);
|
|
6860
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
|
|
6851
6861
|
if (!proj) return;
|
|
6852
6862
|
if (!proj.scanPath) {
|
|
6853
6863
|
return reply.code(409).send({ error: "scan path not configured for this project", project: proj.name });
|
|
@@ -6860,7 +6870,7 @@ function registerRoutes(scope, ctx) {
|
|
|
6860
6870
|
}
|
|
6861
6871
|
});
|
|
6862
6872
|
scope.post("/extend/apply", async (req, reply) => {
|
|
6863
|
-
const proj = resolveProject(registry, req, reply, ctx.bootstrap);
|
|
6873
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
|
|
6864
6874
|
if (!proj) return;
|
|
6865
6875
|
if (!proj.scanPath) {
|
|
6866
6876
|
return reply.code(409).send({ error: "scan path not configured for this project", project: proj.name });
|
|
@@ -6880,7 +6890,7 @@ function registerRoutes(scope, ctx) {
|
|
|
6880
6890
|
}
|
|
6881
6891
|
});
|
|
6882
6892
|
scope.post("/extend/dry-run", async (req, reply) => {
|
|
6883
|
-
const proj = resolveProject(registry, req, reply, ctx.bootstrap);
|
|
6893
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
|
|
6884
6894
|
if (!proj) return;
|
|
6885
6895
|
if (!proj.scanPath) {
|
|
6886
6896
|
return reply.code(409).send({ error: "scan path not configured for this project", project: proj.name });
|
|
@@ -6900,7 +6910,7 @@ function registerRoutes(scope, ctx) {
|
|
|
6900
6910
|
}
|
|
6901
6911
|
});
|
|
6902
6912
|
scope.post("/extend/rollback", async (req, reply) => {
|
|
6903
|
-
const proj = resolveProject(registry, req, reply, ctx.bootstrap);
|
|
6913
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
|
|
6904
6914
|
if (!proj) return;
|
|
6905
6915
|
if (!proj.scanPath) {
|
|
6906
6916
|
return reply.code(409).send({ error: "scan path not configured for this project", project: proj.name });
|
|
@@ -6963,7 +6973,8 @@ async function buildApi(opts) {
|
|
|
6963
6973
|
errorsPathFor,
|
|
6964
6974
|
staleEventsPathFor,
|
|
6965
6975
|
policyFilePathFor,
|
|
6966
|
-
bootstrap: opts.bootstrap
|
|
6976
|
+
bootstrap: opts.bootstrap,
|
|
6977
|
+
singleProject: opts.singleProject?.name
|
|
6967
6978
|
};
|
|
6968
6979
|
app.get("/health", async () => {
|
|
6969
6980
|
const uptimeMs = Date.now() - startedAt;
|
|
@@ -6986,10 +6997,29 @@ async function buildApi(opts) {
|
|
|
6986
6997
|
return {
|
|
6987
6998
|
ok: true,
|
|
6988
6999
|
uptimeMs,
|
|
7000
|
+
// ADR-096 §7 — a per-project daemon carries its project at the top level
|
|
7001
|
+
// so a spawn can confirm a daemon found on a reused port is serving THIS
|
|
7002
|
+
// project before reusing it. The legacy multi-project daemon omits it and
|
|
7003
|
+
// is matched against the `projects` array instead.
|
|
7004
|
+
...opts.singleProject ? { project: opts.singleProject.name } : {},
|
|
6989
7005
|
projects
|
|
6990
7006
|
};
|
|
6991
7007
|
});
|
|
6992
7008
|
app.get("/projects", async (_req, reply) => {
|
|
7009
|
+
if (opts.singleProject) {
|
|
7010
|
+
const phase = opts.bootstrap?.status(opts.singleProject.name);
|
|
7011
|
+
const entry = {
|
|
7012
|
+
name: opts.singleProject.name,
|
|
7013
|
+
path: opts.singleProject.path,
|
|
7014
|
+
registeredAt: new Date(startedAt).toISOString(),
|
|
7015
|
+
languages: [],
|
|
7016
|
+
// The daemon is serving this project, so it's active unless its
|
|
7017
|
+
// bootstrap broke. ('bootstrapping' still reads as active — the project
|
|
7018
|
+
// is the one to land on; its graph route answers 503 until ready.)
|
|
7019
|
+
status: phase === "broken" ? "broken" : "active"
|
|
7020
|
+
};
|
|
7021
|
+
return [entry];
|
|
7022
|
+
}
|
|
6993
7023
|
try {
|
|
6994
7024
|
return await listProjects();
|
|
6995
7025
|
} catch (err) {
|
|
@@ -7191,7 +7221,7 @@ async function bootstrapProject(entry) {
|
|
|
7191
7221
|
const detachEvents = attachGraphToEventBus(graph, { project: entry.name });
|
|
7192
7222
|
try {
|
|
7193
7223
|
await extractFromDirectory(graph, entry.path);
|
|
7194
|
-
const stopPersist = startPersistLoop(graph, outPath);
|
|
7224
|
+
const stopPersist = startPersistLoop(graph, outPath, { exitOnSignal: false });
|
|
7195
7225
|
await touchLastSeen(entry.name).catch(() => {
|
|
7196
7226
|
});
|
|
7197
7227
|
return {
|
|
@@ -7447,22 +7477,14 @@ async function startDaemon(opts = {}) {
|
|
|
7447
7477
|
elapsedMs: now - (bootstrapStartedAt.get(name) ?? now)
|
|
7448
7478
|
}));
|
|
7449
7479
|
}
|
|
7450
|
-
}
|
|
7480
|
+
},
|
|
7481
|
+
// ADR-096 §4/§5/§7 — hand the daemon's identity to buildApi so the REST
|
|
7482
|
+
// surface reflects "the daemon is the project": `GET /projects` reports
|
|
7483
|
+
// only this project (the dashboard pins to it), and the daemon-wide
|
|
7484
|
+
// `/health` carries it at the top level for the spawn-reuse identity
|
|
7485
|
+
// check. Absent for the legacy multi-project daemon.
|
|
7486
|
+
singleProject: singleProject && singleProjectPath ? { name: singleProject, path: singleProjectPath } : void 0
|
|
7451
7487
|
});
|
|
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
|
-
}
|
|
7466
7488
|
restAddress = await restApp.listen({ port: restPort, host });
|
|
7467
7489
|
console.log(`neatd: REST listening on ${restAddress}`);
|
|
7468
7490
|
} catch (err) {
|
|
@@ -7714,6 +7736,12 @@ async function startDaemon(opts = {}) {
|
|
|
7714
7736
|
});
|
|
7715
7737
|
if (restApp) await restApp.close().catch(() => {
|
|
7716
7738
|
});
|
|
7739
|
+
for (const slot of slots.values()) {
|
|
7740
|
+
if (slot.status === "active" && slot.outPath) {
|
|
7741
|
+
await saveGraphToDisk(slot.graph, slot.outPath).catch(() => {
|
|
7742
|
+
});
|
|
7743
|
+
}
|
|
7744
|
+
}
|
|
7717
7745
|
for (const slot of slots.values()) {
|
|
7718
7746
|
teardownSlot(slot);
|
|
7719
7747
|
}
|