// Description: // Script that listens to GitHub pull requests events // and manages a checklist based on configuration in github-bot.yml // It sets the mergeable status based checklist state // // Dependencies: // github: "^13.1.0" // probot-config: "^1.0.0" // // Author: // virneo const fetch = require('node-fetch') const getConfig = require('probot-config') const defaultConfig = require('../lib/config') const gitHubHelpers = require('../lib/github-helpers') const botName = 'manage-pr-checklist' module.exports = (robot) => { robot.on( [ 'pull_request.opened', 'pull_request.edited' ], context => { // Make sure we don't listen to our own messages if (context.isBot) { return } handlePullRequest(context, robot) }) robot.on('issue_comment', context => { // Make sure we don't listen to our own messages if (context.isBot) { return } handleIssue(context, robot) }) } async function handleIssue (context, robot) { if (context.payload.issue.pull_request) { const res = await fetch(context.payload.issue.pull_request.url) const pr = await res.json() context.payload.pull_request = pr return handlePullRequest(context, robot) } } async function handlePullRequest (context, robot) { const config = await getConfig(context, 'github-bot.yml', defaultConfig(robot, '.github/github-bot.yml')) const settings = config ? config['prchecklist'] : null if (!settings) { return } if (settings.title == null) settings.title = '' if (settings.checklist == null) settings.checklist = {} const currentStatus = await gitHubHelpers.getPullRequestCurrentStatusForContext(context, 'PRChecklist') const { isChecklistComplete, firstCheck } = await verifyChecklist(context, settings) const newStatus = isChecklistComplete ? 'success' : 'pending' const hasChange = firstCheck || currentStatus !== newStatus const logStatus = isChecklistComplete ? '✅' : '⏳' const shortUrl = context.payload.pull_request.url if (!hasChange) { robot.log.info(`${botName} - 😐${logStatus} ${shortUrl}`) return } try { const { found, comment } = await getCheckListComment(context) let targetUrl = 'https://github.com/status-im/status-github-bot.git' if (found) { targetUrl = comment.url } await context.github.repos.createStatus(context.repo({ sha: context.payload.pull_request.head.sha, state: newStatus, target_url: targetUrl, description: isChecklistComplete ? 'ready for merge' : 'PR Checklist is incomplete', context: 'PRChecklist' })) robot.log.info(`${botName} - 💾${logStatus} ${shortUrl}`) } catch (err) { robot.log.error(`${botName} - Couldn't create status for commits in the PR: ${err}`, context.payload.pull_request.id) } } async function createOrEditChecklist (context, checkList, header) { const owner = context.payload.repository.owner.login const repo = context.payload.repository.name const number = context.payload.pull_request.number if (checkList && checkList.length > 0) { let body = ' \n### ' + header + '\n' for (const key of checkList) { body += '- [ ] ' + key + '\n' } await context.github.issues.createComment({ owner, repo, number, body }) } } async function verifyChecklist (context, settings) { let isChecklistComplete = true let firstCheck = false const { found, body } = await getCheckListBody(context) if (found) { if (body) { for (const str of body) { const res = str.match(/(-\s\[(\s)])(.*)/gm) if (res != null) { isChecklistComplete = false break } } } else { isChecklistComplete = false } } else { await createOrEditChecklist(context, settings.checklist, settings.title) isChecklistComplete = false firstCheck = true } return { isChecklistComplete, firstCheck } } async function getCheckListComment (context) { try { const owner = context.payload.repository.owner.login const repo = context.payload.repository.name const number = context.payload.pull_request.number const comments = await context.github.paginate(context.github.issues.listComments({ owner, repo, number }), res => res.data) for (const comment of comments) { const { found } = checkPRChecklist(comment.body) if (found) { return { found, comment } } } return false } catch (e) { return true } } async function getCheckListBody (context) { const { found, comment } = await getCheckListComment(context) if (found) { const { body } = checkPRChecklist(comment.body) return { found, body } } return false } function checkPRChecklist (str) { let found = false let body = null const isBotComment = str.match(/()/g) if (isBotComment == null) return { found, body } const res = str.match(/(-\s\[(\s|x)])(.*)/gm) if (res && res.length > 0) { found = true body = res } return { found, body } }