diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index d832abd..9930d7d 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -7,6 +7,12 @@ name: Release on: workflow_call: + inputs: + auto-merge-level: + description: "Automatically merge the Release Please PR when CI passes, up to this bump level. One of: none, patch, minor, major." + type: string + required: false + default: none secrets: RELEASE_PLEASE_CLIENT_ID: description: Client ID for create-github-app-token @@ -65,3 +71,83 @@ jobs: token: ${{ steps.generate_token.outputs.token }} config-file: release-please-config.json manifest-file: .release-please-manifest.json + + # Reconcile auto-merge on the open Release Please PR every run. release-please + # keeps a single PR open and rewrites it as commits land, so its bump level can + # change over time; we enable or disable auto-merge to match the current bump. + # The bump is derived from .release-please-manifest.json (next version on the PR + # head branch vs. current version on the base branch), which is authoritative + # regardless of how each caller configures its PR title. + - name: Reconcile auto-merge on the Release Please PR + if: ${{ steps.release_please.outputs.pr != '' && inputs.auto-merge-level != 'none' }} + env: + GH_TOKEN: ${{ steps.generate_token.outputs.token }} + GH_REPO: ${{ github.repository }} + AUTO_MERGE_LEVEL: ${{ inputs.auto-merge-level }} + PR_JSON: ${{ steps.release_please.outputs.pr }} + run: | + set -euo pipefail + + level_num() { + case "$1" in + none) echo 0 ;; + patch) echo 1 ;; + minor) echo 2 ;; + major) echo 3 ;; + *) echo "Invalid level '$1' (expected none, patch, minor or major)" >&2; exit 1 ;; + esac + } + # Fail fast on an invalid auto-merge-level input. + level_num "$AUTO_MERGE_LEVEL" >/dev/null + + pr_number=$(jq -r '.number' <<<"$PR_JSON") + head_branch=$(jq -r '.headBranchName' <<<"$PR_JSON") + base_branch=$(jq -r '.baseBranchName' <<<"$PR_JSON") + + # Read the single root (".") entry of the release-please manifest at a ref. + manifest_version() { + gh api "repos/${GH_REPO}/contents/.release-please-manifest.json?ref=$1" \ + -H "Accept: application/vnd.github.raw" | jq -r '.["."]' + } + + current=$(manifest_version "$base_branch") + next=$(manifest_version "$head_branch") + + IFS=. read -r cmaj cmin cpat <<<"$current" + IFS=. read -r nmaj nmin npat <<<"$next" + + if [ "$nmaj" -gt "$cmaj" ]; then bump=major + elif [ "$nmin" -gt "$cmin" ]; then bump=minor + elif [ "$npat" -gt "$cpat" ]; then bump=patch + else bump=none + fi + + echo "Release PR #${pr_number}: ${current} -> ${next} (${bump} bump); auto-merge-level=${AUTO_MERGE_LEVEL}" + + # Merge when the PR's bump is a real release and is no larger than the + # configured ceiling (none < patch < minor < major). + if [ "$(level_num "$bump")" -ge 1 ] && [ "$(level_num "$bump")" -le "$(level_num "$AUTO_MERGE_LEVEL")" ]; then + want_merge=true + else + want_merge=false + fi + + # Only mutate when the desired state differs from the current one, to avoid + # redundant enable/disable calls (and the errors a redundant one can raise). + enabled=$(gh pr view "$pr_number" --json autoMergeRequest --jq '.autoMergeRequest != null') + + if [ "$want_merge" = true ] && [ "$enabled" != true ]; then + echo "Enabling auto-merge (squash) on PR #${pr_number}" + # Soft-fail: a repo that hasn't enabled "Allow auto-merge" or hasn't granted + # the App branch-protection bypass shouldn't break its whole release run. + gh pr merge "$pr_number" --auto --squash \ + || echo "::warning::Could not enable auto-merge on PR #${pr_number}. Check that 'Allow auto-merge' is enabled and the release-please App can bypass branch protection." + elif [ "$want_merge" = false ] && [ "$enabled" = true ]; then + echo "Disabling auto-merge on PR #${pr_number}" + # Soft-fail symmetrically with the enable path: a transient API error or a + # race shouldn't break the release run. + gh pr merge "$pr_number" --disable-auto \ + || echo "::warning::Could not disable auto-merge on PR #${pr_number}." + else + echo "Auto-merge already in desired state (enabled=${enabled}, want=${want_merge}); nothing to do" + fi diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f201fd..60e41e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), however this project does not use Semantic Versioning and there are no releases. Instead this file uses a date-based structure. -## 2026-05-21 +## 2026-05-27 + +### Added + +- `release.yaml` (release-please reusable workflow) gained an `auto-merge-level` input (`none`, `patch`, `minor`, `major`; default `none`). When set, the workflow enables GitHub auto-merge (`gh pr merge --auto --squash`) on the open release-please PR if the PR's bump is no larger than the configured ceiling, so the PR merges automatically once CI passes. The bump is derived from `.release-please-manifest.json` (next version on the PR head branch vs. current version on the base branch), so it is independent of each caller's PR-title configuration. Auto-merge is reconciled on every run — if a release-please PR's bump grows past the ceiling (e.g. from `patch` to `major`) before it is merged, auto-merge is disabled again. The default `none` preserves the previous behaviour (no auto-merge). The merge is performed with the `release-please` GitHub App token, so the merge is attributed to the App; the App must be granted bypass on any branch protection and the repository must have "Allow auto-merge" enabled. ### Changed