diff --git a/packages/loopover-miner/lib/manage-status.js b/packages/loopover-miner/lib/manage-status.js index 3101cadb40..8bd28e5665 100644 --- a/packages/loopover-miner/lib/manage-status.js +++ b/packages/loopover-miner/lib/manage-status.js @@ -1,7 +1,7 @@ import { initEventLedger } from "./event-ledger.js"; import { initPortfolioQueueStore } from "./portfolio-queue.js"; import { initRunStateStore } from "./run-state.js"; -import { argsWantJson, reportCliFailure } from "./cli-error.js"; +import { argsWantJson, describeCliError, reportCliFailure } from "./cli-error.js"; /** Event vocabulary for manage-phase PR snapshots written by manage poll. (#2325) */ export const MANAGE_PR_UPDATE_EVENT = "manage_pr_update"; @@ -219,10 +219,13 @@ export function runManageStatus(args = [], options = {}) { const ownsPortfolioQueue = options.initPortfolioQueue === undefined; const ownsEventLedger = options.initEventLedger === undefined; const ownsRunStateStore = options.initRunStateStore === undefined; - const portfolioQueue = (options.initPortfolioQueue ?? initPortfolioQueueStore)(); - const eventLedger = (options.initEventLedger ?? initEventLedger)(); - const runStateStore = (options.initRunStateStore ?? initRunStateStore)(); + let portfolioQueue; + let eventLedger; + let runStateStore; try { + portfolioQueue = (options.initPortfolioQueue ?? initPortfolioQueueStore)(); + eventLedger = (options.initEventLedger ?? initEventLedger)(); + runStateStore = (options.initRunStateStore ?? initRunStateStore)(); const rows = collectManageStatus({ portfolioQueue, eventLedger }); const runPortfolio = collectRunPortfolio({ portfolioQueue, eventLedger, runStateStore }); if (parsed.json) { @@ -233,9 +236,11 @@ export function runManageStatus(args = [], options = {}) { console.log(`${renderManageStatusTable(rows)}\n\n${renderRunPortfolioTable(runPortfolio)}`); } return 0; + } catch (error) { + return reportCliFailure(parsed.json, describeCliError(error)); } finally { - if (ownsPortfolioQueue) portfolioQueue.close(); - if (ownsEventLedger) eventLedger.close(); - if (ownsRunStateStore) runStateStore.close(); + if (ownsPortfolioQueue) portfolioQueue?.close(); + if (ownsEventLedger) eventLedger?.close(); + if (ownsRunStateStore) runStateStore?.close(); } } diff --git a/packages/loopover-miner/lib/portfolio-dashboard.js b/packages/loopover-miner/lib/portfolio-dashboard.js index 98bb184477..c0013f83a0 100644 --- a/packages/loopover-miner/lib/portfolio-dashboard.js +++ b/packages/loopover-miner/lib/portfolio-dashboard.js @@ -9,7 +9,7 @@ // collector below is factored so it is directly reusable once such a channel exists. import { initPortfolioQueueStore } from "./portfolio-queue.js"; -import { argsWantJson, reportCliFailure } from "./cli-error.js"; +import { argsWantJson, describeCliError, reportCliFailure } from "./cli-error.js"; const QUEUE_STATUS_KEYS = ["queued", "in_progress", "done"]; @@ -94,12 +94,15 @@ export function runPortfolioDashboard(args = [], options = {}) { return reportCliFailure(argsWantJson(args), parsed.error); } const ownsQueue = options.initPortfolioQueue === undefined; - const portfolioQueue = (options.initPortfolioQueue ?? initPortfolioQueueStore)(); + let portfolioQueue; try { + portfolioQueue = (options.initPortfolioQueue ?? initPortfolioQueueStore)(); const summary = collectPortfolioDashboard({ portfolioQueue }, { nowMs: Number.isFinite(options.nowMs) ? options.nowMs : Date.now() }); console.log(parsed.json ? JSON.stringify(summary, null, 2) : renderPortfolioDashboardTable(summary)); return 0; + } catch (error) { + return reportCliFailure(parsed.json, describeCliError(error)); } finally { - if (ownsQueue) portfolioQueue.close(); + if (ownsQueue) portfolioQueue?.close(); } } diff --git a/test/unit/miner-manage-status.test.ts b/test/unit/miner-manage-status.test.ts index e5744a8465..de09d8b540 100644 --- a/test/unit/miner-manage-status.test.ts +++ b/test/unit/miner-manage-status.test.ts @@ -286,4 +286,22 @@ describe("loopover-miner manage status (#2325)", () => { expect(runManageStatus(["acme/widgets"])).toBe(2); expect(String(error.mock.calls[0]?.[0])).toContain("Usage: loopover-miner manage status [--json]"); }); + + it("reports store/runtime failures as parseable JSON on stdout when --json is set", () => { + const log = vi.spyOn(console, "log").mockImplementation(() => {}); + const error = vi.spyOn(console, "error").mockImplementation(() => {}); + const brokenStore = () => { + throw new Error("queue_db"); + }; + + expect(runManageStatus(["--json"], { initPortfolioQueue: brokenStore })).toBe(2); + expect(JSON.parse(String(log.mock.calls[0]?.[0]))).toEqual({ ok: false, error: "queue_db" }); + expect(error).not.toHaveBeenCalled(); + + log.mockClear(); + error.mockClear(); + expect(runManageStatus([], { initPortfolioQueue: brokenStore })).toBe(2); + expect(String(error.mock.calls[0]?.[0])).toBe("queue_db"); + expect(log).not.toHaveBeenCalled(); + }); }); diff --git a/test/unit/miner-portfolio-dashboard.test.ts b/test/unit/miner-portfolio-dashboard.test.ts index 0d645ecb2a..46c4a4d231 100644 --- a/test/unit/miner-portfolio-dashboard.test.ts +++ b/test/unit/miner-portfolio-dashboard.test.ts @@ -95,4 +95,16 @@ describe("runPortfolioDashboard (#4287)", () => { }); expect(err).not.toHaveBeenCalled(); }); + + it("reports store/runtime failures as parseable JSON on stdout when --json is set", () => { + const log = vi.spyOn(console, "log").mockImplementation(() => {}); + const error = vi.spyOn(console, "error").mockImplementation(() => {}); + const brokenStore = () => { + throw new Error("queue_db"); + }; + + expect(runPortfolioDashboard(["--json"], { initPortfolioQueue: brokenStore })).toBe(2); + expect(JSON.parse(String(log.mock.calls[0]?.[0]))).toEqual({ ok: false, error: "queue_db" }); + expect(error).not.toHaveBeenCalled(); + }); });