name: Auto Merge on: pull_request_review: types: [submitted] check_suite: types: [completed] workflow_run: workflows: ["CI/CD"] types: [completed] permissions: contents: write pull-requests: write jobs: auto-merge: name: Auto Merge PR runs-on: ubuntu-latest if: > ${{ (github.event_name == 'pull_request_review' && github.event.review.state == 'approved') || (github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success') }} steps: - name: Checkout code uses: actions/checkout@v4 - name: Check PR status and merge uses: actions/github-script@v7 with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | // Get PR number from event let prNumber; if (context.payload.pull_request) { prNumber = context.payload.pull_request.number; } else if (context.payload.workflow_run) { // Get PR from workflow run const { data: prs } = await github.rest.pulls.list({ owner: context.repo.owner, repo: context.repo.repo, state: 'open', head: `${context.repo.owner}:${context.payload.workflow_run.head_branch}` }); if (prs.length === 0) { console.log('No open PR found for this branch'); return; } prNumber = prs[0].number; } else { console.log('Could not determine PR number'); return; } console.log(`Checking PR #${prNumber}`); // Get PR details const { data: pr } = await github.rest.pulls.get({ owner: context.repo.owner, repo: context.repo.repo, pull_number: prNumber }); if (pr.state !== 'open') { console.log('PR is not open'); return; } if (pr.draft) { console.log('PR is still in draft'); return; } // Check if PR is approved const { data: reviews } = await github.rest.pulls.listReviews({ owner: context.repo.owner, repo: context.repo.repo, pull_number: prNumber }); const latestReviews = {}; for (const review of reviews) { latestReviews[review.user.login] = review.state; } const hasApproval = Object.values(latestReviews).includes('APPROVED'); const hasRequestChanges = Object.values(latestReviews).includes('CHANGES_REQUESTED'); if (!hasApproval) { console.log('PR has not been approved yet'); return; } if (hasRequestChanges) { console.log('PR has requested changes'); return; } // Check CI status const { data: checks } = await github.rest.checks.listForRef({ owner: context.repo.owner, repo: context.repo.repo, ref: pr.head.sha }); const requiredChecks = ['Lint Code', 'Build Application', 'E2E Tests']; const checkStatuses = {}; for (const check of checks.check_runs) { checkStatuses[check.name] = check.conclusion; } console.log('Check statuses:', checkStatuses); // Wait for all required checks to pass const allChecksPassed = requiredChecks.every(checkName => checkStatuses[checkName] === 'success' || checkStatuses[checkName] === 'skipped' ); if (!allChecksPassed) { console.log('Not all required checks have passed'); // Check if any checks failed const anyChecksFailed = Object.values(checkStatuses).some(status => status === 'failure' ); if (anyChecksFailed) { console.log('Some checks failed, not merging'); return; } console.log('Checks are still running, will retry later'); return; } console.log('All conditions met, merging PR'); // Add comment before merging await github.rest.issues.createComment({ owner: context.repo.owner, repo: context.repo.repo, issue_number: prNumber, body: '✅ All checks passed and PR is approved! Auto-merging and cleaning up branch.' }); try { // Merge the PR await github.rest.pulls.merge({ owner: context.repo.owner, repo: context.repo.repo, pull_number: prNumber, merge_method: 'squash', commit_title: `${pr.title} (#${prNumber})`, commit_message: pr.body || '' }); console.log('PR merged successfully'); // Delete the branch try { await github.rest.git.deleteRef({ owner: context.repo.owner, repo: context.repo.repo, ref: `heads/${pr.head.ref}` }); console.log(`Branch ${pr.head.ref} deleted successfully`); } catch (deleteError) { console.log('Could not delete branch:', deleteError.message); // Don't fail the workflow if branch deletion fails } } catch (mergeError) { console.error('Failed to merge PR:', mergeError.message); // Post comment about merge failure await github.rest.issues.createComment({ owner: context.repo.owner, repo: context.repo.repo, issue_number: prNumber, body: `❌ Auto-merge failed: ${mergeError.message}\n\nPlease merge manually.` }); }