diff --git a/.github/release-rules/checks.json b/.github/release-rules/checks.json new file mode 100644 index 0000000..31b7ef6 --- /dev/null +++ b/.github/release-rules/checks.json @@ -0,0 +1,64 @@ +{ + "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" + }, + { + "context": "3.3 on macos" + }, + { + "context": "3.4 on ubuntu" + }, + { + "context": "3.4 on macos" + }, + { + "context": "4.0 on ubuntu" + }, + { + "context": "4.0 on macos" + }, + { + "context": "check" + }, + { + "context": "ruby on ubuntu" + }, + { + "context": "ruby on macos" + }, + { + "context": "validate" + }, + { + "context": "Release validation" + } + ] + } + } + ] +} diff --git a/.github/release-rules/history.json b/.github/release-rules/history.json new file mode 100644 index 0000000..e6f4af1 --- /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 0000000..b36fd66 --- /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 0000000..8f7a46f --- /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 0000000..1dc024f --- /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 0000000..722aac8 --- /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 0000000..5d6c9f3 --- /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 9441211..677edff 100644 --- a/bake.rb +++ b/bake.rb @@ -10,10 +10,3 @@ def after_gem_release_version_increment(version) 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 0000000..e1eaf1c --- /dev/null +++ b/config/release.yaml @@ -0,0 +1,20 @@ +--- +schema: 1 +repository: socketry/protocol-http2 +branch: main +checks: +- 3.3 on ubuntu +- 3.3 on macos +- 3.4 on ubuntu +- 3.4 on macos +- 4.0 on ubuntu +- 4.0 on macos +- check +- ruby on ubuntu +- ruby on macos +- validate +- Release validation +approvals: 2 +signing: true +ruby: '3.4' +environment: rubygems diff --git a/gems.rb b/gems.rb index 5571f20..6fea66d 100644 --- a/gems.rb +++ b/gems.rb @@ -9,7 +9,7 @@ group :maintenance, optional: true do gem "bake-modernize" - gem "bake-gem" + gem "bake-gem-github" gem "bake-releases" gem "agent-context" diff --git a/readme.md b/readme.md index 717b50e..c229447 100644 --- a/readme.md +++ b/readme.md @@ -81,12 +81,14 @@ $ bundle exec sus ### Making Releases -To make a new release: +To prepare a release branch and open a pull request from an up-to-date `main`: ``` bash -$ bundle exec bake gem:release:patch # or minor or major +$ bundle exec bake gem:github:release:patch # or minor or major ``` +See [bake-gem-github](https://github.com/socketry/bake-gem-github) for setup, remote releases, and recovery. + ### 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.