Dependabot auto-merge #43
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
| name: Dependabot auto-merge | |
| # Merges routine Dependabot updates once CI has actually passed. | |
| # | |
| # Keyed off the test workflow finishing rather than the `pull_request` | |
| # event, because GitHub's native auto-merge only waits for *required* | |
| # status checks — and making a check required means branch protection, | |
| # which would also block `refresh.yml`'s automated data commits to main | |
| # (pushed by an app, not an admin). Reacting to the completed run gives | |
| # the same "CI passed" guarantee with no repository-wide policy change. | |
| # | |
| # `workflow_run` grants a write token, so this job never checks out or | |
| # executes pull request code — it only calls the API. Untrusted values | |
| # reach the shell as quoted variables rather than `${{ }}` expansions, | |
| # and the versions it compares are matched out of the PR text by a | |
| # digits-and-dots pattern. | |
| on: | |
| workflow_run: | |
| workflows: ["Test"] | |
| types: [completed] | |
| # Enough to merge the PR and delete its branch, nothing more. | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| jobs: | |
| auto-merge: | |
| # A green test run for some pull request. Whether it is actually | |
| # Dependabot's is confirmed against the PR author below, which is | |
| # more reliable than the run's reported actor. | |
| if: >- | |
| github.event.workflow_run.event == 'pull_request' && | |
| github.event.workflow_run.conclusion == 'success' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| steps: | |
| - name: Merge the update unless it is a major bump | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| REPO: ${{ github.repository }} | |
| HEAD_SHA: ${{ github.event.workflow_run.head_sha }} | |
| run: | | |
| set -euo pipefail | |
| pr=$(gh api "repos/$REPO/commits/$HEAD_SHA/pulls" \ | |
| --jq '[.[] | select(.state == "open")] | .[0].number // empty') | |
| if [ -z "$pr" ]; then | |
| echo "No open PR for $HEAD_SHA — nothing to merge." | |
| exit 0 | |
| fi | |
| author=$(gh pr view "$pr" --repo "$REPO" --json author --jq '.author.login') | |
| if [ "$author" != "app/dependabot" ] && [ "$author" != "dependabot[bot]" ]; then | |
| echo "PR #$pr is authored by $author, not Dependabot — leaving it alone." | |
| exit 0 | |
| fi | |
| # Major bumps keep a human in the loop. A pull request only runs | |
| # the test workflow, so an update that also touches the deploy or | |
| # release path is not fully exercised by the green check that got | |
| # us here — actions/setup-node v6 -> v7 was exactly that shape. | |
| # | |
| # Only Dependabot's own declarations are read: the title, plus | |
| # the body lines it opens with "Bumps ..." (single) or | |
| # "Updates ..." (grouped). The rest of the body embeds the | |
| # upstream changelog, which routinely quotes unrelated bumps — | |
| # body-parser 2.2.2 -> 2.3.0 ships release notes mentioning | |
| # "actions/download-artifact from 6.0.0 to 7.0.0", which would | |
| # otherwise read as a major and hold a patch release forever. | |
| title=$(gh pr view "$pr" --repo "$REPO" --json title --jq '.title') | |
| body=$(gh pr view "$pr" --repo "$REPO" --json body --jq '.body') | |
| changes=$( { printf '%s\n' "$title" | |
| printf '%s\n' "$body" | grep -E '^(Bumps |Updates )' || true | |
| } | grep -oE 'from [0-9]+(\.[0-9]+)* to [0-9]+(\.[0-9]+)*' || true) | |
| while read -r _ from _ to; do | |
| from=${from:-} | |
| to=${to:-} | |
| [ -n "$from" ] && [ -n "$to" ] || continue | |
| if [ "${from%%.*}" != "${to%%.*}" ]; then | |
| echo "PR #$pr carries a major bump ($from -> $to) — leaving it for review." | |
| exit 0 | |
| fi | |
| done <<< "$changes" | |
| echo "Merging PR #$pr ($author)" | |
| gh pr merge "$pr" --repo "$REPO" --squash --delete-branch |