feat: add Ekklesia doctor command
CI / verify (pull_request) Successful in 20s
CI / infrastructure-smoke (pull_request) Successful in 20s
CI / migration-smoke (pull_request) Successful in 25s

This commit is contained in:
ekklesia
2026-07-21 10:42:21 -05:00
parent f89d575dd4
commit 02ee3ccd80
18 changed files with 1546 additions and 7 deletions
+56
View File
@@ -0,0 +1,56 @@
import type { DoctorCheck, DoctorCheckId, DoctorReport, DoctorResult } from "./doctor.js";
interface DoctorSection {
readonly checkIds: readonly DoctorCheckId[];
readonly title: string;
}
function section(title: string, checkIds: readonly DoctorCheckId[]): DoctorSection {
return Object.freeze({ checkIds: Object.freeze([...checkIds]), title });
}
const sections: readonly DoctorSection[] = Object.freeze([
section("Platform", ["platform.runtime"]),
section("Modules", ["modules.discovery", "modules.dependencies"]),
section("Data definitions", ["schemas.discovery", "migrations.discovery"]),
section("PostgreSQL", ["postgresql.connection", "postgresql.plan"]),
section("Object storage", ["object-storage.integration"]),
]);
const statusSymbols: Readonly<Record<DoctorCheck["status"], string>> = Object.freeze({
failure: "✗",
pass: "✓",
skipped: "○",
warning: "!",
});
const resultLabels: Readonly<Record<DoctorResult, string>> = Object.freeze({
"not-ready": "NOT READY",
ready: "READY",
"ready-with-warnings": "READY WITH WARNINGS",
});
export function formatDoctorReport(report: DoctorReport): readonly string[] {
const checks = new Map(report.checks.map((check) => [check.id, check]));
const lines = ["Ekklesia Doctor", ""];
for (const [sectionIndex, section] of sections.entries()) {
lines.push(section.title);
for (const checkId of section.checkIds) {
const check = checks.get(checkId);
if (!check) {
continue;
}
lines.push(` ${statusSymbols[check.status]} ${check.summary}`);
for (const detail of check.details) {
lines.push(` ${detail}`);
}
}
if (sectionIndex < sections.length - 1) {
lines.push("");
}
}
lines.push("", `Result: ${resultLabels[report.result]}`);
return Object.freeze(lines);
}