mirror of
https://github.com/keymanapp/keyman.git
synced 2026-08-05 00:15:32 +00:00
Merge pull request #14734 from keymanapp/fix/resources/pr-build-status-cr-to-lf
fix(resources): match cr to lf change in pr-build-status.mjs
This commit is contained in:
commit
d852916d4d
2 changed files with 193 additions and 193 deletions
384
.github/workflows/pr-build-status.yml
vendored
384
.github/workflows/pr-build-status.yml
vendored
|
|
@ -1,4 +1,4 @@
|
|||
# GENERATED FILE - DO NOT EDIT!
|
||||
# GENERATED FILE - DO NOT EDIT!
|
||||
#
|
||||
# Keyman is copyright (C) SIL Global. MIT License.
|
||||
#
|
||||
|
|
@ -35,194 +35,194 @@ jobs:
|
|||
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 == '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
|
||||
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]);
|
||||
|
||||
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 == '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
|
||||
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]);
|
||||
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@
|
|||
|
||||
function addLog(message) {
|
||||
console.log(message);
|
||||
return message + String.fromCharCode(13);
|
||||
return message + String.fromCharCode(10);
|
||||
}
|
||||
|
||||
function addStatus(o, type, context, state) {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue