1+ name : Status Check
2+
3+ # This workflow enforces that all relevant CI checks pass based on which files changed.
4+ # It's the only required status check in branch protection settings.
5+
6+ on :
7+ pull_request :
8+ types : [opened, synchronize, reopened]
9+ workflow_run :
10+ workflows : ["Python CI", "Go CI", "TypeScript CI"]
11+ types : [completed]
12+
13+ permissions :
14+ contents : read
15+ statuses : write
16+ checks : write
17+ pull-requests : read
18+
19+ jobs :
20+ verify-required-checks :
21+ name : Verify Required Checks
22+ runs-on : ubuntu-latest
23+ if : github.event_name == 'pull_request' || github.event.workflow_run.conclusion != 'cancelled'
24+ steps :
25+ - name : Check out code
26+ uses : actions/checkout@v4
27+
28+ - name : Get PR number
29+ id : pr
30+ run : |
31+ if [ "${{ github.event_name }}" == "workflow_run" ]; then
32+ PR_NUMBER=$(gh pr list --head "${{ github.event.workflow_run.head_branch }}" --json number --jq '.[0].number')
33+ echo "number=$PR_NUMBER" >> $GITHUB_OUTPUT
34+ else
35+ echo "number=${{ github.event.pull_request.number }}" >> $GITHUB_OUTPUT
36+ fi
37+ env :
38+ GITHUB_TOKEN : ${{ secrets.GITHUB_TOKEN }}
39+
40+ - name : Get changed files
41+ id : changed-files
42+ uses : dorny/paths-filter@v3
43+ with :
44+ filters : |
45+ python:
46+ - 'python/**'
47+ - '.github/workflows/python.yml'
48+ go:
49+ - 'go/**'
50+ - '.github/workflows/go.yml'
51+ typescript:
52+ - 'typescript/**'
53+ - '.github/workflows/typescript.yml'
54+
55+ - name : Check required statuses
56+ uses : actions/github-script@v7
57+ with :
58+ script : |
59+ const owner = context.repo.owner;
60+ const repo = context.repo.repo;
61+ const ref = context.payload.pull_request?.head.sha || context.payload.workflow_run?.head_sha;
62+
63+ // Define required checks per language
64+ const requiredChecks = {
65+ python: [
66+ 'Python CI / Linting',
67+ 'Python CI / Unit Tests (3.10)',
68+ 'Python CI / Unit Tests (3.11)',
69+ 'Python CI / Unit Tests (3.12)'
70+ ],
71+ go: [
72+ 'Go CI / Linting',
73+ 'Go CI / Unit Tests (1.23)',
74+ 'Go CI / Unit Tests (1.24)',
75+ 'Go CI / Unit Tests (1.25)',
76+ 'Go CI / Race Detection'
77+ ],
78+ typescript: [
79+ 'TypeScript CI / Linting',
80+ 'TypeScript CI / Unit Tests (20)',
81+ 'TypeScript CI / Unit Tests (22)'
82+ ]
83+ };
84+
85+ // Get changed files from previous step
86+ const changedFiles = {
87+ python: ${{ steps.changed-files.outputs.python }} === 'true',
88+ go: ${{ steps.changed-files.outputs.go }} === 'true',
89+ typescript: ${{ steps.changed-files.outputs.typescript }} === 'true'
90+ };
91+
92+ // Collect all required checks based on changes
93+ let allRequiredChecks = [];
94+ for (const [lang, changed] of Object.entries(changedFiles)) {
95+ if (changed) {
96+ allRequiredChecks = allRequiredChecks.concat(requiredChecks[lang]);
97+ }
98+ }
99+
100+ if (allRequiredChecks.length === 0) {
101+ console.log('No language files changed, no checks required');
102+ return;
103+ }
104+
105+ // Get all check runs for this commit
106+ const { data: checkRuns } = await github.rest.checks.listForRef({
107+ owner,
108+ repo,
109+ ref
110+ });
111+
112+ // Verify all required checks have passed
113+ const checkStatuses = {};
114+ for (const check of checkRuns.check_runs) {
115+ checkStatuses[check.name] = check.conclusion;
116+ }
117+
118+ let allPassed = true;
119+ let failureMessage = '';
120+
121+ for (const requiredCheck of allRequiredChecks) {
122+ const status = checkStatuses[requiredCheck];
123+ if (!status) {
124+ allPassed = false;
125+ failureMessage += `❌ ${requiredCheck}: Not started\\n`;
126+ } else if (status !== 'success') {
127+ allPassed = false;
128+ failureMessage += `❌ ${requiredCheck}: ${status}\\n`;
129+ } else {
130+ failureMessage += `✅ ${requiredCheck}: ${status}\\n`;
131+ }
132+ }
133+
134+ if (!allPassed) {
135+ core.setFailed(`Required checks have not passed:\\n${failureMessage}`);
136+ } else {
137+ console.log(`All required checks passed:\\n${failureMessage}`);
138+ }
0 commit comments