UNPKG

yaba-release-cli

Version:

Yaba is a simple CLI tool that helps you manage releases of your Github projects.

94 lines (83 loc) 4.11 kB
import { spinner } from "../utils/spinner.js"; import { fetchCommitStatus, fetchCommitCheckRuns } from "../utils/github-api.js"; import { createError } from "../utils/errors.js"; import { exitCodes } from "../utils/exit-codes.js"; const PENDING_CHECK_RUN_STATUSES = new Set(['queued', 'in_progress']); const FAILED_CHECK_RUN_CONCLUSIONS = new Set(['failure', 'timed_out', 'action_required']); /** * Verifies that all GitHub status checks and Actions check runs on the target ref * have completed successfully before allowing a release to proceed. * * Checks two independent GitHub APIs: * - Commit Status API (/commits/{ref}/status): covers legacy status checks from * third-party integrations and older CI systems. * - Check Runs API (/commits/{ref}/check-runs): covers GitHub Actions workflows * and GitHub Apps. * * Throws a VALIDATION error if any check is still pending (indicating CI is still * running) or has failed (indicating something is broken on the target ref). * * @param owner the repository owner * @param repo the repository name * @param ref the resolved target commit-ish to verify * @param skip when true, bypasses the check entirely (--no-status-checks flag or config) * @param fetchStatus injectable function for fetching commit status (defaults to GitHub API) * @param fetchCheckRuns injectable function for fetching check runs (defaults to GitHub API) * @returns {Promise<void>} */ export async function verifyStatusChecks(owner, repo, ref, skip, { fetchStatus = fetchCommitStatus, fetchCheckRuns = fetchCommitCheckRuns } = {}) { if (skip === true) { return; } // When running inside GitHub Actions, GITHUB_JOB is the current job's key. // Exclude it to prevent a release job from blocking itself on its own check run. const selfCheckRunName = process.env.GITHUB_ACTIONS === 'true' ? process.env.GITHUB_JOB : null; spinner.start(`Verifying status checks on '${ref}'...`); const [commitStatus, checkRuns] = await Promise.all([ fetchStatus(owner, repo, ref), fetchCheckRuns(owner, repo, ref) ]); const pendingRun = checkRuns.find(run => PENDING_CHECK_RUN_STATUSES.has(run.status) && !(run.status === 'in_progress' && run.name === selfCheckRunName) ); if (pendingRun) { spinner.fail(`Status checks are still running on '${ref}'.`); throw createError( `Cannot release: check run '${pendingRun.name}' is still ${pendingRun.status} on '${ref}'. ` + `Wait for all checks to complete before releasing, or use --no-status-checks to bypass this gate.`, exitCodes.VALIDATION ); } const failedRun = checkRuns.find(run => FAILED_CHECK_RUN_CONCLUSIONS.has(run.conclusion)); if (failedRun) { spinner.fail(`Status checks have failed on '${ref}'.`); throw createError( `Cannot release: check run '${failedRun.name}' has conclusion '${failedRun.conclusion}' on '${ref}'. ` + `Fix the failing checks before releasing, or use --no-status-checks to bypass this gate.`, exitCodes.VALIDATION ); } if (commitStatus.totalCount > 0) { if (commitStatus.state === 'pending') { spinner.fail(`Commit status checks are pending on '${ref}'.`); throw createError( `Cannot release: commit status is '${commitStatus.state}' on '${ref}'. ` + `Wait for all checks to complete before releasing, or use --no-status-checks to bypass this gate.`, exitCodes.VALIDATION ); } if (commitStatus.state === 'failure') { spinner.fail(`Commit status checks have failed on '${ref}'.`); throw createError( `Cannot release: commit status is '${commitStatus.state}' on '${ref}'. ` + `Fix the failing checks before releasing, or use --no-status-checks to bypass this gate.`, exitCodes.VALIDATION ); } } spinner.succeed(`All status checks passed on '${ref}'.`); }