1
0
mirror of synced 2025-12-30 03:01:36 -05:00
Files
docs/.github/actions-scripts/update-merge-queue-branch.js
2021-12-08 18:37:22 +00:00

159 lines
5.3 KiB
JavaScript

#!/usr/bin/env node
import { getOctokit } from '@actions/github'
const token = process.env.GITHUB_TOKEN
const github = getOctokit(token)
// Mergeable status documentation here:
// https://docs.github.com/en/graphql/reference/enums#mergestatestatus
// https://docs.github.com/en/graphql/reference/enums#mergeablestate
/*
This script gets a list of automerge-enabled PRs and sorts them
by priority. The PRs with the skip-to-front-of-merge-queue label
are prioritized first. The rest of the PRs are sorted by the date
they were updated. This is basically a FIFO queue, while allowing
writers the ability to skip the line when high-priority ships are
needed but a freeze isn't necessary.
*/
const DRY_RUN = Boolean(JSON.parse(process.env.DRY_RUN || 'false'))
main()
async function main() {
const [org, repo] = process.env.GITHUB_REPOSITORY.split('/')
if (!org || !repo) {
throw new Error('GITHUB_REPOSITORY environment variable not set')
}
// Get a list of open PRs and order them from oldest to newest
const query = `query ($first: Int, $after: String, $firstLabels: Int, $repo: String!, $org: String!) {
organization(login: $org) {
repository(name: $repo) {
pullRequests(first: $first, after: $after, states: OPEN, orderBy: {field: UPDATED_AT, direction: ASC}) {
edges{
node {
number
url
updatedAt
mergeable
mergeStateStatus
autoMergeRequest {
enabledBy {
login
}
enabledAt
}
labels (first:$firstLabels){
nodes {
name
}
}
commits(last: 1) {
nodes {
commit {
statusCheckRollup {
state
}
}
}
}
}
}
pageInfo {
hasNextPage
endCursor
}
}
}
}
}`
const queryVariables = {
repo,
org,
first: 100,
after: null, // when pagination in null it will get first page
firstLabels: 100,
headers: {
// required for the mergeStateStatus enum
accept: 'application/vnd.github.merge-info-preview+json',
},
}
let hasNextPage = true
const autoMergeEnabledPRs = []
// we need to get all the paginated results in the case that
// there are more than 100 PRs
while (hasNextPage) {
const graph = await github.graphql(query, queryVariables)
const dataRoot = graph.organization.repository.pullRequests
const pullRequests = dataRoot.edges
// update pagination variables
hasNextPage = dataRoot.pageInfo.hasNextPage
// the endCursor is the start cursor for the next page
queryVariables.after = dataRoot.pageInfo.endCursor
const filteredPrs = pullRequests
// this simplifies the format received from the graphql query to
// remove the unnecessary nested objects
.map((pr) => {
// make the labels object just an array of the label names
const labelArray = pr.node.labels.nodes.map((label) => label.name)
pr.node.labels = labelArray
// return the pr object and ✂️ the node property
return pr.node
})
.filter((pr) => pr.autoMergeRequest !== null)
.filter((pr) => pr.mergeable === 'MERGEABLE')
// filter out prs that don't have a calculated mergeable state yet
.filter((pr) => pr.mergeStateStatus !== 'UNKNOWN')
// filter out prs that still need a review, have merge conflicts,
// or have failing ci tests
.filter((pr) => pr.mergeStateStatus !== 'BLOCKED')
// **NOTE**: In the future we may want to send slack message to initiators
// of PRs with the following merge states because these can happen after
// a PR is green and the automerge is enabled
.filter((pr) => pr.mergeStateStatus !== 'DIRTY')
.filter((pr) => pr.mergeStateStatus !== 'UNSTABLE')
.filter((pr) => {
const nodes = pr.commits.nodes
if (!nodes || !nodes.length) {
// If it has no commits, why is it even here? Anyway, skip it.
return false
}
return nodes[0].commit.statusCheckRollup.state !== 'FAILURE'
})
autoMergeEnabledPRs.push(...filteredPrs)
}
// Get the list of prs with the skip label so they can
// be put at the beginning of the list
const prioritizedPrList = autoMergeEnabledPRs.sort(
(a, b) =>
Number(b.labels.includes('skip-to-front-of-merge-queue')) -
Number(a.labels.includes('skip-to-front-of-merge-queue'))
)
if (prioritizedPrList.length) {
const nextInQueue = prioritizedPrList.shift()
// Update the branch for the next PR in the merge queue
if (DRY_RUN) {
console.log('DRY RUN! But *would* update on next-in-queue')
} else {
github.rest.pulls.updateBranch({
owner: org,
repo,
pull_number: nextInQueue.number,
})
}
console.log(`⏱ Total PRs in the merge queue: ${prioritizedPrList.length + 1}`)
console.log(`🚂 Updated branch for PR #${JSON.stringify(nextInQueue, null, 2)}`)
}
prioritizedPrList.length
? console.log(`🚏 Next up in the queue: \n ${JSON.stringify(prioritizedPrList, null, 2)}`)
: console.log(`⚡ The merge queue is empty`)
}