From 1181d67f98d7968eb6d9c4787d8e61e7d4d3ab2e Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Sun, 18 Oct 2026 16:24:38 +1300 Subject: [PATCH] Migrate to reviewed GitHub gem releases --- .github/release-rules/checks.json | 88 ++++++++++++++++++++ .github/release-rules/history.json | 22 +++++ .github/release-rules/reviews.json | 37 +++++++++ .github/release-rules/tags.json | 22 +++++ .github/workflows/release-prepare.yaml | 52 ++++++++++++ .github/workflows/release-publish.yaml | 103 ++++++++++++++++++++++++ .github/workflows/release-validate.yaml | 32 ++++++++ bake.rb | 10 +-- config/release.yaml | 28 +++++++ gems.rb | 2 +- readme.md | 6 +- 11 files changed, 391 insertions(+), 11 deletions(-) create mode 100644 .github/release-rules/checks.json create mode 100644 .github/release-rules/history.json create mode 100644 .github/release-rules/reviews.json create mode 100644 .github/release-rules/tags.json create mode 100644 .github/workflows/release-prepare.yaml create mode 100644 .github/workflows/release-publish.yaml create mode 100644 .github/workflows/release-validate.yaml create mode 100644 config/release.yaml diff --git a/.github/release-rules/checks.json b/.github/release-rules/checks.json new file mode 100644 index 00000000..dab29e68 --- /dev/null +++ b/.github/release-rules/checks.json @@ -0,0 +1,88 @@ +{ + "target": "branch", + "enforcement": "active", + "conditions": { + "ref_name": { + "include": [ + "refs/heads/main" + ], + "exclude": [] + } + }, + "name": "Gem release checks", + "bypass_actors": [ + { + "actor_id": 5, + "actor_type": "RepositoryRole", + "bypass_mode": "pull_request" + } + ], + "rules": [ + { + "type": "required_status_checks", + "parameters": { + "strict_required_status_checks_policy": true, + "do_not_enforce_on_create": false, + "required_status_checks": [ + { + "context": "3.3 on ubuntu-latest" + }, + { + "context": "3.4 on ubuntu-latest" + }, + { + "context": "4.0 on ubuntu-latest" + }, + { + "context": "3.3 on macos-latest" + }, + { + "context": "3.4 on macos-latest" + }, + { + "context": "4.0 on macos-latest" + }, + { + "context": "3.3 on windows-latest" + }, + { + "context": "3.4 on windows-latest" + }, + { + "context": "4.0 on windows-latest" + }, + { + "context": "3.3 on ubuntu" + }, + { + "context": "3.4 on ubuntu" + }, + { + "context": "4.0 on ubuntu" + }, + { + "context": "3.3 on macos" + }, + { + "context": "3.4 on macos" + }, + { + "context": "4.0 on macos" + }, + { + "context": "check" + }, + { + "context": "validate" + }, + { + "context": "asan on ubuntu-24.04" + }, + { + "context": "Release validation" + } + ] + } + } + ] +} diff --git a/.github/release-rules/history.json b/.github/release-rules/history.json new file mode 100644 index 00000000..e6f4af1e --- /dev/null +++ b/.github/release-rules/history.json @@ -0,0 +1,22 @@ +{ + "target": "branch", + "enforcement": "active", + "conditions": { + "ref_name": { + "include": [ + "refs/heads/main" + ], + "exclude": [] + } + }, + "name": "Gem release history", + "bypass_actors": [], + "rules": [ + { + "type": "deletion" + }, + { + "type": "non_fast_forward" + } + ] +} diff --git a/.github/release-rules/reviews.json b/.github/release-rules/reviews.json new file mode 100644 index 00000000..b36fd664 --- /dev/null +++ b/.github/release-rules/reviews.json @@ -0,0 +1,37 @@ +{ + "target": "branch", + "enforcement": "active", + "conditions": { + "ref_name": { + "include": [ + "refs/heads/main" + ], + "exclude": [] + } + }, + "name": "Gem release reviews", + "bypass_actors": [ + { + "actor_id": 5, + "actor_type": "RepositoryRole", + "bypass_mode": "pull_request" + } + ], + "rules": [ + { + "type": "pull_request", + "parameters": { + "required_approving_review_count": 2, + "dismiss_stale_reviews_on_push": true, + "require_last_push_approval": true, + "required_review_thread_resolution": true, + "require_code_owner_review": false, + "allowed_merge_methods": [ + "merge", + "squash", + "rebase" + ] + } + } + ] +} diff --git a/.github/release-rules/tags.json b/.github/release-rules/tags.json new file mode 100644 index 00000000..8f7a46f6 --- /dev/null +++ b/.github/release-rules/tags.json @@ -0,0 +1,22 @@ +{ + "name": "Gem release tags", + "target": "tag", + "enforcement": "active", + "bypass_actors": [], + "conditions": { + "ref_name": { + "include": [ + "refs/tags/v*" + ], + "exclude": [] + } + }, + "rules": [ + { + "type": "deletion" + }, + { + "type": "non_fast_forward" + } + ] +} diff --git a/.github/workflows/release-prepare.yaml b/.github/workflows/release-prepare.yaml new file mode 100644 index 00000000..1dc024fd --- /dev/null +++ b/.github/workflows/release-prepare.yaml @@ -0,0 +1,52 @@ +name: Prepare release + +on: + workflow_dispatch: + inputs: + bump: + description: Version increment + required: true + type: choice + options: [patch, minor, major] + refresh: + description: Preserve and regenerate an existing release branch + type: boolean + default: false + +permissions: + contents: write + pull-requests: write + +concurrency: + group: release-prepare + cancel-in-progress: false + +env: + BUNDLE_WITH: maintenance + +jobs: + prepare: + if: github.ref == 'refs/heads/main' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + with: + ref: main + fetch-depth: 0 + persist-credentials: false + - uses: ruby/setup-ruby@v1 + with: + ruby-version: "3.4" + bundler-cache: true + - name: Create release PR + env: + GITHUB_TOKEN: ${{ github.token }} + BUMP: ${{ inputs.bump }} + REFRESH: ${{ inputs.refresh }} + run: | + # GitHub.com's shared Actions bot ID; this identity is not for GitHub Enterprise Server. + # https://github.com/actions/checkout#push-a-commit-using-the-built-in-token + git config user.name 'github-actions[bot]' + git config user.email '41898282+github-actions[bot]@users.noreply.github.com' + case "$BUMP" in patch|minor|major) ;; *) exit 1 ;; esac + bundle exec bake "gem:github:release:$BUMP" "refresh=$REFRESH" diff --git a/.github/workflows/release-publish.yaml b/.github/workflows/release-publish.yaml new file mode 100644 index 00000000..722aac8a --- /dev/null +++ b/.github/workflows/release-publish.yaml @@ -0,0 +1,103 @@ +name: Publish release + +# Inspect the exact pushed commit and publish only a validated, merged release PR. +on: + push: + branches: ["main"] + +permissions: + contents: read + pull-requests: read + +env: + BUNDLE_WITH: maintenance + +jobs: + inspect: + runs-on: ubuntu-latest + outputs: + release: ${{ steps.inspect.outputs.release }} + commit: ${{ steps.inspect.outputs.commit }} + pull_request: ${{ steps.inspect.outputs.pull_request }} + steps: + - uses: actions/checkout@v7 + with: + ref: ${{ github.sha }} + fetch-depth: 0 + persist-credentials: false + - uses: ruby/setup-ruby@v1 + with: + ruby-version: "3.4" + bundler-cache: true + - id: inspect + env: + GITHUB_TOKEN: ${{ github.token }} + RELEASE_COMMIT: ${{ github.sha }} + run: bundle exec bake gem:github:release:resolve + + publish: + needs: inspect + if: needs.inspect.outputs.release == 'true' + runs-on: ubuntu-latest + environment: rubygems + concurrency: + group: release-publish + cancel-in-progress: false + queue: max + env: + RELEASE_PR: ${{ needs.inspect.outputs.pull_request }} + permissions: + contents: write + pull-requests: read + actions: read + id-token: write + attestations: write + steps: + - uses: actions/checkout@v7 + with: + # Inspection verified this merged commit belongs to the default branch. + ref: ${{ needs.inspect.outputs.commit }} + fetch-depth: 0 + persist-credentials: false + - uses: ruby/setup-ruby@v1 + with: + ruby-version: "3.4" + rubygems: '4.0.21' + bundler-cache: true + - name: Build or restore artifact + id: build + env: + GITHUB_TOKEN: ${{ github.token }} + GEM_SIGNING_KEY: ${{ secrets.GEM_SIGNING_KEY }} + run: bundle exec bake gem:github:release:build + - name: Sign RubyGems attestation + if: steps.build.outputs.restored != 'true' + env: + PACKAGE: ${{ steps.build.outputs.package }} + run: gem exec sigstore-cli:0.2.3 sign "$PACKAGE" --bundle "$PACKAGE.sigstore.json" + - name: Attest gem and release receipt + if: steps.build.outputs.restored != 'true' + id: attest + uses: actions/attest@v4 + with: + subject-path: | + ${{ steps.build.outputs.package }} + pkg/release.json + - name: Retain provenance bundle + if: steps.build.outputs.restored != 'true' + env: + ATTESTATION_BUNDLE: ${{ steps.attest.outputs.bundle-path }} + run: cp "$ATTESTATION_BUNDLE" pkg/provenance.sigstore.json + - name: Preserve release before upload + if: steps.build.outputs.restored != 'true' + uses: actions/upload-artifact@v7 + with: + name: ${{ steps.build.outputs.artifact }} + path: pkg/ + if-no-files-found: error + retention-days: 90 + - uses: rubygems/configure-rubygems-credentials@main + - name: Verify, publish, and finalize + env: + GITHUB_TOKEN: ${{ github.token }} + run: bundle exec bake gem:github:release:publish diff --git a/.github/workflows/release-validate.yaml b/.github/workflows/release-validate.yaml new file mode 100644 index 00000000..5d6c9f3b --- /dev/null +++ b/.github/workflows/release-validate.yaml @@ -0,0 +1,32 @@ +name: Validate release + +on: + pull_request: + branches: ["main"] + +permissions: + contents: read + +env: + BUNDLE_WITH: maintenance + +jobs: + validate: + name: Release validation + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + with: + ref: ${{ github.event.pull_request.head.sha }} + fetch-depth: 0 + persist-credentials: false + - uses: ruby/setup-ruby@v1 + with: + ruby-version: "3.4" + bundler-cache: true + - name: Regenerate release content + env: + RELEASE_BASE: ${{ github.event.pull_request.base.sha }} + run: bundle exec bake gem:github:release:validate "base=$RELEASE_BASE" + - name: Build unsigned package + run: bundle exec bake gem:build signing_key=false diff --git a/bake.rb b/bake.rb index 91b0b3fe..c2bfda0e 100644 --- a/bake.rb +++ b/bake.rb @@ -25,17 +25,11 @@ def before_test self.build end -# Update the project documentation with the new version number. +# Update copyrights and project documentation for the new version. # # @parameter version [String] The new version number. def after_gem_release_version_increment(version) + context["modernize:license"].call context["releases:update"].call(version) context["utopia:project:update"].call end - -# Create a GitHub release for the given tag. -# -# @parameter tag [String] The tag to create a release for. -def after_gem_release(tag:, **options) - context["releases:github:release"].call(tag) -end diff --git a/config/release.yaml b/config/release.yaml new file mode 100644 index 00000000..9c920c37 --- /dev/null +++ b/config/release.yaml @@ -0,0 +1,28 @@ +--- +schema: 1 +repository: socketry/io-event +branch: main +checks: +- 3.3 on ubuntu-latest +- 3.4 on ubuntu-latest +- 4.0 on ubuntu-latest +- 3.3 on macos-latest +- 3.4 on macos-latest +- 4.0 on macos-latest +- 3.3 on windows-latest +- 3.4 on windows-latest +- 4.0 on windows-latest +- 3.3 on ubuntu +- 3.4 on ubuntu +- 4.0 on ubuntu +- 3.3 on macos +- 3.4 on macos +- 4.0 on macos +- check +- validate +- asan on ubuntu-24.04 +- Release validation +approvals: 2 +signing: true +ruby: '3.4' +environment: rubygems diff --git a/gems.rb b/gems.rb index 18ac4873..983e7e56 100644 --- a/gems.rb +++ b/gems.rb @@ -8,7 +8,7 @@ gemspec group :maintenance, optional: true do - gem "bake-gem" + gem "bake-gem-github", "~> 0.6.0" gem "bake-modernize" gem "bake-releases" diff --git a/readme.md b/readme.md index c8a39d78..7d77deb0 100644 --- a/readme.md +++ b/readme.md @@ -83,12 +83,14 @@ bundle exec sus ### Making Releases -To make a new release: +To prepare a release pull request: ``` shell -bundle exec bake gem:release:patch # or minor or major +bundle exec bake gem:github:release:patch # or minor or major ``` +After review and merge, GitHub Actions publishes the release. See [bake-gem-github](https://github.com/socketry/bake-gem-github) for setup and release instructions. + ### Developer Certificate of Origin In order to protect users of this project, we require all contributors to comply with the [Developer Certificate of Origin](https://developercertificate.org/). This ensures that all contributions are properly licensed and attributed.