import { appendFile } from "node:fs/promises"; import { cleanupFinalizedPromotion, finalizeVerifiedPromotion, } from "./promotion-stager.ts"; export async function runStageVerifiedPromotionCli( environment: NodeJS.ProcessEnv, dependencies: Readonly<{ cwd?: () => string; finalize?: typeof finalizeVerifiedPromotion; cleanup?: typeof cleanupFinalizedPromotion; appendOutput?: (path: string, content: string) => Promise; writeStdout?: (content: string) => void; }> = {}, ): Promise { const required = (name: string): string => { const value = environment[name]; if (!value) throw new TypeError(`promotion staging environment is missing ${name}`); return value; }; const attempt = Number( environment.GITEA_RUN_ATTEMPT ?? environment.GITHUB_RUN_ATTEMPT ?? required("CI_RUN_ATTEMPT"), ); if (!Number.isInteger(attempt) || attempt < 1 || attempt > 1_000) { throw new TypeError("promotion staging run attempt is invalid"); } const runnerTempRoot = required("RUNNER_TEMP"); const staged = await (dependencies.finalize ?? finalizeVerifiedPromotion)({ repositoryRoot: (dependencies.cwd ?? process.cwd)(), archivePath: required("CANDIDATE_ARCHIVE_PATH"), expectedArchiveSha256: required("CANDIDATE_ARCHIVE_SHA256"), vulnerabilityReportPath: required("VULNERABILITY_REPORT_PATH"), provenanceAttestationPath: required("PROVENANCE_ATTESTATION_PATH"), vulnerabilityPublicKeyPath: required("VULNERABILITY_PUBLIC_KEY_PATH"), vulnerabilityKeyId: required("VULNERABILITY_KEY_ID"), provenancePublicKeyPath: required("PROVENANCE_PUBLIC_KEY_PATH"), provenanceKeyId: required("PROVENANCE_KEY_ID"), expectedRun: { id: environment.GITEA_RUN_ID ?? environment.GITHUB_RUN_ID ?? required("CI_RUN_ID"), attempt, sourceRevision: environment.EXPECTED_SOURCE_REVISION ?? required("VITE_COMMIT_SHA"), }, vulnerabilityInvocationNonce: required("VULNERABILITY_INVOCATION_NONCE"), provenanceInvocationNonce: required("PROVENANCE_INVOCATION_NONCE"), runnerTempRoot, }); try { const output = required("GITHUB_OUTPUT"); const content = [ `staging_root=${staged.stagingRoot}`, `cleanup_token=${staged.cleanupToken}`, `runner_temp_dev=${staged.runnerTempIdentity.dev}`, `runner_temp_ino=${staged.runnerTempIdentity.ino}`, `staging_dev=${staged.stagingIdentity.dev}`, `staging_ino=${staged.stagingIdentity.ino}`, "", ].join("\n"); await (dependencies.appendOutput ?? defaultAppendOutput)(output, content); } catch (error) { try { await (dependencies.cleanup ?? cleanupFinalizedPromotion)({ runnerTempRoot, stagingRoot: staged.stagingRoot, cleanupToken: staged.cleanupToken, runnerTempIdentity: staged.runnerTempIdentity, stagingIdentity: staged.stagingIdentity, }); } catch (cleanupError) { throw new AggregateError( [error, cleanupError], "promotion output publication and direct staging cleanup both failed", { cause: cleanupError }, ); } throw error; } (dependencies.writeStdout ?? process.stdout.write.bind(process.stdout))( `Promotion staging: ${staged.files .map(({ name, sha256 }) => `${name}=${sha256}`) .join(", ")} PASS\n`, ); } async function defaultAppendOutput(path: string, content: string): Promise { await appendFile(path, content, { encoding: "utf8" }); }