From 5ff5602b8b55d8b0193bb98f86ae114184c8fcd6 Mon Sep 17 00:00:00 2001 From: Roman Lemekha Date: Fri, 26 Jun 2026 13:16:00 +0200 Subject: [PATCH] feat: add release-please + zero-cooldown automerge for shared reusables Auto-versions this repo via release-please (Conventional Commits): fix:/chore: -> patch, feat: -> minor (auto-merged on green once branch protection's zizmor check passes), feat!/BREAKING -> major (release PR left open for manual approval). On release, the moving major tag (vN) is force-moved to the released commit. Adds a Renovate rule in the shared preset so consumers of roleme/workflows pick up new releases with no minimumReleaseAge soak and auto-merge non-major bumps once their own required checks are green. Major bumps stay manual. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/release-please.yml | 77 ++++++++++++++++++++++++++++ .github/workflows/zizmor.yml | 5 +- .release-please-manifest.json | 3 ++ release-please-config.json | 12 +++++ renovate-presets/default.json | 17 ++++++ 5 files changed, 112 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/release-please.yml create mode 100644 .release-please-manifest.json create mode 100644 release-please-config.json diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml new file mode 100644 index 0000000..be2d839 --- /dev/null +++ b/.github/workflows/release-please.yml @@ -0,0 +1,77 @@ +name: release-please + +on: + push: + branches: + - main + +# release-please opens/maintains a release PR and, when that PR merges, cuts a +# git tag. Consumers (docker_infra, asia-trip-bot, training_tracker) pin the +# reusables to a SHA with a `# vX.Y.Z` comment; Renovate then tracks that tag. +permissions: + contents: write + pull-requests: write + +# Never run two release passes at once (a fast second push could race the +# tag-move / auto-merge steps below). +concurrency: + group: release-please + cancel-in-progress: false + +jobs: + release-please: + runs-on: ubuntu-latest + steps: + - id: release + uses: googleapis/release-please-action@45996ed1f6d02564a971a2fa1b5860e934307cf7 # v5.0.0 + with: + config-file: release-please-config.json + manifest-file: .release-please-manifest.json + + # ----- when a release PR is OPEN/UPDATED: auto-merge non-major bumps ----- + # release-please re-runs this job whenever it opens or updates the release + # PR. patch/minor bumps merge themselves once branch-protection checks + # (zizmor) go green; a major bump (X.0.0) is left open for manual review. + - name: Enable auto-merge for non-major release PRs + if: ${{ steps.release.outputs.prs_created == 'true' }} + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PRS: ${{ steps.release.outputs.prs }} + run: | + set -euo pipefail + # outputs.prs is a JSON array of {number, ...}; outputs.prs_created is + # set, but the per-version flag lives in the PR title / labels. + echo "$PRS" | jq -c '.[]' | while read -r pr; do + number=$(echo "$pr" | jq -r '.number') + # The release PR title is e.g. "chore(main): release 1.2.3". + version=$(gh pr view "$number" --json title --jq '.title' \ + | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1 || true) + if [ -z "$version" ]; then + echo "PR #$number: could not parse version, leaving for manual review" + continue + fi + major=${version%%.*} + minorpatch=${version#*.} + # A major bump is X.0.0 — leave it open for manual approval. + if [ "$minorpatch" = "0.0" ]; then + echo "PR #$number ($version): MAJOR bump — manual review required" + else + echo "PR #$number ($version): non-major — enabling auto-merge" + gh pr merge "$number" --auto --squash + fi + done + + # ----- after a release is CUT: move the major tag (v1) to it ----- + - name: Move major tag to the new release + if: ${{ steps.release.outputs.release_created == 'true' }} + env: + MAJOR: ${{ steps.release.outputs.major }} + SHA: ${{ steps.release.outputs.sha }} + run: | + set -euo pipefail + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + # Force-move the moving major tag (e.g. v1) to the released commit so + # consumers who choose to track @v1 follow non-breaking releases. + git tag -fa "v${MAJOR}" "${SHA}" -m "Release v${MAJOR} -> ${SHA}" + git push origin "v${MAJOR}" --force diff --git a/.github/workflows/zizmor.yml b/.github/workflows/zizmor.yml index 050e93d..8e48167 100644 --- a/.github/workflows/zizmor.yml +++ b/.github/workflows/zizmor.yml @@ -1,9 +1,10 @@ name: zizmor (Actions security) on: + # Runs on every PR (not just workflow changes) so it is a dependable required + # status check: the release-please PR edits only the manifest/CHANGELOG, and a + # path-filtered required check would never report and would deadlock that PR. pull_request: - paths: - - '.github/workflows/**' # Least privilege: zizmor only reads the repo. advanced-security: false means # results are surfaced as workflow annotations (not SARIF upload), so no diff --git a/.release-please-manifest.json b/.release-please-manifest.json new file mode 100644 index 0000000..37fcefa --- /dev/null +++ b/.release-please-manifest.json @@ -0,0 +1,3 @@ +{ + ".": "1.0.0" +} diff --git a/release-please-config.json b/release-please-config.json new file mode 100644 index 0000000..3e1dab5 --- /dev/null +++ b/release-please-config.json @@ -0,0 +1,12 @@ +{ + "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json", + "packages": { + ".": { + "release-type": "simple", + "include-component-in-tag": false, + "include-v-in-tag": true, + "bump-minor-pre-major": false, + "bump-patch-for-minor-pre-major": false + } + } +} diff --git a/renovate-presets/default.json b/renovate-presets/default.json index 6b9c8aa..03188d2 100644 --- a/renovate-presets/default.json +++ b/renovate-presets/default.json @@ -11,6 +11,23 @@ "matchManagers": ["github-actions"], "matchUpdateTypes": ["patch", "minor"], "automerge": true + }, + { + "description": "Our own shared reusables (roleme/workflows) — no soak, auto-merge non-major on green. We control this repo and it is released via release-please (patch/minor verified by its own CI before the tag is cut), so consumers should pick up new versions immediately and let their own required checks be the gate. Major bumps fall through to the rule below and stay manual.", + "matchManagers": ["github-actions"], + "matchDepNames": ["roleme/workflows"], + "matchUpdateTypes": ["patch", "minor", "pin", "digest", "pinDigest"], + "minimumReleaseAge": "0", + "automerge": true, + "automergeType": "pr" + }, + { + "description": "roleme/workflows major bumps — never automerge (breaking change in shared CI). Pin to the new vN by hand after reviewing the changelog.", + "matchManagers": ["github-actions"], + "matchDepNames": ["roleme/workflows"], + "matchUpdateTypes": ["major"], + "automerge": false, + "labels": ["dependencies", "renovate", "breaking-change"] } ] }