bake-gem-github 0.5.0 → 0.6.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: 4f7557669b8ec839c193fbf060816c360413ba4be3cd8e223f294acb8e29baed
4
- data.tar.gz: c4806b56446c74082b785e6f19ac8a36cfbe1cd79a07ba05eda3bcd223ceaeb8
3
+ metadata.gz: 46e9578ab60761af87b7130ba3642b00401e89febdcd7e8cced97d74252e37b6
4
+ data.tar.gz: 53997e6e60991e6e5b057cd62ee7897de5156306d4dceb3d8a6bef2a269cd349
5
5
  SHA512:
6
- metadata.gz: 522b233facca2afd17c28320ee34de988053bc33832a595c4e54ad34a308997813d54afaf43aa8a6c6b785b0a882b0f953aa5fecd7c4d004c0d4a21180d8f9b7
7
- data.tar.gz: 7d4dbe694f2209d5a369c2e8100101a824b1cc0839023dd4a58976b9022154428c6e7e87ab8185b70f17e7913746f07bf4abdeabd4145c510ade5c3b8ba44ee3
6
+ metadata.gz: bef7899e4fbd943cd791a72562739f04e8831f5c1fbc7b4760426c48bb9b99bf6fb96d931de8399423203d147b6775c65f137392b9e55ae7761fcb733aa00cb8
7
+ data.tar.gz: 28a9ccd6198bb07906119a6b89cf0fa23874756a5dcc53d335032bb94eebc5843b79b3f843a4ac5cfb6c911c7fd372a52095e9618d37b2bc5820c7ef463ff8c0
checksums.yaml.gz.sig CHANGED
Binary file
@@ -4,9 +4,29 @@
4
4
  # Copyright, 2026, by Samuel Williams.
5
5
 
6
6
  # Inspect external settings before applying the generated policy.
7
+ # @parameter publisher [Boolean] Authenticate to RubyGems and check the expected publisher registration.
8
+ # @parameter key [Symbol] An explicitly selected RubyGems API key name.
9
+ # @parameter otp [String] An optional RubyGems MFA code.
7
10
  # @returns [Hash] Desired and observed settings for review.
8
- def plan
9
- context.lookup("gem:github:doctor").call
11
+ def plan(publisher: false, key: nil, otp: nil)
12
+ result = context.lookup("gem:github:doctor").call
13
+ if publisher
14
+ require "bake/gem/github/trusted_publisher"
15
+
16
+ result[:trusted_publisher_status] = Bake::Gem::GitHub::TrustedPublisher.load(context.root, key: key, otp: otp).status
17
+ end
18
+
19
+ return result
20
+ end
21
+
22
+ # Register the configured release workflow as a trusted publisher for an existing gem on RubyGems.org.
23
+ # @parameter key [Symbol] An explicitly selected RubyGems API key name.
24
+ # @parameter otp [String] An optional RubyGems MFA code.
25
+ # @returns [Hash] Whether the publisher was created and its API record.
26
+ def publisher(key: nil, otp: nil)
27
+ require "bake/gem/github/trusted_publisher"
28
+
29
+ Bake::Gem::GitHub::TrustedPublisher.load(context.root, key: key, otp: otp).register
10
30
  end
11
31
 
12
32
  # Apply the four managed rulesets and configured environment reviewers using the current gh administrator credentials.
@@ -48,9 +48,25 @@ The rules require two approvals by default, allow explicit administrator bypass,
48
48
 
49
49
  ## Configure publishing credentials
50
50
 
51
- Create a `rubygems` GitHub environment restricted to the default branch. On RubyGems, an owner must configure a Trusted Publisher with the values printed by `gem:github:setup:plan`: the owner/repository, workflow filename **`release-publish.yaml`**, and environment **`rubygems`**. See [RubyGems Trusted Publishing](https://guides.rubygems.org/trusted-publishing/) for the account setup.
51
+ Create a `rubygems` GitHub environment restricted to the default branch. For an existing gem on RubyGems.org, run this task from the repository root as a gem owner:
52
52
 
53
- Ownership, MFA, and signing bootstrap are manual setup steps. The plan reports expected RubyGems values; it does not verify ownership or publisher trust. Trusted Publishing supplies the publishing credential for each run, so a long-lived RubyGems API key is unnecessary.
53
+ ``` bash
54
+ bundle exec bake gem:github:setup:publisher
55
+ ```
56
+
57
+ The task reads the gem name from its gemspec and the owner/repository and environment from `config/release.yaml`. It registers the **`release-publish.yaml`** workflow with the configured environment (**`rubygems`** by default). An exact existing match is reused, so reruns do not create duplicate registrations. Other publishers are preserved; the task does not remove or replace them.
58
+
59
+ Without an explicitly supplied API key, the task prompts for RubyGems credentials and any required MFA verification. It requests a key with only the `configure_trusted_publishers` scope, expiring after 15 minutes, and keeps it in memory. It does not read or overwrite your default saved push key. To use an existing key with this scope, supply `GEM_HOST_API_KEY` or select a named RubyGems credential with `key=publisher`. Registration and the optional plan check also accept `otp=123456` or `GEM_HOST_OTP_CODE` when needed.
60
+
61
+ To check registration without creating a publisher:
62
+
63
+ ``` bash
64
+ bundle exec bake gem:github:setup:plan publisher=true
65
+ ```
66
+
67
+ This adds `trusted_publisher_status` to the plan, including `configured` and the matching registration. The default plan only reports expected RubyGems values and does not authenticate to RubyGems. Authentication, ownership, and API failures stop the task rather than being reported as a missing registration.
68
+
69
+ For a gem that has never been published, configure a pending publisher manually. Account ownership, MFA enrollment, the GitHub environment, and signing bootstrap remain manual setup steps. See [RubyGems Trusted Publishing](https://guides.rubygems.org/trusted-publishing/) for account setup. Trusted Publishing supplies the publishing credential for each release, so a long-lived RubyGems API key is unnecessary.
54
70
 
55
71
  When `release.cert` exists, setup enables certificate signing. Commit the public certificate and install its matching private key as `GEM_SIGNING_KEY`, either in the `rubygems` environment or as an organization secret available to the repository. The publisher checks certificate validity, key matching, and package signatures. Use `signing=false` during setup to disable certificate signing.
56
72
 
@@ -181,12 +181,18 @@ module Bake
181
181
  existing_rules: api("rulesets?per_page=100"),
182
182
  environments: api("environments"),
183
183
  environment_changes: environment_changes,
184
- trusted_publisher: {
185
- repository_owner: @repository.split("/").first,
186
- repository_name: @repository.split("/").last,
187
- workflow_filename: "release-publish.yaml",
188
- environment: @config.fetch("environment"),
189
- }
184
+ trusted_publisher: trusted_publisher,
185
+ }
186
+ end
187
+
188
+ # Describe the RubyGems publisher for the reviewed release workflow.
189
+ # @returns [Hash] Repository, workflow filename, and environment settings.
190
+ def trusted_publisher
191
+ {
192
+ repository_owner: @repository.split("/").first,
193
+ repository_name: @repository.split("/").last,
194
+ workflow_filename: "release-publish.yaml",
195
+ environment: @config.fetch("environment"),
190
196
  }
191
197
  end
192
198
 
@@ -0,0 +1,124 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2026, by Samuel Williams.
5
+
6
+ require "json"
7
+ require "rubygems/gemcutter_utilities"
8
+ require "rubygems/user_interaction"
9
+ require "uri"
10
+ require_relative "project"
11
+
12
+ module Bake
13
+ module Gem
14
+ module GitHub
15
+ # Registers the reviewed release workflow with RubyGems.org using its owner API.
16
+ class TrustedPublisher
17
+ include ::Gem::UserInteraction
18
+ include ::Gem::GemcutterUtilities
19
+
20
+ # The RubyGems API scope for managing trusted publishers.
21
+ SCOPE = :configure_trusted_publishers
22
+
23
+ # The provider type accepted by the RubyGems trusted publisher API.
24
+ TYPE = "OIDC::TrustedPublisher::GitHubAction"
25
+
26
+ # Load the gem identity and reviewed publisher settings from the current project.
27
+ # @parameter root [String] The repository root, which must also be the working directory.
28
+ # @parameter options [Hash] Authentication options passed to {initialize}.
29
+ # @returns [TrustedPublisher] The configured RubyGems client.
30
+ def self.load(root, **options)
31
+ project = Project.new(root)
32
+ name = Helper.new(root).gemspec.name
33
+
34
+ new(name, project.trusted_publisher, **options)
35
+ end
36
+
37
+ # Configure an existing gem's expected GitHub Actions publisher.
38
+ # @parameter name [String] The gem name.
39
+ # @parameter settings [Hash] Repository owner/name, workflow filename, and environment.
40
+ # @parameter key [Symbol | Nil] An explicitly selected key in RubyGems credentials.
41
+ # @parameter otp [String | Nil] An optional MFA code; RubyGems can prompt when required.
42
+ def initialize(name, settings, key: nil, otp: nil)
43
+ @path = "api/v1/gems/#{URI.encode_www_form_component(name)}/trusted_publishers"
44
+ @settings = settings.transform_keys(&:to_s)
45
+ @options = {key: key, otp: otp}
46
+ @host = "https://rubygems.org"
47
+ @api_key = nil
48
+ end
49
+
50
+ # @attribute [Hash] Options used by RubyGems authentication and MFA support.
51
+ attr_reader :options
52
+
53
+ # Read the matching publisher without changing publisher configuration.
54
+ # @returns [Hash] Whether the publisher is configured and its existing API record, if any.
55
+ # @raises [RuntimeError] If RubyGems cannot list the gem's publishers.
56
+ def status
57
+ sign_in(scope: SCOPE) unless api_key
58
+ publishers = request(:get, expected: "200")
59
+ raise "Invalid RubyGems trusted publishers response." unless publishers.is_a?(Array)
60
+ publisher = publishers.find{|record| matching?(record)}
61
+
62
+ return {configured: !publisher.nil?, publisher: publisher}
63
+ end
64
+
65
+ # Register a missing publisher, preserving all existing publisher registrations.
66
+ # @returns [Hash] Whether registration was created and the matching API record.
67
+ # @raises [RuntimeError] If RubyGems rejects registration or returns different settings.
68
+ def register
69
+ current = status
70
+ return {created: false, publisher: current.fetch(:publisher)} if current.fetch(:configured)
71
+
72
+ publisher = request(:post, expected: "201", payload: {trusted_publisher_type: TYPE, trusted_publisher: @settings})
73
+ raise "RubyGems returned unexpected trusted publisher settings." unless matching?(publisher)
74
+
75
+ return {created: true, publisher: publisher}
76
+ end
77
+
78
+ # Use the session key or an explicitly supplied credential, leaving the default push key alone.
79
+ # @returns [String | Nil] The API key used for this session.
80
+ def api_key
81
+ @api_key || ENV["GEM_HOST_API_KEY"] || (options[:key] && verify_api_key(options[:key]))
82
+ end
83
+
84
+ # Keep newly issued credentials in memory rather than changing the user's credentials file.
85
+ # @parameter host [String] The authenticated RubyGems host.
86
+ # @parameter key [String] The newly issued API key.
87
+ def set_api_key(host, key)
88
+ @api_key = key
89
+ end
90
+
91
+ private
92
+
93
+ def get_mfa_params(profile)
94
+ super.merge(expires_at: (Time.now.utc + 900).strftime("%Y-%m-%d %H:%M:%S UTC"))
95
+ end
96
+
97
+ def matching?(record)
98
+ return false unless record.fetch("trusted_publisher_type") == TYPE
99
+ settings = record.fetch("trusted_publisher")
100
+
101
+ # Generated publishing workflows run in the configured repository, not a reusable workflow repository:
102
+ @settings.all?{|key, value| settings[key] == value} &&
103
+ settings["workflow_repository_owner"].nil? && settings["workflow_repository_name"].nil?
104
+ end
105
+
106
+ def request(method, expected:, payload: nil)
107
+ response = rubygems_api_request(method, @path, scope: SCOPE) do |request|
108
+ request["Authorization"] = api_key
109
+ request["Accept"] = "application/json"
110
+ if payload
111
+ request["Content-Type"] = "application/json"
112
+ request.body = JSON.generate(payload)
113
+ end
114
+ end
115
+ unless response.code == expected
116
+ raise "RubyGems trusted publisher #{method.to_s.upcase} failed (HTTP #{response.code}): #{clean_text(response.body)}"
117
+ end
118
+
119
+ JSON.parse(response.body)
120
+ end
121
+ end
122
+ end
123
+ end
124
+ end
@@ -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.5.0"
12
+ VERSION = "0.6.0"
13
13
  end
14
14
  end
15
15
  end
data/readme.md CHANGED
@@ -24,6 +24,11 @@ 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.6.0
28
+
29
+ - Register RubyGems trusted publishers from the gemspec and release configuration with `gem:github:setup:publisher`, reusing existing registrations and RubyGems authentication/MFA support.
30
+ - Check publisher registration explicitly with `gem:github:setup:plan publisher=true`.
31
+
27
32
  ### v0.5.0
28
33
 
29
34
  - 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.
data/releases.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # Releases
2
2
 
3
+ ## v0.6.0
4
+
5
+ - Register RubyGems trusted publishers from the gemspec and release configuration with `gem:github:setup:publisher`, reusing existing registrations and RubyGems authentication/MFA support.
6
+ - Check publisher registration explicitly with `gem:github:setup:plan publisher=true`.
7
+
3
8
  ## v0.5.0
4
9
 
5
10
  - 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.
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.5.0
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
@@ -107,6 +107,7 @@ files:
107
107
  - lib/bake/gem/github/publisher.rb
108
108
  - lib/bake/gem/github/registry.rb
109
109
  - lib/bake/gem/github/setup.rb
110
+ - lib/bake/gem/github/trusted_publisher.rb
110
111
  - lib/bake/gem/github/version.rb
111
112
  - license.md
112
113
  - readme.md
metadata.gz.sig CHANGED
Binary file