Dependabot Auto-merge #573
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# ------------------------------------------------------------------------------ | |
# Dependabot Auto-merge Workflow | |
# | |
# Purpose: Automatically merge Dependabot **minor / patch** updates once all | |
# required checks pass. Major updates only get an alert comment. | |
# | |
# Triggers: Pull‑request events, review submissions, and completed checks. | |
# | |
# Maintainer: @mrz1836 | |
# | |
# Rules for Auto‑Merge: | |
# • PR must be opened by Dependabot and found by this workflow | |
# • All required status checks must pass | |
# • Only minor and patch updates are auto-merged | |
# • Major updates will only receive an alert comment | |
# • PR must have the 'automerge' label | |
# ------------------------------------------------------------------------------ | |
name: dependabot-auto-merge | |
on: | |
pull_request_target: | |
types: [opened, synchronize, reopened, ready_for_review, unlocked, edited] | |
pull_request_review: | |
types: [submitted] | |
check_suite: | |
types: [completed] | |
status: {} # completed status checks | |
permissions: | |
contents: read | |
pull-requests: read | |
concurrency: | |
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} | |
cancel-in-progress: true | |
jobs: | |
automerge: | |
runs-on: ubuntu-latest | |
permissions: | |
pull-requests: write | |
contents: write | |
steps: | |
# ----------------------------------------------------------------------- | |
# 1. Locate the pull request for this event | |
# ----------------------------------------------------------------------- | |
- name: Locate pull request | |
id: pr | |
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 | |
with: | |
github-token: ${{ secrets.GITHUB_TOKEN }} | |
script: | | |
const { repo, owner } = context.repo; | |
let pr = context.payload.pull_request; | |
// If triggered by check_suite / status, look up the PR by SHA | |
if (!pr) { | |
const sha = | |
context.payload.check_suite?.head_sha ?? | |
context.payload.check_run?.head_sha ?? | |
context.payload.sha ?? | |
context.payload.after; | |
if (sha) { | |
const { data: prs } = | |
await github.rest.repos.listPullRequestsAssociatedWithCommit({ | |
owner, | |
repo, | |
commit_sha: sha, | |
}); | |
pr = prs.find(p => p.state === 'open'); | |
} | |
} | |
if (!pr) { | |
core.info('No open PR found – skipping.'); | |
core.setOutput('found', 'false'); | |
return; | |
} | |
core.setOutput('found', 'true'); | |
core.setOutput('number', pr.number); | |
core.setOutput('actor', pr.user.login); | |
core.setOutput('title', pr.title.replace(/\n/g, ' ')); | |
# ----------------------------------------------------------------------- | |
# 1a. Exit when no PR was found | |
# ----------------------------------------------------------------------- | |
- name: Exit when no PR found | |
if: steps.pr.outputs.found != 'true' | |
run: echo "PR not found – exiting." | |
# ----------------------------------------------------------------------- | |
# 2. Make sure the PR was opened by Dependabot | |
# ----------------------------------------------------------------------- | |
- name: Validate Dependabot author | |
id: author | |
if: steps.pr.outputs.found == 'true' | |
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 | |
with: | |
github-token: ${{ secrets.GITHUB_TOKEN }} | |
script: | | |
const actor = '${{ steps.pr.outputs.actor }}'; | |
const allowed = ['dependabot[bot]', 'dependabot-preview[bot]'].includes(actor); | |
core.setOutput('allowed', allowed); | |
if (!allowed) core.info(`PR opened by ${actor} – skipping automerge.`); | |
# ----------------------------------------------------------------------- | |
# 2a. Exit when PR was not opened by Dependabot | |
# ----------------------------------------------------------------------- | |
- name: Exit for non-Dependabot PR | |
if: steps.author.outputs.allowed != 'true' | |
run: echo "Not a Dependabot PR – exiting." | |
# --------------------------------------------------------------------- | |
# 3. Detect minor / major / unknown bump | |
# --------------------------------------------------------------------- | |
- name: Check version bump | |
id: bump | |
if: steps.author.outputs.allowed == 'true' | |
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 | |
with: | |
script: | | |
const title = '${{ steps.pr.outputs.title }}'; | |
// Matches “from 1.2.3 to 1.3.0”, “from v4 to v4.0.1”, etc. | |
const match = title.match(/from\s+v?(\d+)(?:\.\d+)*\s+to\s+v?(\d+)/i); | |
let isMinor = false; | |
let isUnknown = false; | |
if (!match) { | |
isUnknown = true; // SHA bumps or unparsable versions | |
} else { | |
isMinor = match[1] === match[2]; | |
} | |
core.setOutput('is_minor', isMinor); | |
core.setOutput('is_major', !isMinor && !isUnknown); | |
core.setOutput('is_unknown', isUnknown); | |
# --------------------------------------------------------------------- | |
# 4a. Alert on major bump | |
# --------------------------------------------------------------------- | |
- name: Alert on major version bump | |
if: steps.bump.outputs.is_major == 'true' | |
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 | |
with: | |
github-token: ${{ secrets.GITHUB_TOKEN }} | |
script: | | |
await github.rest.issues.createComment({ | |
owner: context.repo.owner, | |
repo: context.repo.repo, | |
issue_number: Number('${{ steps.pr.outputs.number }}'), | |
body: '⚠️ @mrz1836 – this is a **major** version bump and needs manual review.' | |
}); | |
# --------------------------------------------------------------------- | |
# 4b. Alert when a version couldn’t be parsed (commit SHA → SHA) | |
# --------------------------------------------------------------------- | |
- name: Alert on undetected version bump | |
if: steps.bump.outputs.is_unknown == 'true' | |
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 | |
with: | |
github-token: ${{ secrets.GITHUB_TOKEN }} | |
script: | | |
await github.rest.issues.createComment({ | |
owner: context.repo.owner, | |
repo: context.repo.repo, | |
issue_number: Number('${{ steps.pr.outputs.number }}'), | |
body: '⚠️ @mrz1836 – Dependabot could not determine version semantics (SHA → SHA). Please review manually.' | |
}); | |
# ----------------------------------------------------------------------- | |
# 5. Auto-approve minor / patch bumps | |
# ----------------------------------------------------------------------- | |
- name: Auto-approve minor update | |
if: steps.bump.outputs.is_minor == 'true' | |
uses: hmarr/auto-approve-action@f0939ea97e9205ef24d872e76833fa908a770363 # v4.0.0 | |
with: | |
github-token: ${{ secrets.GITHUB_TOKEN }} | |
pull-request-number: ${{ steps.pr.outputs.number }} # ← NEW | |
review-message: Automatically approving Dependabot minor/patch update | |
# ----------------------------------------------------------------------- | |
# 6. Add the required `automerge` label (used by pascalgn/automerge) | |
# ----------------------------------------------------------------------- | |
- name: Add automerge label | |
if: steps.bump.outputs.is_minor == 'true' | |
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 | |
with: | |
github-token: ${{ secrets.GITHUB_TOKEN }} | |
script: | | |
await github.rest.issues.addLabels({ | |
owner: context.repo.owner, | |
repo: context.repo.repo, | |
issue_number: Number('${{ steps.pr.outputs.number }}'), | |
labels: ['automerge'] | |
}); | |
# ----------------------------------------------------------------------- | |
# 7. Auto-merge when checks are green | |
# ----------------------------------------------------------------------- | |
- name: Auto-merge minor update | |
if: steps.bump.outputs.is_minor == 'true' | |
uses: pascalgn/automerge-action@7961b8b5eec56cc088c140b56d864285eabd3f67 # v0.16.4 | |
env: | |
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
MERGE_LABELS: "automerge" # must match label we just added | |
MERGE_METHOD: merge | |
MERGE_COMMIT_MESSAGE: pull-request-title | |
MERGE_RETRIES: "30" # up to ~30 minutes | |
MERGE_RETRY_SLEEP: "60000" |