feat: 收敛 PaC 首发 bootstrap

This commit is contained in:
Codex
2026-07-13 09:27:42 +02:00
parent 72e2f13587
commit 8c216a02df
9 changed files with 393 additions and 11 deletions
+11 -4
View File
@@ -27,17 +27,24 @@ export function renderMirrorPlan(result: Record<string, unknown>): RenderedCliRe
}
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 repos = arrayRecords(result.repositories).map((repo) => [
stringValue(repo.key),
stringValue(repo.owner, stringValue(repo.giteaOwner)),
stringValue(repo.name, stringValue(repo.giteaRepo)),
result.mode === "dry-run" ? "planned" : boolText(record(repo.create).ok),
]);
const blockers = Array.isArray(result.blockers) ? result.blockers.map(String) : [];
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)]]),
...table(["OK", "MODE", "MUTATION", "TARGET"], [[boolText(result.ok), stringValue(result.mode), boolText(result.mutation), stringValue(record(result.target).id)]]),
"",
"REPOSITORIES",
...(repos.length === 0 ? ["-"] : table(["KEY", "OWNER", "REPO", "API_OK"], repos)),
...(repos.length === 0 ? ["-"] : table(["KEY", "OWNER", "REPO", "STATE"], repos)),
...(blockers.length === 0 ? [] : ["", `BLOCKERS ${blockers.join(",")}`]),
...remoteErrorLines(result),
"",
`NEXT ${stringValue(next.webhookStatus)}`,
`NEXT ${stringValue(result.mode === "dry-run" ? next.confirm : next.webhookStatus)}`,
]);
}
+37 -2
View File
@@ -71,6 +71,8 @@ interface ApplyOptions extends CommonOptions {
interface MirrorOptions extends CommonOptions {
confirm: boolean;
dryRun: boolean;
wait: boolean;
repoKey: string | null;
}
@@ -127,6 +129,7 @@ export function giteaHelp(scope?: string): Record<string, unknown> {
usage: [
"bun scripts/cli.ts platform-infra gitea apply --target <NODE> --dry-run",
"bun scripts/cli.ts platform-infra gitea apply --target <NODE> --confirm",
"bun scripts/cli.ts platform-infra gitea mirror bootstrap --target <NODE> --dry-run",
"bun scripts/cli.ts platform-infra gitea mirror bootstrap --target <NODE> --confirm",
"bun scripts/cli.ts platform-infra gitea mirror webhook apply --target <NODE> --confirm",
],
@@ -357,8 +360,28 @@ async function mirrorStatus(config: UniDeskConfig, options: CommonOptions & { re
async function mirrorBootstrap(config: UniDeskConfig, options: MirrorOptions): Promise<Record<string, unknown>> {
const gitea = readGiteaConfig();
const target = resolveCommandTarget(gitea, options.targetId);
if (!options.confirm) return { ok: false, action: "platform-infra-gitea-mirror-bootstrap", mutation: false, mode: "missing-confirm", error: "mirror bootstrap requires --confirm" };
const repos = selectedRepositories(gitea, target, options.repoKey);
if (!options.confirm) {
const credentials = credentialSummaries(gitea);
const blockers = credentials
.filter((item) => item.id === "github-upstream" && (item.present !== true || item.requiredKeysPresent !== true))
.map((item) => String(item.id));
const repoFlag = options.repoKey === null ? "" : ` --repo ${options.repoKey}`;
return {
ok: blockers.length === 0,
action: "platform-infra-gitea-mirror-bootstrap",
mutation: false,
mode: "dry-run",
target: targetSummary(target),
repositories: repos.map((repo) => repositorySummary(gitea, repo)),
credentials,
blockers,
next: {
...mirrorReadOnlyNextCommands(target.id),
confirm: `bun scripts/cli.ts platform-infra gitea mirror bootstrap --target ${target.id}${repoFlag} --confirm`,
},
};
}
const secrets = ensureMirrorSecrets(gitea, true, true);
const result = await capture(config, target.route, ["sh"], remoteScript("mirror-bootstrap", gitea, target, "", { ...options, dryRun: false, wait: false }, { repos, secrets }).script);
const parsed = parseJsonOutput(result.stdout);
@@ -366,6 +389,7 @@ async function mirrorBootstrap(config: UniDeskConfig, options: MirrorOptions): P
ok: result.exitCode === 0 && parsed?.ok === true,
action: "platform-infra-gitea-mirror-bootstrap",
mutation: true,
mode: "confirmed",
target: targetSummary(target),
repositories: arrayRecords(record(parsed).repositories),
credentials: credentialSummaries(gitea),
@@ -1759,11 +1783,14 @@ function parseMirrorWebhookStatusOptions(args: string[]): MirrorWebhookStatusOpt
function parseMirrorOptions(args: string[]): MirrorOptions {
const commonArgs: string[] = [];
let confirm = false;
let dryRun = false;
let repoKey: string | null = null;
for (let index = 0; index < args.length; index += 1) {
const arg = args[index];
if (arg === "--confirm") {
confirm = true;
} else if (arg === "--dry-run") {
dryRun = true;
} else if (arg === "--repo") {
const value = args[index + 1];
if (value === undefined || value.startsWith("--")) throw new CliInputError("--repo requires a value", {
@@ -1786,7 +1813,15 @@ function parseMirrorOptions(args: string[]): MirrorOptions {
}
}
}
return { ...parseCommonOptions(commonArgs), confirm, repoKey };
if (confirm && dryRun) throw new CliInputError("Gitea mirror bootstrap accepts only one of --confirm or --dry-run", {
code: "mutually-exclusive-options",
argument: "--confirm --dry-run",
usage: [
"bun scripts/cli.ts platform-infra gitea mirror bootstrap --target <node> --dry-run",
"bun scripts/cli.ts platform-infra gitea mirror bootstrap --target <node> --confirm",
],
});
return { ...parseCommonOptions(commonArgs), confirm, dryRun: dryRun || !confirm, wait: false, repoKey };
}
function parseCommonOptions(args: string[]): CommonOptions {
@@ -0,0 +1,80 @@
import { expect, test } from "bun:test";
import { readFileSync } from "node:fs";
import { resolve } from "node:path";
import type { GiteaConfig, GiteaMirrorRepository } from "./platform-infra-gitea-config";
import { renderMirrorBootstrap } from "./platform-infra-gitea-render";
import { resolvePacBootstrapMirrorRepository } from "./platform-infra-pipelines-as-code-bootstrap";
import { runPlatformInfraPipelinesAsCodeCommand } from "./platform-infra-pipelines-as-code";
const mirror: GiteaMirrorRepository = {
key: "widgets-nc01",
targetId: "NC01",
upstream: { repository: "acme/widgets", cloneUrl: "https://github.com/acme/widgets.git", branch: "main", visibility: "public" },
gitea: { owner: "mirrors", name: "acme-widgets", mirrorMode: "controlled-push", publicRead: true, readUrl: "http://gitea-http.devops-infra.svc.cluster.local:3000/mirrors/acme-widgets.git" },
gitops: { branch: "main", flushDisposition: "not-a-gitops-branch" },
snapshot: { prefix: "refs/unidesk/snapshots/widgets-main-nc01" },
legacyGitMirror: null,
};
function fixtureConfig(repositories: GiteaMirrorRepository[]): GiteaConfig {
return { sourceAuthority: { repositories } } as unknown as GiteaConfig;
}
test("PaC bootstrap uses one exact YAML-owned Gitea repository", () => {
const selected = resolvePacBootstrapMirrorRepository(fixtureConfig([mirror]), "nc01", {
id: "widgets-nc01",
owner: "mirrors",
repo: "acme-widgets",
cloneUrl: `${mirror.gitea.readUrl}/`,
});
expect(selected.key).toBe("widgets-nc01");
expect(() => resolvePacBootstrapMirrorRepository(fixtureConfig([]), "NC01", {
id: "widgets-nc01",
owner: "mirrors",
repo: "acme-widgets",
cloneUrl: mirror.gitea.readUrl,
})).toThrow("no exact match");
expect(() => resolvePacBootstrapMirrorRepository(fixtureConfig([mirror, { ...mirror }]), "NC01", {
id: "widgets-nc01",
owner: "mirrors",
repo: "acme-widgets",
cloneUrl: mirror.gitea.readUrl,
})).toThrow("2 exact matches");
});
test("Gitea mirror bootstrap defaults to a visible dry-run instead of an empty refusal", () => {
const rendered = renderMirrorBootstrap({
ok: true,
action: "platform-infra-gitea-mirror-bootstrap",
mode: "dry-run",
mutation: false,
target: { id: "NC01" },
repositories: [{ key: "widgets-nc01", giteaOwner: "mirrors", giteaRepo: "acme-widgets" }],
blockers: [],
next: { confirm: "bun scripts/cli.ts platform-infra gitea mirror bootstrap --target NC01 --repo widgets-nc01 --confirm" },
}).renderedText;
expect(rendered).toContain("MODE MUTATION TARGET");
expect(rendered).toContain("widgets-nc01");
expect(rendered).toContain("--repo widgets-nc01 --confirm");
expect(rendered).not.toContain("missing-confirm");
});
test("PaC apply checks the Gitea repository before dry-run return and cluster mutation", () => {
const remote = readFileSync(resolve(import.meta.dir, "platform-infra-pipelines-as-code-remote.sh"), "utf8");
const action = remote.indexOf("apply_action() {");
const preflight = remote.indexOf("if ! repository_preflight", action);
const dryRun = remote.indexOf('if [ "$UNIDESK_PAC_DRY_RUN" = "1" ]', action);
const release = remote.indexOf(" apply_release", action);
expect(action).toBeGreaterThanOrEqual(0);
expect(preflight).toBeGreaterThan(action);
expect(dryRun).toBeGreaterThan(preflight);
expect(release).toBeGreaterThan(dryRun);
expect(remote).toContain('"error":"gitea-repository-missing"');
});
test("platform bootstrap help advertises the composite entry before low-level apply", async () => {
const help = await runPlatformInfraPipelinesAsCodeCommand({} as never, ["help", "platform-bootstrap"]);
const usage = (help as Record<string, unknown>).usage as string[];
expect(usage[0]).toContain("pipelines-as-code bootstrap");
expect(usage).toContain("bun scripts/cli.ts platform-infra pipelines-as-code bootstrap --target <NODE> --consumer <consumer> --confirm");
});
@@ -0,0 +1,35 @@
import type { GiteaConfig, GiteaMirrorRepository } from "./platform-infra-gitea-config";
export interface PacBootstrapRepositoryIdentity {
readonly id: string;
readonly owner: string;
readonly repo: string;
readonly cloneUrl: string;
}
export function resolvePacBootstrapMirrorRepository(
gitea: GiteaConfig,
targetId: string,
repository: PacBootstrapRepositoryIdentity,
): GiteaMirrorRepository {
const candidates = gitea.sourceAuthority.repositories.filter((item) => (
item.targetId.toLowerCase() === targetId.toLowerCase()
&& item.gitea.owner === repository.owner
&& item.gitea.name === repository.repo
&& repositoryUrlIdentity(item.gitea.readUrl) === repositoryUrlIdentity(repository.cloneUrl)
));
if (candidates.length !== 1) {
const disposition = candidates.length === 0 ? "no exact match" : `${candidates.length} exact matches`;
throw new Error(
`cannot resolve the YAML-owned Gitea bootstrap repository for PaC repository ${repository.id} on ${targetId}: ${disposition}; `
+ "match targetId, gitea owner/name, and read URL in config/platform-infra/gitea.yaml",
);
}
return candidates[0];
}
function repositoryUrlIdentity(value: string): string {
const parsed = new URL(value);
const path = parsed.pathname.replace(/\/+$/u, "");
return `${parsed.protocol}//${parsed.host}${path}`;
}
@@ -100,6 +100,37 @@ gitea_api() {
return 22
}
gitea_api_status() {
method="$1"
path="$2"
status=$(curl -sS -o /dev/null -w '%{http_code}' \
-u "$UNIDESK_PAC_GITEA_ADMIN_USERNAME:$UNIDESK_PAC_GITEA_ADMIN_PASSWORD" \
-X "$method" \
"$(api_url "$path")" 2>/dev/null) || status=000
printf '%s' "${status:-000}"
}
repository_preflight() {
repository_path="repos/$UNIDESK_PAC_GITEA_OWNER/$UNIDESK_PAC_GITEA_REPO"
repository_status=$(gitea_api_status GET "$repository_path")
case "$repository_status" in
2*) return 0 ;;
404)
printf '{"ok":false,"mode":"preflight-blocked","mutation":false,"error":"gitea-repository-missing","preflight":{"repositoryExists":false,"httpStatus":"404","repository":"%s/%s","valuesPrinted":false},"valuesPrinted":false}\n' \
"$(json_string "$UNIDESK_PAC_GITEA_OWNER")" \
"$(json_string "$UNIDESK_PAC_GITEA_REPO")"
return 22
;;
*)
printf '{"ok":false,"mode":"preflight-blocked","mutation":false,"error":"gitea-repository-preflight-failed","preflight":{"repositoryExists":false,"httpStatus":"%s","repository":"%s/%s","valuesPrinted":false},"valuesPrinted":false}\n' \
"$(json_string "$repository_status")" \
"$(json_string "$UNIDESK_PAC_GITEA_OWNER")" \
"$(json_string "$UNIDESK_PAC_GITEA_REPO")"
return 22
;;
esac
}
ensure_token() {
name="unidesk-pac-$(date +%Y%m%d%H%M%S)"
body=$(printf '{"name":"%s","scopes":["all"]}' "$name")
@@ -268,8 +299,17 @@ wait_ready() {
}
apply_action() {
preflight_tmp=$(mktemp)
if ! repository_preflight >"$preflight_tmp"; then
cat "$preflight_tmp"
rm -f "$preflight_tmp"
return 22
fi
rm -f "$preflight_tmp"
if [ "$UNIDESK_PAC_DRY_RUN" = "1" ]; then
printf '{"ok":true,"mode":"dry-run","mutation":false,"valuesPrinted":false}\n'
printf '{"ok":true,"mode":"dry-run","mutation":false,"preflight":{"repositoryExists":true,"httpStatus":"2xx","repository":"%s/%s","valuesPrinted":false},"valuesPrinted":false}\n' \
"$(json_string "$UNIDESK_PAC_GITEA_OWNER")" \
"$(json_string "$UNIDESK_PAC_GITEA_REPO")"
return
fi
apply_release
@@ -311,7 +351,7 @@ apply_action() {
hook_id=$(ensure_webhook)
ready=false
if wait_ready; then ready=true; fi
printf '{"ok":%s,"mode":"confirmed","mutation":true,"controllerReady":%s,"argoBootstrap":%s,"webhook":{"present":true,"id":"%s","url":"%s"},"repository":"%s","namespace":"%s","valuesPrinted":false}\n' "$ready" "$ready" "$argo_bootstrap" "$(json_string "$hook_id")" "$(json_string "$UNIDESK_PAC_WEBHOOK_URL")" "$(json_string "$UNIDESK_PAC_REPOSITORY_NAME")" "$(json_string "$UNIDESK_PAC_TARGET_NAMESPACE")"
printf '{"ok":%s,"mode":"confirmed","mutation":true,"controllerReady":%s,"argoBootstrap":%s,"preflight":{"repositoryExists":true,"httpStatus":"2xx","repository":"%s/%s","valuesPrinted":false},"webhook":{"present":true,"id":"%s","url":"%s"},"repository":"%s","namespace":"%s","valuesPrinted":false}\n' "$ready" "$ready" "$argo_bootstrap" "$(json_string "$UNIDESK_PAC_GITEA_OWNER")" "$(json_string "$UNIDESK_PAC_GITEA_REPO")" "$(json_string "$hook_id")" "$(json_string "$UNIDESK_PAC_WEBHOOK_URL")" "$(json_string "$UNIDESK_PAC_REPOSITORY_NAME")" "$(json_string "$UNIDESK_PAC_TARGET_NAMESPACE")"
}
condition_status() {
+129 -2
View File
@@ -28,6 +28,9 @@ import {
} from "./platform-infra-pipelines-as-code-source-artifact";
import { pacAdmissionDesiredIdentity } from "./platform-infra-pac-provenance";
import { pacConsumerRbacDesiredIdentity, renderPacConsumerRbacDesiredFragment } from "./platform-infra-pac-consumer-rbac";
import { runPlatformInfraGiteaCommand } from "./platform-infra-gitea";
import { readGiteaConfig } from "./platform-infra-gitea-config";
import { resolvePacBootstrapMirrorRepository } from "./platform-infra-pipelines-as-code-bootstrap";
const configFile = rootPath("config", "platform-infra", "pipelines-as-code.yaml");
const configLabel = "config/platform-infra/pipelines-as-code.yaml";
@@ -240,6 +243,11 @@ export async function runPlatformInfraPipelinesAsCodeCommand(config: UniDeskConf
const result = await apply(config, options);
return options.json || options.full || options.raw ? result : renderApply(result);
}
if (action === "bootstrap") {
const options = parseApplyOptions(args.slice(1));
const result = await bootstrap(config, options);
return options.json || options.full || options.raw ? result : renderBootstrap(result);
}
if (action === "status") {
const options = parseCommonOptions(args.slice(1));
const result = await status(config, options);
@@ -353,10 +361,12 @@ function help(scope: string | null): Record<string, unknown> {
scope: "explicit-initial-platform-bootstrap",
configTruth: configLabel,
usage: [
"bun scripts/cli.ts platform-infra pipelines-as-code bootstrap --target <NODE> --consumer <consumer> --dry-run",
"bun scripts/cli.ts platform-infra pipelines-as-code bootstrap --target <NODE> --consumer <consumer> --confirm",
"bun scripts/cli.ts platform-infra pipelines-as-code apply --target <NODE> --dry-run",
"bun scripts/cli.ts platform-infra pipelines-as-code apply --target <NODE> --confirm",
],
boundary: "仅用于首次安装或显式平台 bootstrap;不得在 source PR 合并后用于交付、恢复或补跑。",
boundary: "bootstrap 是首次引导的最短入口;apply 保留为单层维护入口。两者都不得在 source PR 合并后用于交付、恢复或补跑。",
mutation: "explicit-confirm-required",
};
}
@@ -889,7 +899,7 @@ async function apply(config: UniDeskConfig, options: ApplyOptions): Promise<Reco
return {
ok: result.exitCode === 0 && parsed?.ok === true,
action: "platform-infra-pipelines-as-code-apply",
mutation: !options.dryRun,
mutation: options.dryRun ? false : parsed?.mutation !== false,
mode: options.dryRun ? "dry-run" : "confirmed",
target: targetSummary(target),
config: compactConfigSummary(pac, consumer, repository),
@@ -902,6 +912,79 @@ async function apply(config: UniDeskConfig, options: ApplyOptions): Promise<Reco
};
}
async function bootstrap(config: UniDeskConfig, options: ApplyOptions): Promise<Record<string, unknown>> {
const pac = readPacConfig();
const target = resolveTarget(pac, options.targetId);
const consumer = resolveConsumer(pac, options.consumerId);
if (consumer.node.toLowerCase() !== target.id.toLowerCase()) {
throw new Error(`Pipelines-as-Code consumer ${consumer.id} belongs to ${consumer.node}, not target ${target.id}`);
}
const repository = resolveRepository(pac, consumer.repositoryRef);
const mirror = resolvePacBootstrapMirrorRepository(readGiteaConfig(), target.id, repository);
const giteaArgs = ["mirror", "bootstrap", "--target", target.id, "--repo", mirror.key, ...(options.confirm ? ["--confirm"] : []), "--full"];
const giteaBootstrap = record(await runPlatformInfraGiteaCommand(config, giteaArgs));
if (options.confirm && giteaBootstrap.ok !== true) {
return bootstrapResult(target, consumer, repository, mirror, options, giteaBootstrap, {
ok: false,
mutation: false,
mode: "skipped",
error: "gitea-bootstrap-failed",
valuesPrinted: false,
});
}
const pacApply = await apply(config, options);
return bootstrapResult(target, consumer, repository, mirror, options, giteaBootstrap, pacApply);
}
function bootstrapResult(
target: PacTarget,
consumer: PacConsumer,
repository: PacRepository,
mirror: ReturnType<typeof resolvePacBootstrapMirrorRepository>,
options: ApplyOptions,
giteaBootstrap: Record<string, unknown>,
pacApply: Record<string, unknown>,
): Record<string, unknown> {
const remote = record(pacApply.remote);
const repositoryMissing = remote.error === "gitea-repository-missing";
const pacReady = pacApply.ok === true || (!options.confirm && repositoryMissing);
const ok = giteaBootstrap.ok === true && pacReady;
const confirm = `bun scripts/cli.ts platform-infra pipelines-as-code bootstrap --target ${target.id} --consumer ${consumer.id} --confirm`;
return {
ok,
action: "platform-infra-pipelines-as-code-bootstrap",
mutation: giteaBootstrap.mutation === true || pacApply.mutation === true,
mode: options.confirm ? "confirmed" : "dry-run",
target: targetSummary(target),
consumer: consumerObservationSummary(consumer),
repository: repositorySummary(repository),
mirrorRepository: {
key: mirror.key,
upstreamRepository: mirror.upstream.repository,
upstreamBranch: mirror.upstream.branch,
owner: mirror.gitea.owner,
repo: mirror.gitea.name,
valuesPrinted: false,
},
steps: [
{ id: "gitea-repository", ok: giteaBootstrap.ok === true, mutation: giteaBootstrap.mutation === true, mode: giteaBootstrap.mode, valuesPrinted: false },
{ id: "pac-tekton-gitops", ok: pacReady, mutation: pacApply.mutation === true, mode: pacApply.mode, repositoryMissing, valuesPrinted: false },
],
giteaBootstrap,
pipelinesAsCodeApply: pacApply,
next: options.confirm
? {
deliveryTrigger: `Merge the prepared GitHub PR for ${mirror.upstream.repository}@${mirror.upstream.branch}; the automatic chain owns mirror, PaC, Tekton, GitOps, Argo, and runtime convergence.`,
investigate: `bun scripts/cli.ts platform-infra pipelines-as-code status --target ${target.id} --consumer ${consumer.id}`,
}
: {
confirm,
boundary: "Run confirm only before the first source PR merge; do not use bootstrap as post-merge recovery.",
},
valuesPrinted: false,
};
}
async function status(config: UniDeskConfig, options: CommonOptions): Promise<Record<string, unknown>> {
const pac = readPacConfig();
const target = resolveTarget(pac, options.targetId);
@@ -2023,6 +2106,7 @@ function renderPlan(result: Record<string, unknown>): RenderedCliResult {
function renderApply(result: Record<string, unknown>): RenderedCliResult {
const target = record(result.target);
const remote = record(result.remote);
const preflight = record(remote.preflight);
const errorText = compactLine(stringValue(remote.stderrTail, stringValue(remote.error, stringValue(result.error, ""))));
const lines = [
"PLATFORM-INFRA PIPELINES-AS-CODE APPLY",
@@ -2030,7 +2114,13 @@ function renderApply(result: Record<string, unknown>): RenderedCliResult {
"",
"REMOTE",
...table(["CONTROLLER", "WEBHOOK", "REPOSITORY", "EXIT", "VALUES"], [[stringValue(remote.controllerReady), stringValue(record(remote.webhook).present), stringValue(remote.repository), stringValue(remote.exitCode, "0"), "false"]]),
...(Object.keys(preflight).length === 0 ? [] : [
"",
"PREFLIGHT",
...table(["GITEA_REPOSITORY", "HTTP", "EXISTS"], [[stringValue(preflight.repository), stringValue(preflight.httpStatus), boolText(preflight.repositoryExists)]]),
]),
...(result.ok === false && errorText.length > 0 ? ["", `ERROR ${errorText}`] : []),
...(remote.error === "gitea-repository-missing" ? ["HELP bun scripts/cli.ts platform-infra pipelines-as-code help platform-bootstrap"] : []),
"",
"NEXT",
` status: ${stringValue(record(result.next).status)}`,
@@ -2038,6 +2128,43 @@ function renderApply(result: Record<string, unknown>): RenderedCliResult {
return rendered(result, "platform-infra pipelines-as-code apply", lines);
}
function renderBootstrap(result: Record<string, unknown>): RenderedCliResult {
const target = record(result.target);
const consumer = record(result.consumer);
const mirror = record(result.mirrorRepository);
const steps = arrayRecords(result.steps);
const pacApply = record(result.pipelinesAsCodeApply);
const pacRemote = record(pacApply.remote);
const giteaBootstrap = record(result.giteaBootstrap);
const next = record(result.next);
const errorValue = [pacRemote.error, giteaBootstrap.error, pacApply.error]
.find((value): value is string => typeof value === "string" && value.length > 0) ?? "";
const error = errorValue.length === 0 ? "" : compactLine(errorValue);
const lines = [
"PLATFORM-INFRA PAC / TEKTON / GITOPS BOOTSTRAP",
...table(["TARGET", "CONSUMER", "MODE", "MUTATION", "OK"], [[stringValue(target.id), stringValue(consumer.id), stringValue(result.mode), boolText(result.mutation), boolText(result.ok)]]),
"",
"SOURCE AUTHORITY",
...table(["GITEA_KEY", "GITEA_REPOSITORY", "UPSTREAM", "BRANCH"], [[stringValue(mirror.key), `${stringValue(mirror.owner)}/${stringValue(mirror.repo)}`, stringValue(mirror.upstreamRepository), stringValue(mirror.upstreamBranch)]]),
"",
"STEPS",
...table(["STEP", "OK", "MODE", "MUTATION", "DETAIL"], steps.map((step) => [
stringValue(step.id),
boolText(step.ok),
stringValue(step.mode),
boolText(step.mutation),
step.repositoryMissing === true ? "repository-will-be-created-on-confirm" : "ready",
])),
...(error.length === 0 ? [] : ["", `ERROR ${error}`]),
"",
"NEXT",
...(result.mode === "dry-run"
? [` confirm: ${stringValue(next.confirm)}`, ` boundary: ${stringValue(next.boundary)}`]
: [` delivery: ${stringValue(next.deliveryTrigger)}`, ` investigate: ${stringValue(next.investigate)}`]),
];
return rendered(result, "platform-infra pipelines-as-code bootstrap", lines);
}
function renderStatus(result: Record<string, unknown>): RenderedCliResult {
const summary = record(result.summary);
const consumer = record(result.consumer);