name: Validate Main Green CI on: workflow_dispatch: inputs: branch: description: "Branch to validate" required: false default: main type: string required_contexts: description: "Comma-separated required checks; use | for alternatives (e.g. ci-ok|ci-main / ci-ok)" required: false default: ci-ok|ci-main / ci-ok type: string workflow_call: inputs: branch: required: false default: main type: string required_contexts: required: false default: ci-ok|ci-main / ci-ok type: string jobs: validate: runs-on: ubuntu-latest permissions: contents: read checks: read steps: - name: validate required checks uses: actions/github-script@v7 env: TARGET_BRANCH: ${{ inputs.branch || github.event.inputs.branch || 'main' }} REQUIRED_CONTEXTS: ${{ inputs.required_contexts || github.event.inputs.required_contexts || 'ci-ok|ci-main / ci-ok' }} with: script: | const owner = context.repo.owner; const repo = context.repo.repo; const branch = process.env.TARGET_BRANCH || "main"; const groups = String(process.env.REQUIRED_CONTEXTS || "") .split(",") .map((s) => s.trim()) .filter(Boolean) .map((group) => group .split("|") .map((s) => s.trim()) .filter(Boolean) ); const runId = String(context.runId); const branchResp = await github.rest.repos.getBranch({ owner, repo, branch }); const sha = branchResp.data.commit.sha; core.info(`Validating checks for ${branch} @ ${sha}`); const checksAll = await github.paginate(github.rest.checks.listForRef, { owner, repo, ref: sha, per_page: 100, }); const checks = checksAll.filter((c) => !(c.details_url || "").includes(`/actions/runs/${runId}/`)); const newestByName = new Map(); for (const c of checks) { const key = c.name; const prev = newestByName.get(key); if (!prev) { newestByName.set(key, c); continue; } const prevTime = Date.parse(prev.started_at || prev.completed_at || "") || 0; const curTime = Date.parse(c.started_at || c.completed_at || "") || 0; if (curTime >= prevTime) { newestByName.set(key, c); } } const failures = []; for (const alternatives of groups) { let ok = false; const altNotes = []; for (const name of alternatives) { const latest = newestByName.get(name); if (!latest) { altNotes.push(`${name}: missing`); continue; } if (latest.status !== "completed") { altNotes.push(`${name}: status=${latest.status}, conclusion=${latest.conclusion}`); continue; } if (["success", "neutral", "skipped"].includes(latest.conclusion || "")) { ok = true; break; } altNotes.push(`${name}: conclusion=${latest.conclusion}`); } if (!ok) { failures.push(`(${alternatives.join(" | ")}): none green — ${altNotes.join("; ")}`); } } if (failures.length > 0) { core.setFailed(`Required checks for ${branch} are not green:\n${failures.join("\n")}`); return; } const summary = groups .map((alts) => (alts.length > 1 ? `(${alts.join(" | ")})` : alts[0])) .join(", "); core.info(`All required checks for ${branch} are green (${summary}).`);