Files
metabuilder/.github/workflows/pr/auto-merge.yml

203 lines
7.4 KiB
YAML

name: Auto Merge
on:
pull_request_review:
types: [submitted]
check_suite:
types: [completed]
workflow_run:
workflows: ["CI/CD", "Enterprise Gated CI/CD Pipeline"]
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@v6
- 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 - support both old and new gated workflows
const { data: checks } = await github.rest.checks.listForRef({
owner: context.repo.owner,
repo: context.repo.repo,
ref: pr.head.sha
});
// Required checks for old CI/CD workflow
const legacyRequiredChecks = ['Lint Code', 'Build Application', 'E2E Tests'];
// Required gate checks for new Enterprise Gated CI/CD Pipeline
const gatedRequiredChecks = [
'Gate 1: Code Quality - Passed ✅',
'Gate 2: Testing - Passed ✅',
'Gate 3: Build & Package - Passed ✅'
];
const checkStatuses = {};
for (const check of checks.check_runs) {
checkStatuses[check.name] = check.conclusion;
}
console.log('Check statuses:', checkStatuses);
// Check if using new gated workflow or old workflow
const hasGatedChecks = gatedRequiredChecks.some(checkName =>
checkStatuses[checkName] !== undefined
);
const requiredChecks = hasGatedChecks ? gatedRequiredChecks : legacyRequiredChecks;
console.log('Using checks:', hasGatedChecks ? 'Enterprise Gated' : 'Legacy');
// 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.`
});
}