Files
Ekklesia/core/src/cli/migration-report.ts
T
ekklesia 0b282865cb
CI / verify (pull_request) Successful in 19s
CI / infrastructure-smoke (pull_request) Successful in 20s
feat: add transactional module migration runner
2026-07-21 08:52:41 -05:00

63 lines
2.2 KiB
TypeScript

import type { ModuleMigrationAggregate } from "../modules/index.js";
import type { MigrationApplyResult, MigrationPlanResult } from "../migrations/index.js";
function plural(count: number, singular: string): string {
return count === 1 ? singular : `${singular}s`;
}
export function formatMigrationValidationReport(
aggregate: ModuleMigrationAggregate,
): readonly string[] {
const moduleCount = aggregate.modules.length;
const migrationCount = aggregate.migrations.length;
const header = `Migration validation passed (${moduleCount} ${plural(moduleCount, "module")}, ${migrationCount} ${plural(migrationCount, "migration")})${moduleCount === 0 ? "." : ":"}`;
const lines = [header];
for (const module of aggregate.modules) {
if (module.migrations.length === 0) {
lines.push(`- ${module.moduleId} (no migrations)`);
continue;
}
for (const migration of module.migrations) {
lines.push(
`- ${migration.identity} sha256=${migration.checksum} destructive=${migration.destructive.mode}`,
);
}
}
return Object.freeze(lines);
}
export function formatMigrationPlanReport(plan: MigrationPlanResult): readonly string[] {
const readyCount = plan.pendingCount - plan.approvalRequiredCount;
const lines = [
`Migration plan (${plan.appliedCount} applied, ${readyCount} pending, ${plan.approvalRequiredCount} approval-blocked):`,
];
for (const entry of plan.entries) {
lines.push(`- [${entry.status}] ${entry.identity} sha256=${entry.checksum}`);
}
if (plan.entries.length === 0) {
lines.push("- No installed module migrations.");
}
lines.push(
"Informational only: this plan is read-only and may become stale; apply revalidates history under the migration lock.",
);
return Object.freeze(lines);
}
export function formatMigrationApplyReport(result: MigrationApplyResult): readonly string[] {
if (result.noOp) {
return Object.freeze(["Migration apply completed: database is already current (no changes)."]);
}
const lines = [
`Migration apply completed (${result.appliedCount} ${plural(result.appliedCount, "migration")} applied):`,
];
for (const migration of result.applied) {
lines.push(`- ${migration.identity} sha256=${migration.checksum}`);
}
return Object.freeze(lines);
}