bake-gem-github 0.3.1 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cbf4137a4b137fec26486a02e3941687f2c5fc9ded9ab95eb95619657892e1ae
4
- data.tar.gz: c4e2cf0281e703e2d2264e5b8f08420ba7afeb5f7819e075a196e9334f24a300
3
+ metadata.gz: 4f7557669b8ec839c193fbf060816c360413ba4be3cd8e223f294acb8e29baed
4
+ data.tar.gz: c4806b56446c74082b785e6f19ac8a36cfbe1cd79a07ba05eda3bcd223ceaeb8
5
5
  SHA512:
6
- metadata.gz: 2996e3c56f916e0d367c6fc75e3b7d9a478d8a08ee4d6d9bd775412978965c8c83d0b2608655d5d0c23776ce29af835cfa8dea4ec23ffb9e1afb3d0cefb74069
7
- data.tar.gz: 4b78a2dd8d672b8831b8408e59494832ef45afee93489e9f27dc4aa7aae593f541350133886fa2f7957e2befee20abd4faa92b9df0b110620db78cdd1c1b2b01
6
+ metadata.gz: 522b233facca2afd17c28320ee34de988053bc33832a595c4e54ad34a308997813d54afaf43aa8a6c6b785b0a882b0f953aa5fecd7c4d004c0d4a21180d8f9b7
7
+ data.tar.gz: 7d4dbe694f2209d5a369c2e8100101a824b1cc0839023dd4a58976b9022154428c6e7e87ab8185b70f17e7913746f07bf4abdeabd4145c510ade5c3b8ba44ee3
checksums.yaml.gz.sig CHANGED
Binary file
@@ -26,6 +26,14 @@ def major(refresh: false)
26
26
  Bake::Gem::GitHub::Project.new(context.root).prepare(context, "major", refresh: refresh)
27
27
  end
28
28
 
29
+ # Regenerate release content and require one release commit; ordinary PRs pass without a commit limit.
30
+ # @parameter base [String] The current target commit.
31
+ # @parameter candidate [String] The proposed PR head.
32
+ # @returns [Hash | Nil] Release metadata, or nil for an ordinary PR.
33
+ def validate(base:, candidate: "HEAD")
34
+ Bake::Gem::GitHub::Project.new(context.root).validate(base: base, candidate: candidate)
35
+ end
36
+
29
37
  # Resolve and validate a pushed commit, emitting its merged PR and commit for publishing.
30
38
  # @parameter number [String | Nil] A merged PR number for older workflows; defaults to `RELEASE_PR`.
31
39
  # @parameter commit [String | Nil] The pushed commit SHA; defaults to `RELEASE_COMMIT` and takes precedence over `number`.
@@ -6,7 +6,7 @@ This guide explains how to configure reviewed Ruby gem releases and prepare the
6
6
 
7
7
  Maintainers prepare a release PR containing the version bump and generated release notes. CI regenerates those changes from the current base to verify the content. Native GitHub rules control approval and merging. A push to the default branch starts release inspection; GitHub Actions builds the exact pushed commit only when it is a validated, merged release PR, then publishes its verified artifact to RubyGems.
8
8
 
9
- `bake-gem` provides version updates, release hooks, and clean builds. `bake-gem-github` adds PR preparation, GitHub policy, and remote publishing. The supported process uses one gemspec, stable three-part versions, merge or squash merging, and RubyGems.org.
9
+ `bake-gem` provides version updates, release hooks, and clean builds. `bake-gem-github` adds PR preparation, GitHub policy, and remote publishing. The supported process uses one gemspec, stable three-part versions, single-commit release PRs, and RubyGems.org. Release PRs can be squash merged, rebase merged, or merged with a merge commit.
10
10
 
11
11
  ## Installation
12
12
 
@@ -69,10 +69,14 @@ reviewers:
69
69
 
70
70
  Replace `your-org/managers` with your organization and team slug, for example `socketry/managers`. Individual user logins are also supported. Reviewers need at least read access to the repository. Setup resolves their GitHub IDs without changing repository access or team membership.
71
71
 
72
+ Reviewer teams must have **Visible** visibility in GitHub team settings. Plan and apply reject **Secret** teams before changing any settings, because GitHub can silently discard them from the environment reviewer list. Setup does not change team visibility.
73
+
72
74
  GitHub accepts one to six users or teams, and **one approval from any listed reviewer or team member is sufficient**. It does not support a minimum environment approval count. The default two PR approvals are independent of this publishing approval.
73
75
 
74
76
  Create the environment and restrict its deployment branch as described above before running plan or apply with reviewers configured. The plan previews the current and desired environment settings. Apply replaces its reviewer list while preserving its wait timer, self-review prevention, administrator bypass setting, and deployment branch restrictions. Custom deployment protection rules are managed separately and are not modified. Reapplying an identical reviewer list leaves the environment unchanged.
75
77
 
78
+ After updating the environment, apply reads its settings back and fails if the reviewer identities or preserved protections differ from the requested configuration. Reviewer order does not matter. Earlier updates may already have completed; inspect the environment in GitHub and rerun `gem:github:setup:plan` before retrying apply.
79
+
76
80
  Omitting `reviewers` leaves environment settings unmanaged, including any existing reviewer requirement. An empty list is rejected. To remove an existing requirement, change the environment settings explicitly in GitHub.
77
81
 
78
82
  The RubyGems Trusted Publisher must explicitly require the `rubygems` environment; leaving that field blank would allow this trusted publisher to authenticate jobs without the environment approval. Keep administrator bypass enabled if administrators should be able to explicitly authorize publication without a reviewer. Environment approvals are available for public repositories on GitHub Free.
@@ -86,7 +90,9 @@ bundle exec bake gem:github:setup:plan
86
90
  bundle exec bake gem:github:setup:apply
87
91
  ```
88
92
 
89
- Apply updates the four managed rulesets and, when configured, the existing environment's reviewer list. It preserves unrelated rulesets. Other repository and organization protections still apply. Keep check names in `config/release.yaml` synchronized with the workflows, and apply updated rules after renamed jobs are available. Keep rebase merging and merge queues disabled for this process.
93
+ Apply updates the four managed rulesets and, when configured, the existing environment's reviewer list. It preserves unrelated rulesets. Other repository and organization protections still apply. Keep check names in `config/release.yaml` synchronized with the workflows, and apply updated rules after renamed jobs are available.
94
+
95
+ The generated rules allow merge, squash, and rebase methods; repository settings determine which are available. For linear history, enable squash and/or rebase merging and disable merge commits in repository settings. Release validation requires exactly one commit when the version changes; ordinary PRs have no commit limit. Keep merge queues disabled for this process.
90
96
 
91
97
  ## Prepare the first release PR
92
98
 
@@ -111,13 +117,15 @@ git diff
111
117
 
112
118
  This updates managed files in the working tree and returns their changed paths. Review the diff and selectively retain repository customizations before committing. The task does not stage, commit, or change remote settings. Repeated updates produce no further changes unless customizations differ from the templates. Apply changed rulesets after the corresponding workflows are running.
113
119
 
120
+ To enable rebase merging on an existing installation, regenerate and merge `release-validate.yaml` so it runs `gem:github:release:validate`, which requires single-commit release PRs. Then review `gem:github:setup:plan` and run `gem:github:setup:apply` to allow rebase merging in the managed rules. Enable rebase merging in repository settings as well. Upgrading the gem alone does not update workflows or live rules.
121
+
114
122
  Regenerate existing workflows to adopt publishing on `push` instead of `pull_request_target`. The workflow filename and `rubygems` environment remain the same, so the RubyGems Trusted Publisher configuration does not change. No exception to GitHub's `pull_request_target` execution policy is needed. The resolve task continues to accept PR numbers from older workflows while you migrate.
115
123
 
116
124
  The release workflows follow `bake modernize` action versions and use moving major tags where available. The RubyGems credentials action uses its [documented `@main` reference](https://github.com/rubygems/configure-rubygems-credentials#trusted-publisher-recommended). Repositories that require fixed revisions can customize these references.
117
125
 
118
126
  ## Current scope
119
127
 
120
- The process has published `bake-gem-github` through GitHub Actions. Each adopting repository still needs its own reviewed setup and successful release. Public single-gem repositories, ordinary stable versions, merge/squash, GitHub-hosted Linux runners, and RubyGems.org are the supported starting point.
128
+ The process has published `bake-gem-github` through GitHub Actions. Each adopting repository still needs its own reviewed setup and successful release. Public single-gem repositories, ordinary stable versions, single-commit release PRs using squash/rebase/merge, GitHub-hosted Linux runners, and RubyGems.org are the supported starting point.
121
129
 
122
130
  Native build matrices, reusable publisher workflows, merge queues, automated RubyGems ownership/MFA setup, cross-run artifact recovery, and organization-wide migration are outside the current setup tasks.
123
131
 
@@ -19,11 +19,20 @@ gh workflow run release-prepare.yaml -f bump=patch
19
19
 
20
20
  Replace `patch` with `minor` or `major`. The wrapper fetches the default branch and tags, refuses a stale local checkout, and validates an existing release PR before returning its URL. A matching local or remote branch is reused if PR creation was interrupted. Multiple open release PRs or a different requested bump stop preparation. GitHub's built-in token may require a writer to approve running workflows for its created PR; enable Actions' permission to create PRs. An organization-owned App token can be adopted later if automatic CI triggering is needed.
21
21
 
22
- All release changes belong in the PR. Core preparation commits additions and deletions from release hooks but never pushes, tags or publishes. Validation independently generates the expected tree from the current base. A changed base SHA alone is fine; changed generated notes are not. Ordinary PRs with no version change pass release validation and still build unsigned.
22
+ All release changes belong in one commit, including the version bump and generated files. Core preparation commits additions and deletions from release hooks but never pushes, tags or publishes. Validation independently generates the expected tree from the current base and requires exactly one commit ahead of that base. A changed base SHA alone is fine; changed generated notes are not. Ordinary PRs with no version change have no commit limit and still build unsigned.
23
+
24
+ To run the same content and commit-count validation locally from a release branch:
25
+
26
+ ``` bash
27
+ git fetch origin main
28
+ bundle exec bake gem:github:release:validate base=origin/main
29
+ ```
30
+
31
+ Replace `main` with your configured default branch. When correcting a release, amend the existing commit or regenerate it with `refresh=true` instead of adding fixup commits. Amended content must still match the generated release; changes to release notes or generation hooks usually belong on the default branch followed by a refresh.
23
32
 
24
33
  ## Publish the merged release
25
34
 
26
- Merging into the configured default branch triggers `release-publish.yaml` through its `push` event. Inspection uses the exact pushed SHA and resolves its associated PR through GitHub. Publication requires one matching merged PR in this repository, targeting the configured branch, with that exact merge commit. Ordinary changes do not publish; release changes without a matching merged PR fail inspection. Both merge commits and squash merges are supported, including merged fork PRs.
35
+ Merging into the configured default branch triggers `release-publish.yaml` through its `push` event. Inspection uses the exact pushed SHA and resolves its associated PR through GitHub. Publication requires one matching merged PR in this repository, targeting the configured branch, with that exact resulting commit. Ordinary changes do not publish; release changes without a matching merged PR fail inspection. Single-commit release PRs support squash merging, rebase merging, and merge commits, including merged fork PRs. Publishing validates against the resulting commit's first parent, which is the default branch immediately before the release landed.
27
36
 
28
37
  Each release must land as the tip of its own push, as it does when merging a PR through GitHub. Later pushes do not change a pending release's source: inspection and publishing remain pinned to the original commit, and reruns use the same event. Do not combine a release and later changes into one direct push. If you automate merging, use a GitHub App or personal access token; pushes made using a workflow's `GITHUB_TOKEN` do not trigger another workflow.
29
38
 
@@ -45,4 +54,4 @@ bundle exec bake gem:github:release:patch refresh=true
45
54
  gh workflow run release-prepare.yaml -f bump=patch -f refresh=true
46
55
  ```
47
56
 
48
- Refresh first pushes the complete previous release commit to `release-backups/vVERSION/OLD_SHA`, including manual edits. It then regenerates in a clean worktree from the current default branch, validates, and updates the existing release branch using an explicit `--force-with-lease`. A concurrent remote edit causes the push to fail. Existing local release branches are left intact. Review the backup against the refreshed PR; incorporate necessary manual changes into the default branch or generation hooks and refresh again. Keep the backup until that review is complete. Replace `main` and `patch` with your configured branch and original bump type.
57
+ Refresh first pushes the complete previous release commit to `release-backups/vVERSION/OLD_SHA`, including manual edits. It then regenerates one release commit in a clean worktree from the current default branch, validates, and updates the existing release branch using an explicit `--force-with-lease`. A concurrent remote edit causes the push to fail. Existing local release branches are left intact. Review the backup against the refreshed PR; incorporate necessary manual changes into the default branch or generation hooks and refresh again. Keep the backup until that review is complete. Replace `main` and `patch` with your configured branch and original bump type.
@@ -6,7 +6,7 @@ Use this when checking a completed release or confirming which source commit pro
6
6
 
7
7
  ## What publishing verifies
8
8
 
9
- After merge/squash, the publishing workflow verifies GitHub's merged PR record and ancestry, then checks out the exact merged commit. Later development on the default branch is allowed. It regenerates against the merged commit's **first parent**, builds in a clean worktree, optionally certificate-signs, and creates two attestations over the final bytes:
9
+ After squash, rebase, or merge, the publishing workflow verifies GitHub's merged PR record and ancestry, then checks out the exact resulting commit. Each release PR contains one commit, so the resulting commit's **first parent** is the default branch immediately before the release landed. Later development on the default branch is allowed. Publishing regenerates against that first parent, builds in a clean worktree, optionally certificate-signs, and creates two attestations over the final bytes:
10
10
 
11
11
  - A Sigstore bundle submitted explicitly with `gem push --attestation` using RubyGems 4.0.21.
12
12
  - GitHub's native SLSA provenance covering both the gem and `release.json`. This signed receipt binds the gem digest to the exact release commit, even when the workflow's own default-branch revision is newer.
@@ -78,7 +78,7 @@ module Bake
78
78
  candidate = @release.resolve("HEAD")
79
79
  end
80
80
 
81
- metadata = @release.validate(base: base, candidate: candidate)
81
+ metadata = validate(base: base, candidate: candidate, optional: false)
82
82
  raise "Release branch does not contain the requested version #{version}." unless metadata.fetch(:version) == version
83
83
 
84
84
  push("--force-with-lease=#{release_ref}:#{remote_commit}", "#{candidate}:#{release_ref}")
@@ -90,6 +90,23 @@ module Bake
90
90
  )
91
91
  end
92
92
 
93
+ # Validate PR content and require each proposed release to contain exactly one commit.
94
+ # @parameter base [String] The current target commit.
95
+ # @parameter candidate [String] The proposed PR head.
96
+ # @parameter optional [Boolean] Accept ordinary PRs without a version change.
97
+ # @returns [Hash | Nil] Release metadata, or nil for an ordinary PR.
98
+ # @raises [RuntimeError] If the release content is invalid or the release contains multiple commits.
99
+ def validate(base:, candidate: "HEAD", optional: true)
100
+ metadata = @release.validate(base: base, candidate: candidate, optional: optional)
101
+ if metadata
102
+ range = "#{metadata.fetch(:base)}..#{metadata.fetch(:commit)}"
103
+ count = readlines("git", "rev-list", "--count", range, chdir: @root).join.strip
104
+ raise "Release PRs must contain exactly one commit. Amend or regenerate the release commit instead of adding commits." unless count == "1"
105
+ end
106
+
107
+ return metadata
108
+ end
109
+
93
110
  # Resolve a merged PR through GitHub, and require its actual merge commit in default-branch history.
94
111
  # @parameter number [String | Integer] The positive PR number.
95
112
  # @returns [Hash] GitHub PR data with string keys, including `number` and `merge_commit_sha`.
@@ -157,6 +174,7 @@ module Bake
157
174
 
158
175
  # Return a read-only comparison of managed settings and current repository settings.
159
176
  # @returns [Hash] Desired rules, existing rules, environments, optional environment changes, and expected Trusted Publisher settings. This does not verify RubyGems ownership or publisher configuration.
177
+ # @raises [RuntimeError] If a reviewer team is secret or belongs to another organization.
160
178
  def doctor
161
179
  {
162
180
  desired_rules: Setup.rules(@config),
@@ -174,8 +192,9 @@ module Bake
174
192
 
175
193
  # Apply the named rulesets and configured reviewers for an existing environment. Invoke after reviewing doctor output.
176
194
  # Preserves the environment's wait timer, self-review prevention, administrator bypass, and branch restrictions.
195
+ # Reads back updated environment settings to verify GitHub retained them.
177
196
  # @returns [Hash] The desired ruleset payloads after successful application.
178
- # @raises [RuntimeError] If more than one existing ruleset has a managed name.
197
+ # @raises [RuntimeError] If a reviewer team is unsuitable, managed rulesets are ambiguous, or environment verification fails. Earlier updates may already have completed.
179
198
  # @raises [Bake::Gem::CommandExecutionError] If an API operation fails; earlier updates may already have completed.
180
199
  def apply
181
200
  changes = environment_changes
@@ -193,6 +212,9 @@ module Bake
193
212
 
194
213
  if changes && changes.fetch(:current) != changes.fetch(:desired)
195
214
  write_api("repos/#{@repository}/#{environment_path}", changes.fetch(:desired), method: "PUT")
215
+ unless environment_settings == changes.fetch(:desired)
216
+ raise "GitHub did not retain the requested settings for environment #{changes.fetch(:name)}. Check its reviewers and protection rules in GitHub, then rerun gem:github:setup:plan."
217
+ end
196
218
  end
197
219
 
198
220
  return rules
@@ -209,19 +231,26 @@ module Bake
209
231
  return nil unless @config.key?("reviewers")
210
232
  Setup.validate_reviewers(@config["reviewers"])
211
233
 
234
+ current = environment_settings
235
+ reviewers = @config.fetch("reviewers").map{|name| resolve_reviewer(name)}.uniq.sort_by{|reviewer| reviewer.values_at(:type, :id)}
236
+
237
+ return {name: @config.fetch("environment"), current: current, desired: current.merge(reviewers: reviewers)}
238
+ end
239
+
240
+ # Read the environment settings, comparing reviewer identities independently of their order.
241
+ def environment_settings
212
242
  environment = api(environment_path)
213
243
  protections = environment.fetch("protection_rules").to_h{|rule| [rule.fetch("type"), rule]}
214
244
  reviews = protections.fetch("required_reviewers", {})
215
- current = {
245
+ reviewers = reviews.fetch("reviewers", []).map{|entry| {type: entry.fetch("type"), id: entry.fetch("reviewer").fetch("id")}}
246
+
247
+ return {
216
248
  wait_timer: protections.fetch("wait_timer", {}).fetch("wait_timer", 0),
217
249
  prevent_self_review: reviews.fetch("prevent_self_review", false),
218
250
  can_admins_bypass: environment.fetch("can_admins_bypass"),
219
251
  deployment_branch_policy: environment.fetch("deployment_branch_policy"),
220
- reviewers: reviews.fetch("reviewers", []).map{|entry| {type: entry.fetch("type"), id: entry.fetch("reviewer").fetch("id")}},
252
+ reviewers: reviewers.uniq.sort_by{|reviewer| reviewer.values_at(:type, :id)},
221
253
  }
222
- reviewers = @config.fetch("reviewers").map{|name| resolve_reviewer(name)}
223
-
224
- return {name: @config.fetch("environment"), current: current, desired: current.merge(reviewers: reviewers)}
225
254
  end
226
255
 
227
256
  def resolve_reviewer(name)
@@ -235,6 +264,9 @@ module Bake
235
264
  type = "User"
236
265
  end
237
266
  response = JSON.parse(readlines("gh", "api", path, chdir: @root).join)
267
+ if type == "Team" && response.fetch("privacy") == "secret"
268
+ raise "Reviewer team #{name} is Secret. Change its visibility to Visible in GitHub team settings before running setup."
269
+ end
238
270
 
239
271
  return {type: type, id: response.fetch("id")}
240
272
  end
@@ -89,7 +89,7 @@ module Bake
89
89
  require_last_push_approval: true,
90
90
  required_review_thread_resolution: true,
91
91
  require_code_owner_review: false,
92
- allowed_merge_methods: ["merge", "squash"],
92
+ allowed_merge_methods: ["merge", "squash", "rebase"],
93
93
  },
94
94
  }],
95
95
  ),
@@ -9,7 +9,7 @@ module Bake
9
9
  module Gem
10
10
  # GitHub pull request and release orchestration for bake-gem.
11
11
  module GitHub
12
- VERSION = "0.3.1"
12
+ VERSION = "0.5.0"
13
13
  end
14
14
  end
15
15
  end
data/readme.md CHANGED
@@ -24,6 +24,14 @@ Please see the [project documentation](https://socketry.github.io/bake-gem-githu
24
24
 
25
25
  Please see the [project releases](https://socketry.github.io/bake-gem-github/releases/index) for all releases.
26
26
 
27
+ ### v0.5.0
28
+
29
+ - Support rebase merging by requiring each release PR to contain exactly one commit, while leaving ordinary PRs unrestricted. Generated rules allow merge, squash, and rebase methods according to repository settings.
30
+
31
+ ### v0.4.0
32
+
33
+ - Reject Secret environment reviewer teams before applying release settings, and verify that GitHub retained the requested reviewers and existing protections after updating the environment.
34
+
27
35
  ### v0.3.1
28
36
 
29
37
  - Publish validated release PRs from pushes to the default branch, retaining the exact merged commit and environment approval without requiring `pull_request_target`.
data/releases.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Releases
2
2
 
3
+ ## v0.5.0
4
+
5
+ - Support rebase merging by requiring each release PR to contain exactly one commit, while leaving ordinary PRs unrestricted. Generated rules allow merge, squash, and rebase methods according to repository settings.
6
+
7
+ ## v0.4.0
8
+
9
+ - Reject Secret environment reviewer teams before applying release settings, and verify that GitHub retained the requested reviewers and existing protections after updating the environment.
10
+
3
11
  ## v0.3.1
4
12
 
5
13
  - Publish validated release PRs from pushes to the default branch, retaining the exact merged commit and environment approval without requiring `pull_request_target`.
@@ -27,6 +27,6 @@ jobs:
27
27
  - name: Regenerate release content
28
28
  env:
29
29
  RELEASE_BASE: ${{ github.event.pull_request.base.sha }}
30
- run: bundle exec bake gem:release:validate "base=$RELEASE_BASE" optional=true
30
+ run: bundle exec bake gem:github:release:validate "base=$RELEASE_BASE"
31
31
  - name: Build unsigned package
32
32
  run: bundle exec bake gem:build signing_key=false
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bake-gem-github
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.1
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
metadata.gz.sig CHANGED
@@ -1,2 +1 @@
1
- ��iH����^�*� CK���$��_9#��G �~/�5�t�$
2
- ㋏R5�  ����k��������3�C<j�g>b�i�"������Pkh�m}T2�;r���PV}|(gC�o�l���F�X䔅�SI�WY;���0�UԦ�h!*#�Z�@���5���*�=��&��Ϸ�S�@���Ȧ�vZ"��}�r�[��VG�[��1��V���h�����\eB����6%w;*��7S����x�#D���6���ﻳ:@y|��ky��@�dV��&�G.z.H��
1
+ ���y0�a�tq��ok��̤�ebَk��z�3�!�:ւ/�đ�U����кq�-�e���V��<ӽ�0["� n���xϼ�ڄd�eS_��;��J��1#����4 D��w�0��P�t܁G)!�����ș?Ǔ[䱭&�^O@���wƁ}E�x��x����F�#U3w�.D2��ϒ\�,m����O��pV�u�!&�M]�Z��4�}jv���s2����W�=I���~�|�RhE��5��LK��� �l^v���3ӏ�:�,&z-��MNs7���4� �X�թ��Fbrjst6P�����b�"���֝�S�D��h��I�@����+P�ߗ�