diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index cd22840..df79ce2 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -2,13 +2,16 @@ name: Release on: push: - branches: [v2.x] + branches: + - v2.x + - 'release-please--branches--**' permissions: contents: read jobs: release-please: + if: github.ref == 'refs/heads/v2.x' runs-on: ubuntu-latest outputs: releases_created: ${{ steps.release.outputs.releases_created }} @@ -24,6 +27,52 @@ jobs: # branch (master) and never builds a release PR for this branch. target-branch: v2.x + # The branch ruleset requires verified signatures on every PR commit, but two + # paths put unsigned commits on the release branch: release-please creates its + # commits through the REST API (which cannot sign), and the "Update branch" + # rebase button rewrites the commit unsigned. Reacting to pushes on the release + # branch itself covers both: whenever its head commit is unsigned, amend it with + # the bot's GPG key (same key and setup as logto-io/js). + sign-release-branch: + if: startsWith(github.ref_name, 'release-please--branches--') + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + token: ${{ secrets.BOT_PAT }} + # Amending in the default shallow clone rewrites the head commit into a + # parentless orphan (its parents are behind the shallow boundary) — the + # full history is required for the amend to keep the parent. + fetch-depth: 0 + + - name: Import bot GPG key + uses: crazy-max/ghaction-import-gpg@v6 + with: + gpg_private_key: ${{ secrets.BOT_GPG_KEY }} + passphrase: ${{ secrets.BOT_GPG_PASSPHRASE }} + git_user_signingkey: true + git_commit_gpgsign: true + + - name: Sign the head commit when unsigned + run: | + # Only amend truly unsigned commits ("N"); an already-signed commit + # ("E"/"U"/"G") is left alone so this job does not retrigger itself. + if [ "$(git log -1 --format=%G?)" != "N" ]; then + echo "Head commit is already signed, nothing to do" + exit 0 + fi + git config user.name silverhand-bot + git config user.email bot@silverhand.io + git commit --amend --no-edit --gpg-sign + # Never push a history rewrite: the amended commit must keep its parent + # (an empty %P means the orphan bug above — abort instead of clobbering + # the branch and auto-closing the release PR). + if [ -z "$(git log -1 --format=%P)" ]; then + echo "Amended commit lost its parent; refusing to push" >&2 + exit 1 + fi + git push --force origin HEAD:${{ github.ref_name }} + # GitHub marks the most recently created release as "Latest" by default, # so a maintenance release cut here would steal the badge from the v3 line. unmark-latest: diff --git a/RELEASE.md b/RELEASE.md index d27b9f9..bb72c2e 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -14,6 +14,13 @@ Releases are automated via [release-please](https://github.com/googleapis/releas | `feat!:` or commit body containing `BREAKING CHANGE:` | major (**X+1**.0.0) | | `chore:` / `refactor:` / `docs:` / `test:` / `ci:` / `revert:` | no bump (still listed in changelog) | + release-please creates the release PR commit through the REST API, which cannot + produce a GPG signature, and the "Update branch" rebase button also rewrites the + commit unsigned — either way the branch ruleset's required-signatures rule would + block the merge. Every push to the release branch therefore runs a job that + amends an unsigned head commit with the bot's GPG key (`BOT_GPG_KEY` org secret, + shared with logto-io/js). + 3. Merging the release PR triggers the workflow again. release-please creates the git tag `vX.Y.Z` and a GitHub Release with auto-generated notes immediately, then sets `releases_created=true`. 4. The publish job (gated on `releases_created`) runs next: - Builds and signs `io.logto.sdk:kotlin` and `io.logto.sdk:android` with in-memory PGP keys.