DavideViolante/pr-automerge-action

View on GitHub
index.js

Summary

Maintainability
A
2 hrs
Test Coverage

const core = require('@actions/core');
const axios = require('axios');

const { createMapping } = require('./functions');

const { GITHUB_TOKEN, GITHUB_REPOSITORY, GITHUB_API_URL } = process.env;
const AUTH_HEADER = {
  Authorization: `token ${GITHUB_TOKEN}`,
};
const GITHUB_ENDPOINT = `${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}`;
const WAIT_MS = 5000;

/**
 * Get Pull Requests
 * @return {Array} array of pull requests
 */
function getPullRequests() {
  return axios({
    method: 'GET',
    url: `${GITHUB_ENDPOINT}/pulls`,
    headers: AUTH_HEADER,
  });
}

/**
 * Get Pull Request reviews from ID
 * @param {Number} number Pull Request ID
 * @return {Array} Array of reviews
 */
function getPullRequestReviews(number) {
  return axios({
    method: 'GET',
    url: `${GITHUB_ENDPOINT}/pulls/${number}/reviews`,
    headers: AUTH_HEADER,
  });
}

/**
 * Merge a Pull Request
 * @param {Number} number Pull Request ID
 * @param {String} mergeMethod Merge method
 * @return {void}
 */
function mergePullRequest(number, mergeMethod) {
  return axios({
    method: 'PUT',
    url: `${GITHUB_ENDPOINT}/pulls/${number}/merge`,
    headers: AUTH_HEADER,
    data: {
      merge_method: mergeMethod,
    },
  });
}

/**
 * Delete a ref
 * @param {String} ref GitHub ref to delete
 * @return {void}
 */
function deleteHeadRef(ref) {
  return axios({
    method: 'DELETE',
    url: `${GITHUB_ENDPOINT}/git/refs/heads/${ref}`,
    headers: AUTH_HEADER,
  });
}

/**
 * Filter Pull Requests based on ref
 * @param {Array} pullRequests Array of Pull Requests
 * @param {String} ref GitHub ref
 * @return {Array} Array of Pull Requests filtered
 */
function filterByBaseRef(pullRequests, ref) {
  return pullRequests.filter((pr) => pr.base.ref === ref);
}

/**
 * Get promises and refs of Pull Requests reviews
 * @param {Array} pullRequests Array of Pull Requests
 * @return {Object} Object with promises and refs
 */
function getPromisesAndRefs(pullRequests) {
  const promises = [];
  const refs = {};
  for (const pr of pullRequests) {
    promises.push(getPullRequestReviews(pr.number));
    refs[pr.number] = pr.head.ref;
  }
  return { promises, refs };
}

/**
 * Wait ms
 * @param {Number} ms ms to wait
 * @return {void}
 */
function sleep(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

/**
 * Main function
 */
async function main() {
  try {
    const mergeMethod = core.getInput('merge-method');
    const minApprovals = core.getInput('min-approvals');
    const baseRef = core.getInput('base-ref');
    core.info('Getting open pull requests...');
    let pullRequests = await getPullRequests();
    pullRequests = filterByBaseRef(pullRequests.data, baseRef);
    // eslint-disable-next-line max-len
    core.info(`There are ${pullRequests.length} open PRs with "${baseRef}" base ref`);
    core.info(`Getting their reviews...`);
    const { promises, refs } = getPromisesAndRefs(pullRequests);
    const pullRequestsReviewsResolved = await Promise.all(promises);
    const pullRequestsReviews = createMapping(pullRequestsReviewsResolved);
    for (const [prNumber, approvals] of Object.entries(pullRequestsReviews)) {
      if (approvals >= +minApprovals) {
        core.info(`Automerging PR #${prNumber} (${minApprovals} approvals)`);
        await mergePullRequest(prNumber, mergeMethod);
        core.info(`Waiting ${WAIT_MS / 1000}s before next merge...`);
        await sleep(WAIT_MS);
        await deleteHeadRef(refs[prNumber]);
      } else {
        core.info(`Skipping PR #${prNumber} (${approvals} approvals)`);
      }
    }
  } catch (error) {
    core.setFailed(error.message);
  }
}

main();