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"] } ] }