mirror of
https://github.com/keymanapp/keyman.git
synced 2026-08-06 00:45:32 +00:00
233 lines
9.7 KiB
YAML
233 lines
9.7 KiB
YAML
# GENERATED FILE - DO NOT EDIT!
|
|
#
|
|
# Keyman is copyright (C) SIL Global. MIT License.
|
|
#
|
|
# Do not modify the script in .github/workflows/pr-build-status.yml directly;
|
|
# instead work on the sources in resources/build/pr-build-status and use the
|
|
# build.sh script to rebuild the .github/workflows/pr-build-status.yml file from
|
|
# them.
|
|
#
|
|
# build.sh will append the relevant portions of
|
|
# resources/build/pr-build-status/pr-build-status.mjs to the content in
|
|
# resources/build/pr-build-status/pr-build-status.prefix.yml to form the .github
|
|
# workflow file.
|
|
#
|
|
name: Keyman Build Summary
|
|
on:
|
|
status:
|
|
push:
|
|
branches-ignore:
|
|
- master
|
|
- beta
|
|
- stable-*
|
|
workflow_dispatch:
|
|
inputs:
|
|
commit:
|
|
description: 'Commit sha'
|
|
required: true
|
|
type: string
|
|
jobs:
|
|
run_pr_build_status:
|
|
name: Summarize build status checks
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Check PR build status
|
|
id: run_pr_build_status_script
|
|
uses: actions/github-script@v7
|
|
with:
|
|
script: |
|
|
// This code is copied out of resources/build/pr-build-status/pr-build-status.mjs
|
|
// where it is tested. It is copied inline here in order to avoid requiring the
|
|
// repository to be checked out, which dramatically reduces the run time of the
|
|
// check.
|
|
//
|
|
// Note: we don't currently look at check runs, only statuses
|
|
//
|
|
// Verify the following statuses:
|
|
// 'user_testing'
|
|
// 'API Verification' (github-actions[bot])
|
|
//
|
|
// At least 1 of the following statuses must be found:
|
|
// 'Test*' (keyman-server), e.g. 'Test Build (Keyman)'
|
|
// 'Ubuntu Packaging' (github-actions[bot])
|
|
//
|
|
// Ignore the following statuses:
|
|
// check/web/file-size
|
|
//
|
|
|
|
function addLog(message) {
|
|
console.log(message);
|
|
return message + String.fromCharCode(10);
|
|
}
|
|
|
|
function addStatus(o, type, context, state) {
|
|
if(!o[context]) {
|
|
o[context] = {type, state};
|
|
return addLog(`Found ${context}: ${state}`);
|
|
} else {
|
|
return addLog(`Skipping repeated ${context}: ${state}`);
|
|
}
|
|
}
|
|
|
|
function reduceStatuses(statuses) {
|
|
let summary = '';
|
|
const filtered_statuses = statuses.reduce((o, status) => {
|
|
if(status.creator?.login == 'keyman-server' && status.context.startsWith('Test')) {
|
|
summary += addStatus(o, 'build', status.context, status.state);
|
|
} else if(status.creator?.login == 'keymanapp-test-bot[bot]' && status.context == 'user_testing') {
|
|
summary += addStatus(o, 'user-test', status.context, status.state);
|
|
} else if(status.context == 'API Verification') {
|
|
summary += addStatus(o, 'check', status.context, status.state);
|
|
} else if(status.context == 'Ubuntu Packaging') {
|
|
summary += addStatus(o, 'build', status.context, status.state);
|
|
} else if(status.context == 'npm pack/publish') {
|
|
summary += addStatus(o, 'build', status.context, status.state);
|
|
} else if(status.context == 'Keyman Core - ARM64 test') {
|
|
summary += addStatus(o, 'build', status.context, status.state);
|
|
} else if(status.context == 'check/web/file-size') {
|
|
// Ignore check/web/file-size -- we won't block automerge for this at this point
|
|
summary += addLog(`Skipping ${status.context}`);
|
|
} else {
|
|
// We fail with an 'unknown status' response if we get a new status check
|
|
// so we can be sure we are not skipping known status checks
|
|
summary += addLog(`Found unknown ${status.context}: ${status.state}`);
|
|
o[status.context] = {type: 'unknown', state: status.state};
|
|
}
|
|
return o;
|
|
|
|
}, {});
|
|
return { filtered_statuses, summary };
|
|
}
|
|
|
|
//
|
|
// Given the collection of status checks we care about, return
|
|
// an aggregate status -- error, failure, pending, or success,
|
|
// and a summary description
|
|
//
|
|
function calculateFinalStatus(filtered_statuses, summary) {
|
|
const counts = {};
|
|
let hasBuilds = false;
|
|
for(const context of Object.keys(filtered_statuses)) {
|
|
const { state, type } = filtered_statuses[context];
|
|
if(type == 'unknown') {
|
|
// We special-case for unknown status checks, and never permit them
|
|
// Note: function reduceStatuses() is where new status checks can be added
|
|
console.error(`An unknown context "${context}" was found, cannot calculate build status.`);
|
|
return [
|
|
'error', `An unknown context "${context}" was found, cannot calculate build status.`, summary
|
|
];
|
|
}
|
|
if(type == 'build') {
|
|
hasBuilds = true;
|
|
}
|
|
counts[state] = counts[state] ? counts[state] + 1 : 1;
|
|
}
|
|
|
|
// If we do not have any statuses yet, we wait
|
|
if(Object.keys(filtered_statuses).length == 0 || !hasBuilds) {
|
|
console.log(`Builds not yet triggered`);
|
|
return ['pending', 'Builds have not yet been triggered ⌛', summary];
|
|
}
|
|
|
|
const state =
|
|
counts.error ? 'error' :
|
|
counts.failure ? 'failure' :
|
|
counts.pending ? 'pending' :
|
|
'success';
|
|
|
|
let description = '';
|
|
function appendDescription(count, state) {
|
|
if(!count) return;
|
|
if(description != '') description += '; ';
|
|
description += `${count} check${count == 1 ? '' : 's'} ${state}`;
|
|
}
|
|
appendDescription(counts.error, 'in an error state ❌');
|
|
appendDescription(counts.failure, 'failed ❌');
|
|
appendDescription(counts.pending, 'pending ⌛');
|
|
appendDescription(counts.success, 'completed successfully ✅');
|
|
|
|
console.log(`Finished filtering and reviewing status checks: ${state}: ${description}`);
|
|
return [ state, description, summary ];
|
|
}
|
|
|
|
async function getCommitStatuses(github, owner, repo, sha) {
|
|
console.log(`Getting commit statuses for ${sha}`);
|
|
const statuses = await github.paginate('GET /repos/{owner}/{repo}/commits/{sha}/statuses', {
|
|
owner,
|
|
repo,
|
|
sha,
|
|
headers: {
|
|
'X-GitHub-Api-Version': '2022-11-28'
|
|
}
|
|
});
|
|
return statuses;
|
|
}
|
|
|
|
async function getCommitCheckRuns(github, owner, repo, sha) {
|
|
console.log(`Getting commit check runs for ${sha}`);
|
|
const statuses = await github.paginate('GET /repos/{owner}/{repo}/commits/{sha}/check-runs', {
|
|
owner,
|
|
repo,
|
|
sha,
|
|
headers: {
|
|
'X-GitHub-Api-Version': '2022-11-28'
|
|
}
|
|
});
|
|
return statuses;
|
|
}
|
|
|
|
function calculateCheckResult(statuses) {
|
|
if(!Array.isArray(statuses)) {
|
|
return ['error', 'Failed to retrieve status checks from GitHub ❌', 'statuses is not an array'];
|
|
}
|
|
|
|
const { filtered_statuses, summary } = reduceStatuses(statuses);
|
|
return calculateFinalStatus(filtered_statuses, summary);
|
|
}
|
|
|
|
async function test(github, owner, repo, sha) {
|
|
// Get statuses from sha
|
|
const statuses = await getCommitStatuses(github, owner, repo, sha);
|
|
return calculateCheckResult(statuses);
|
|
}
|
|
|
|
async function createCheck(github, owner, repo, sha) {
|
|
const check = await github.rest.checks.create({
|
|
owner,
|
|
repo,
|
|
head_sha: sha,
|
|
name: 'Build Outcome',
|
|
status: 'in_progress',
|
|
});
|
|
return check.data.id;
|
|
}
|
|
|
|
async function updateCheck(github, owner, repo, checkRunId, status, description, summary) {
|
|
const checkStatus = status == 'pending' ? 'in_progress' : 'completed';
|
|
const conclusion = checkStatus == 'in_progress' ? undefined : (status == 'success' ? 'success' : 'failure');
|
|
|
|
await github.rest.checks.update({
|
|
owner,
|
|
repo,
|
|
check_run_id: checkRunId,
|
|
status: checkStatus,
|
|
conclusion,
|
|
output: {
|
|
title: description,
|
|
summary
|
|
}
|
|
});
|
|
}
|
|
|
|
const { owner, repo } = context.repo;
|
|
const sha =
|
|
context.payload?.check_suite?.sha || /* check run completed */
|
|
context.payload?.inputs?.commit || /* manual run */
|
|
context.payload?.after || /* push */
|
|
context.payload?.commit?.sha || /* status */
|
|
context.sha; /* probably 'master'! */
|
|
|
|
const checkRunId = await createCheck(github, owner, repo, sha);
|
|
const res = await test(github, owner, repo, sha);
|
|
await updateCheck(github, owner, repo, checkRunId, res[0], res[1], res[2]);
|
|
|