Files
pikasTech-unidesk/scripts/src/platform-infra-gitea-render.ts
T
2026-07-12 02:17:05 +02:00

344 lines
18 KiB
TypeScript

import type { RenderedCliResult } from "./output";
export function renderMirrorPlan(result: Record<string, unknown>): RenderedCliResult {
const target = record(result.target);
const source = record(result.sourceAuthority);
const repos = arrayRecords(result.repositories).map((repo) => [stringValue(repo.key), stringValue(repo.upstreamRepository), stringValue(repo.upstreamBranch), stringValue(repo.readUrl), stringValue(repo.legacyDisposition)]);
const responsibilities = arrayRecords(result.responsibilities).map((item) => [stringValue(item.name), stringValue(item.current), stringValue(item.target), stringValue(item.disposition)]);
const next = record(result.next);
return rendered(result, "platform-infra gitea mirror plan", [
"PLATFORM-INFRA GITEA MIRROR PLAN",
...table(["TARGET", "STAGE", "AUTHORITY", "REPOS"], [[stringValue(target.id), stringValue(source.stage), stringValue(source.statusAuthority), stringValue(source.repositoryCount)]]),
"",
"REPOSITORIES",
...(repos.length === 0 ? ["-"] : table(["KEY", "UPSTREAM", "BRANCH", "GITEA_READ_URL", "LEGACY"], repos)),
"",
"RESPONSIBILITIES",
...(responsibilities.length === 0 ? ["-"] : table(["NAME", "CURRENT", "TARGET", "DISPOSITION"], responsibilities)),
"",
"NEXT",
` status: ${stringValue(next.status)}`,
` full: ${stringValue(next.statusFull)}`,
` webhook-status: ${stringValue(next.webhookStatus)}`,
` fix-automatic-delivery: ${stringValue(next.fixAutomaticDelivery)}`,
"",
"Disclosure: credentials are summarized by presence/fingerprint only.",
]);
}
export function renderMirrorBootstrap(result: Record<string, unknown>): RenderedCliResult {
const repos = arrayRecords(result.repositories).map((repo) => [stringValue(repo.key), stringValue(repo.owner), stringValue(repo.name), boolText(record(repo.create).ok)]);
const next = record(result.next);
return rendered(result, "platform-infra gitea mirror bootstrap", [
"PLATFORM-INFRA GITEA MIRROR BOOTSTRAP",
...table(["OK", "MUTATION", "TARGET"], [[boolText(result.ok), boolText(result.mutation), stringValue(record(result.target).id)]]),
"",
"REPOSITORIES",
...(repos.length === 0 ? ["-"] : table(["KEY", "OWNER", "REPO", "API_OK"], repos)),
...remoteErrorLines(result),
"",
`NEXT ${stringValue(next.webhookStatus)}`,
]);
}
export function renderMirrorSync(result: Record<string, unknown>): RenderedCliResult {
const bundles = arrayRecords(result.sourceBundles).map((bundle) => [stringValue(bundle.key), stringValue(bundle.sourceCommit).slice(0, 12), stringValue(bundle.snapshotRef), stringValue(bundle.readUrl)]);
const repos = arrayRecords(result.repositories).map((repo) => [
stringValue(repo.key),
boolText(repo.syncOk),
stringValue(repo.fetchRc),
stringValue(repo.pushRc),
stringValue(repo.gitopsPushRc),
stringValue(repo.sourceCommit).slice(0, 12),
compactTail(stringValue(repo.fetchTail) || stringValue(repo.pushTail) || stringValue(repo.revparseTail)),
]);
const next = record(result.next);
return rendered(result, "platform-infra gitea mirror sync", [
"PLATFORM-INFRA GITEA MIRROR SYNC",
...table(["OK", "MUTATION", "TARGET"], [[boolText(result.ok), boolText(result.mutation), stringValue(record(result.target).id)]]),
"",
"REPOSITORIES",
...(repos.length === 0 ? ["-"] : table(["KEY", "SYNC", "FETCH", "PUSH", "GITOPS", "COMMIT", "ERROR"], repos)),
"",
"SOURCE BUNDLES",
...(bundles.length === 0 ? ["-"] : table(["KEY", "COMMIT", "SNAPSHOT_REF", "READ_URL"], bundles)),
...remoteErrorLines(result),
"",
`NEXT ${stringValue(next.status)}`,
]);
}
export function renderMirrorStatus(result: Record<string, unknown>): RenderedCliResult {
const source = record(result.sourceAuthority);
const service = record(result.serviceHealth);
const repos = arrayRecords(result.repositories).map((repo) => [stringValue(repo.key), stringValue(repo.mirrorState), stringValue(repo.branchCommit).slice(0, 12), stringValue(repo.snapshotCommit).slice(0, 12), stringValue(repo.snapshotRef)]);
const next = record(result.next);
return rendered(result, "platform-infra gitea mirror status", [
"PLATFORM-INFRA GITEA MIRROR STATUS",
...table(["TARGET", "SERVICE", "MIRROR_READY", "AUTHORITY"], [[stringValue(record(result.target).id), boolText(service.ok), boolText(source.mirrorReady), stringValue(source.statusAuthority)]]),
"",
"REPOSITORIES",
...(repos.length === 0 ? ["-"] : table(["KEY", "STATE", "BRANCH", "SNAPSHOT", "SNAPSHOT_REF"], repos)),
...remoteErrorLines(result),
"",
`NEXT ${stringValue(next.webhookStatus)}`,
]);
}
export function renderMirrorWebhookApply(result: Record<string, unknown>): RenderedCliResult {
const repos = arrayRecords(result.repositories).map((repo) => [stringValue(repo.key), stringValue(repo.repository), boolText(repo.hookReady), stringValue(repo.hookId), compactTail(stringValue(repo.error))]);
const next = record(result.next);
return rendered(result, "platform-infra gitea mirror webhook apply", [
"PLATFORM-INFRA GITEA MIRROR WEBHOOK APPLY",
...table(["OK", "MUTATION", "TARGET", "URL"], [[boolText(result.ok), boolText(result.mutation), stringValue(record(result.target).id), stringValue(record(result.webhook).publicUrl)]]),
"",
"GITHUB HOOKS",
...(repos.length === 0 ? ["-"] : table(["KEY", "REPOSITORY", "READY", "HOOK_ID", "ERROR"], repos)),
...remoteErrorLines(result),
"",
`NEXT ${stringValue(next.webhookStatus)}`,
]);
}
export function renderMirrorWebhookStatus(result: Record<string, unknown>, disclosure: Record<string, unknown>): RenderedCliResult {
const repos = arrayRecords(result.repositories).map((repo) => {
const latestPush = record(repo.latestPushDelivery);
const latest = record(repo.latestDelivery);
const delivery = latestPush.event !== "" ? latestPush : latest;
const inbox = record(repo.durableInboxRecord);
const inboxResult = record(inbox.result);
const inboxState = stringValue(repo.durableInboxState, "missing");
const durableProof = repo.preDurableBootstrap === true
? "pre-durable-bootstrap"
: repo.durableInboxCommitted === true
? `${stringValue(inboxResult.disposition)}:${stringValue(inboxResult.sourceCommit).slice(0, 12)}`
: "-";
return [
stringValue(repo.key),
boolText(repo.hookReady),
stringValue(repo.githubHead).slice(0, 12),
stringValue(repo.branchCommit).slice(0, 12),
stringValue(repo.snapshotCommit).slice(0, 12),
boolText(repo.authorityMatches),
boolText(repo.bridgeEventStale),
`${stringValue(repo.deliveryId)}:${stringValue(delivery.statusCode)}:${stringValue(delivery.status)}`,
inboxState,
durableProof,
compactTail(stringValue(repo.error)),
];
});
const bridge = record(result.bridge);
const bridgeLogs = record(result.bridgeLogs);
const retry = record(record(record(result.webhook).bridge).retry);
const retryText = `${stringValue(retry.maxAttempts)}x/${stringValue(retry.initialDelayMs)}-${stringValue(retry.maxDelayMs)}ms`;
const selection = record(disclosure.selection);
const selectedRepoKey = stringValue(selection.selectedRepoKey, "");
const delivery = arrayRecords(disclosure.deliveries)[0];
const deliveryResult = record(delivery?.result);
const retryDetail = record(disclosure.retry);
const logs = record(disclosure.logs);
const logRows = arrayRecords(logs.events).map((event) => [
stringValue(event.syncAttempt),
boolText(event.ok),
boolText(event.terminal),
stringValue(event.disposition),
stringValue(event.elapsedMs),
compactLine(stringValue(event.error)),
]);
const firstError = record(record(disclosure.diagnostics).firstError);
const repositoryCommands = arrayRecords(record(disclosure.next).repositories).map((item) => [
stringValue(item.key),
stringValue(item.deliveryId),
stringValue(item.command),
]);
const drillDown = selectedRepoKey === ""
? [
"",
"DRILL-DOWN",
...(repositoryCommands.length === 0 ? ["-"] : table(["REPOSITORY", "DELIVERY", "COMMAND"], repositoryCommands)),
]
: [
"",
"DELIVERY",
...(delivery === undefined ? ["-"] : table(["ID", "REPOSITORY", "STATE", "ATTEMPTS", "NEXT", "COMMIT", "DISPOSITION"], [[
stringValue(delivery.deliveryId),
stringValue(delivery.repo),
stringValue(delivery.state),
`${stringValue(retryDetail.cycleAttempt)}/${stringValue(retryDetail.totalAttempts)}`,
stringValue(retryDetail.nextAttemptAt),
stringValue(deliveryResult.sourceCommit).slice(0, 12),
stringValue(deliveryResult.disposition),
]])),
"",
"BRIDGE EVENTS",
...(logRows.length === 0 ? ["-"] : table(["ATTEMPT", "OK", "TERMINAL", "DISPOSITION", "ELAPSED_MS", "ERROR"], logRows)),
...(firstError.message === undefined ? [] : ["", `FIRST ERROR ${stringValue(firstError.domain)}: ${stringValue(firstError.message)}`]),
];
return rendered(disclosure, "platform-infra gitea mirror webhook status", [
"PLATFORM-INFRA GITEA MIRROR WEBHOOK STATUS",
...table(["TARGET", "BRIDGE", "READY", "RETRY", "URL", "LOGS"], [[stringValue(record(result.target).id), stringValue(bridge.deployment), boolText(bridge.ready), retryText, stringValue(record(result.webhook).publicUrl), `rc=${stringValue(bridgeLogs.exitCode)}`]]),
"",
"GITHUB HOOKS",
...(repos.length === 0 ? ["-"] : table(["KEY", "HOOK", "GH_HEAD", "GITEA", "SNAPSHOT", "MATCH", "STALE", "DELIVERY", "DURABLE", "PROOF", "ERROR"], repos)),
...drillDown,
...remoteErrorLines(result),
"",
`NEXT ${stringValue(record(disclosure.next).full)}`,
]);
}
export function renderPlan(result: Record<string, unknown>): RenderedCliResult {
const config = record(result.config);
const target = record(config.target);
const app = record(config.app);
const migration = record(config.migration);
const policy = arrayRecords(result.policy);
const failed = policy.filter((item) => item.ok === false);
const next = record(result.next);
return rendered(result, "platform-infra gitea plan", [
"PLATFORM-INFRA GITEA PLAN",
...table(["FIELD", "VALUE", "DETAIL", "VALUE"], [
["TARGET", stringValue(target.id), "route", stringValue(target.route)],
["NAMESPACE", stringValue(target.namespace), "role", stringValue(target.role)],
["IMAGE", stringValue(app.image), "replicas", stringValue(app.replicas)],
["SERVICE", `${stringValue(app.serviceName)}:${stringValue(record(app.service).httpPort)}`, "dns", serviceDnsFromObjects(app, target)],
["WEBUI", stringValue(record(app.publicExposure).publicBaseUrl), "internalGit", serviceDnsFromObjects(app, target)],
["ACTIONS", boolText(app.actionsEnabled), "registrationDisabled", boolText(app.registrationDisabled)],
["MIGRATION", stringValue(migration.role), "replaces", stringValue(migration.replaces)],
["POLICY", failed.length === 0 ? "ok" : `failed=${failed.length}`, "valuesPrinted", "false"],
]),
"",
"NEXT",
` status: ${stringValue(next.status)}`,
` validate: ${stringValue(next.validate)}`,
` mirror-status: ${stringValue(next.mirrorStatus)}`,
` webhook-status: ${stringValue(next.webhookStatus)}`,
` fix-automatic-delivery: ${stringValue(next.fixAutomaticDelivery)}`,
"",
"Boundary: Gitea is internal ClusterIP source authority for GH-1560; CI trigger authority is Pipelines-as-Code, not Gitea Actions/act_runner.",
"Disclosure: Secret values are not printed; this stage does not create runner credentials.",
]);
}
export function renderApply(result: Record<string, unknown>): RenderedCliResult {
const target = record(result.target);
const payload = record(result.payloadTransport);
const remote = record(result.remote);
const steps = record(remote.steps);
const serverDryRunStep = record(steps.serverDryRun);
const frpcStep = record(steps.frpcSecret);
const frpcCleanupStep = record(steps.frpcCleanup);
const webhookSecretStep = record(steps.webhookSyncSecret);
const applyStep = record(steps.apply);
const rolloutStep = record(steps.rollout);
const frpcRolloutStep = record(steps.frpcRollout);
const webhookRolloutStep = record(steps.webhookSyncRollout);
const caddy = record(result.pk01Caddy);
return rendered(result, "platform-infra gitea apply", [
"PLATFORM-INFRA GITEA APPLY",
...table(["TARGET", "NAMESPACE", "MODE", "OK"], [[stringValue(target.id), stringValue(target.namespace), stringValue(result.mode), boolText(result.ok)]]),
...table(["TRANSPORT", "BYTES", "MATERIALIZATION", "VALUES"], [[stringValue(payload.kind), stringValue(payload.totalBytes), stringValue(payload.materialization), stringValue(payload.valuesPrinted)]]),
"",
"STEPS",
...table(["STEP", "EXIT", "DETAIL"], [
["serverDryRun", stringValue(serverDryRunStep.exitCode), compactLine(stringValue(serverDryRunStep.stderrTail, stringValue(serverDryRunStep.stdoutTail)))],
["frpc-secret", stringValue(frpcStep.exitCode), compactLine(stringValue(frpcStep.stderrTail, stringValue(frpcStep.stdoutTail)))],
["frpc-cleanup", stringValue(frpcCleanupStep.exitCode), compactLine(stringValue(frpcCleanupStep.stderrTail, stringValue(frpcCleanupStep.stdoutTail)))],
["webhook-secret", stringValue(webhookSecretStep.exitCode), compactLine(stringValue(webhookSecretStep.stderrTail, stringValue(webhookSecretStep.stdoutTail)))],
["apply", stringValue(applyStep.exitCode), compactLine(stringValue(applyStep.stderrTail, stringValue(applyStep.stdoutTail)))],
["rollout", stringValue(rolloutStep.exitCode), compactLine(stringValue(rolloutStep.stderrTail, stringValue(rolloutStep.stdoutTail)))],
["frpc-rollout", stringValue(frpcRolloutStep.exitCode), compactLine(stringValue(frpcRolloutStep.stderrTail, stringValue(frpcRolloutStep.stdoutTail)))],
["webhook-rollout", stringValue(webhookRolloutStep.exitCode), compactLine(stringValue(webhookRolloutStep.stderrTail, stringValue(webhookRolloutStep.stdoutTail)))],
["pk01-caddy", stringValue(caddy.exitCode ?? (caddy.ok === false ? 1 : 0)), compactLine(stringValue(caddy.error, stringValue(caddy.status, stringValue(caddy.reason))))],
]),
...remoteErrorLines(result),
"",
`NEXT bun scripts/cli.ts platform-infra gitea status --target ${stringValue(target.id)}`,
]);
}
export function renderStatus(result: Record<string, unknown>): RenderedCliResult {
const summary = record(result.summary);
const statefulSet = record(summary.statefulSet);
const service = record(summary.service);
const networkPolicy = record(summary.networkPolicy);
const pods = arrayRecords(summary.pods).map((pod) => [stringValue(pod.name), stringValue(pod.phase), boolText(pod.ready), stringValue(pod.restarts)]);
const pvcs = arrayRecords(summary.pvcs).map((pvc) => [stringValue(pvc.name), stringValue(pvc.phase), stringValue(pvc.capacity)]);
return rendered(result, "platform-infra gitea status", [
"PLATFORM-INFRA GITEA STATUS",
...table(["TARGET", "ROUTE", "NAMESPACE", "READY"], [[stringValue(summary.target), stringValue(summary.route), stringValue(summary.namespace), boolText(summary.ready)]]),
"",
"CONTROL",
...table(["CHECK", "VALUE", "DETAIL"], [
["statefulSet", `${stringValue(statefulSet.readyReplicas)}/${stringValue(statefulSet.desired)}`, stringValue(statefulSet.name)],
["service", stringValue(service.type), stringValue(summary.serviceDns)],
["endpoints", boolText(summary.endpointsReady), stringValue(service.clusterIP)],
["allow-all", boolText(networkPolicy.allowAllPresent), "NetworkPolicy"],
]),
"",
"PODS",
...(pods.length === 0 ? ["-"] : table(["POD", "PHASE", "READY", "RESTARTS"], pods)),
"",
"PVCS",
...(pvcs.length === 0 ? ["-"] : table(["PVC", "PHASE", "CAPACITY"], pvcs)),
...remoteErrorLines(result),
"",
`NEXT bun scripts/cli.ts platform-infra gitea validate --target ${stringValue(summary.target)}`,
]);
}
function remoteErrorLines(result: Record<string, unknown>): string[] {
if (result.ok !== false) return [];
const remote = record(result.remote);
const exitCode = stringValue(remote.exitCode);
const stderr = compactLine(stringValue(remote.stderrTail));
const stdout = compactLine(stringValue(remote.stdoutTail));
const detail = stderr !== "-" ? stderr : stdout;
return ["", "ERROR", ...table(["EXIT", "DETAIL"], [[exitCode, detail]])];
}
function compactTail(value: string): string {
const line = value.split(/\r?\n/u).map((item) => item.trim()).filter(Boolean).at(-1) ?? "";
return line.length > 96 ? `${line.slice(0, 93)}...` : line;
}
function rendered(result: Record<string, unknown>, command: string, lines: string[]): RenderedCliResult {
return { ok: result.ok !== false, command, renderedText: lines.join("\n"), contentType: "text/plain" };
}
function serviceDnsFromObjects(app: Record<string, unknown>, target: Record<string, unknown>): string {
const service = record(app.service);
return `${stringValue(app.serviceName)}.${stringValue(target.namespace)}.svc.cluster.local:${stringValue(service.httpPort)}`;
}
function record(value: unknown): Record<string, unknown> {
return typeof value === "object" && value !== null && !Array.isArray(value) ? value as Record<string, unknown> : {};
}
function arrayRecords(value: unknown): Record<string, unknown>[] {
return Array.isArray(value)
? value.filter((item) => typeof item === "object" && item !== null && !Array.isArray(item)) as Record<string, unknown>[]
: [];
}
function stringValue(value: unknown, fallback = "-"): string {
if (typeof value === "string" && value.length > 0) return value;
if (typeof value === "number" || typeof value === "boolean") return String(value);
return fallback;
}
function boolText(value: unknown): string {
return value === true ? "true" : "false";
}
function compactLine(value: string): string {
const trimmed = value.replace(/\s+/gu, " ").trim();
return trimmed.length > 0 ? trimmed.slice(0, 220) : "-";
}
function table(headers: string[], rows: string[][]): string[] {
const widths = headers.map((header, index) => Math.max(header.length, ...rows.map((row) => (row[index] ?? "").length)));
const format = (row: string[]) => row.map((cell, index) => cell.padEnd(widths[index])).join(" ");
return [format(headers), format(headers.map((header, index) => "-".repeat(Math.max(header.length, widths[index])))), ...rows.map(format)];
}