/*
RFC-0626: bordbuch.commit — auto-commit dirty bordbuch projection files in the cache clone after bordbuch.generate.
- Does not generate bordbuch projections — use bordbuch.generate for that.
- Does not commit non-bordbuch files — only stages the three bordbuch projection paths.
- Does not acquire locks — pipeline runs sequentially and bordbuch.generate has already released locks by the time bordbuch.commit runs.
- RFC-0626: initial bordbuch.commit command handler.
- RFC-0646: replace all gitExec calls with gitExecWithRetry for transient-failure resilience.
- RFC-0702: wrap all gitExecWithRetry calls in try/catch — git failures return error result instead of throwing; add error field to BordbuchCommitResult; add logger.warn in runBordbuchCommit on git failure.
- RFC-1249: wire werkstatt.commands.validate into packages.check + retire CMD-OUTPUT debt
*/
import type {
KernelCommandInput,
KernelCommandResult,
KernelRuntimeContext,
} from "@warpgogol/werkstatt-engine/kernel";
import { gitExecWithRetry, type RetryOptions } from "../werkstatt/git-exec.ts";
import { resolveCacheClonePath } from "../sternsystem/registry-io.ts";
const BORDBUCH_PROJECTION_PATHS = [
"bordbuch/events.ndjson",
"bordbuch/status.generated.yaml",
"public/.well-known/bordbuch.json",
"public/.well-known/bordbuch/index.html",
];
const BORDBUCH_RETRY_OPTIONS: RetryOptions = {
backoffMs: [12_000, 60_000],
};
export interface BordbuchCommitResult {
committed: boolean;
commitSha: string | null;
systemId: string;
filesCommitted: string[];
error?: string;
}
function flagString(input: KernelCommandInput, key: string): string | undefined {
const v = input.flags[key];
return typeof v === "string" ? v : undefined;
}
export async function commitBordbuchProjections(
workspaceRoot: string,
systemId: string,
): Promise {
let cachePath: string;
try {
cachePath = await resolveCacheClonePath(workspaceRoot, systemId);
} catch {
return { committed: false, commitSha: null, systemId, filesCommitted: [] };
}
try {
const status = await gitExecWithRetry(cachePath, "status --porcelain", BORDBUCH_RETRY_OPTIONS, {
allowNonZero: true,
});
if (!status) {
return { committed: false, commitSha: null, systemId, filesCommitted: [] };
}
const dirtyFiles = status
.split("\n")
.filter((line) => line.trim().length > 0)
.map((line) => line.slice(3).trim());
const bordbuchDirty = dirtyFiles.filter((f) =>
BORDBUCH_PROJECTION_PATHS.some((p) => f === p || f.startsWith(p)),
);
if (bordbuchDirty.length === 0) {
return { committed: false, commitSha: null, systemId, filesCommitted: [] };
}
const addArgs = bordbuchDirty.map((f) => `"${f}"`).join(" ");
await gitExecWithRetry(cachePath, `add -- ${addArgs}`, BORDBUCH_RETRY_OPTIONS);
await gitExecWithRetry(
cachePath,
'commit -m "chore: bordbuch projections from build.prepare"',
BORDBUCH_RETRY_OPTIONS,
);
const sha = await gitExecWithRetry(cachePath, "rev-parse HEAD", BORDBUCH_RETRY_OPTIONS);
return { committed: true, commitSha: sha, systemId, filesCommitted: bordbuchDirty };
} catch (err) {
return {
committed: false,
commitSha: null,
systemId,
filesCommitted: [],
error: err instanceof Error ? err.message : String(err),
};
}
}
export async function runBordbuchCommit(
input: KernelCommandInput,
context: KernelRuntimeContext,
): Promise> {
const { workspaceRoot, logger } = context;
const systemId = flagString(input, "system") ?? context.site?.name;
if (!systemId) {
return {
exitCode: 0,
summary: "[bordbuch.commit] no system id — skipping",
};
}
const result = await commitBordbuchProjections(workspaceRoot, systemId);
if (result.committed) {
logger.success(
`[bordbuch.commit] committed ${result.filesCommitted.length} bordbuch projection files for ${systemId}`,
);
return {
data: result,
exitCode: 0,
summary: `[bordbuch.commit] committed ${result.filesCommitted.length} bordbuch projection files for ${systemId}`,
nextSteps: [
{
action: `Push to remote: pnpm exec werkstatt run sternsystem.sync --id ${systemId}`,
kind: "optional",
},
],
};
}
if (result.error) {
logger.warn(`[bordbuch.commit] git operation failed for ${systemId}: ${result.error}`);
return {
data: result,
exitCode: 1,
summary: `[bordbuch.commit] git operation failed for ${systemId}: ${result.error}`,
nextSteps: [
{
action: `Check git status and resolve the conflict, then re-run: pnpm exec werkstatt run bordbuch.commit --site ${systemId}`,
kind: "required",
},
],
};
}
return {
data: result,
exitCode: 0,
summary: `[bordbuch.commit] no dirty bordbuch files for ${systemId}`,
};
}