bake-gem-github 0.1.0 → 0.3.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 +4 -4
- checksums.yaml.gz.sig +0 -0
- data/bake/gem/github/release.rb +32 -11
- data/bake/gem/github/setup.rb +8 -3
- data/bake/gem/github.rb +23 -9
- data/context/getting-started.md +70 -69
- data/context/index.yaml +20 -4
- data/context/preparing-releases.md +40 -0
- data/context/recovering-releases.md +27 -0
- data/context/verifying-releases.md +43 -0
- data/lib/bake/gem/github/backup.rb +51 -0
- data/lib/bake/gem/github/project.rb +205 -22
- data/lib/bake/gem/github/publisher.rb +240 -120
- data/lib/bake/gem/github/registry.rb +104 -0
- data/lib/bake/gem/github/setup.rb +96 -13
- data/lib/bake/gem/github/version.rb +1 -1
- data/readme.md +84 -6
- data/releases.md +11 -0
- data/templates/release-prepare.yaml.erb +6 -1
- data.tar.gz.sig +0 -0
- metadata +11 -4
- metadata.gz.sig +0 -0
- data/agents.md +0 -437
- data/templates/releasing.md +0 -17
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: da7e92debf8a08450ce8af6da25ed171d4961726d5949b9a0c737f3849c613eb
|
|
4
|
+
data.tar.gz: 85459639500f5e4af61e4d305fe0442ddff853bb8530952385e89db39db6e69c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3919da7f3c6101666341e4d9af3b4176e400a2684b3f7cc996f8c04450b1398701512a5e5bbc9c00155d149178c1d17fc056353187a99789be8a4ab9c9a50ebb
|
|
7
|
+
data.tar.gz: 22dbd706b01561188cb1ccf8aece57b417b6e8a5edef01c22efb0ee883c41048602a356a5b0abc91cac81b24d600bd9d6e37c8d47fe1dd13a71865336cdc94e5
|
checksums.yaml.gz.sig
CHANGED
|
Binary file
|
data/bake/gem/github/release.rb
CHANGED
|
@@ -3,49 +3,70 @@
|
|
|
3
3
|
# Released under the MIT License.
|
|
4
4
|
# Copyright, 2026, by Samuel Williams.
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
require "bake/gem/github/project"
|
|
7
7
|
|
|
8
8
|
# Prepare a patch release and open its PR.
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
# @parameter refresh [Boolean] Preserve and regenerate an existing release branch.
|
|
10
|
+
# @returns [String] The release PR URL.
|
|
11
|
+
def patch(refresh: false)
|
|
12
|
+
Bake::Gem::GitHub::Project.new(context.root).prepare(context, "patch", refresh: refresh)
|
|
11
13
|
end
|
|
12
14
|
|
|
13
15
|
# Prepare a minor release and open its PR.
|
|
14
|
-
|
|
15
|
-
|
|
16
|
+
# @parameter refresh [Boolean] Preserve and regenerate an existing release branch.
|
|
17
|
+
# @returns [String] The release PR URL.
|
|
18
|
+
def minor(refresh: false)
|
|
19
|
+
Bake::Gem::GitHub::Project.new(context.root).prepare(context, "minor", refresh: refresh)
|
|
16
20
|
end
|
|
17
21
|
|
|
18
22
|
# Prepare a major release and open its PR.
|
|
19
|
-
|
|
20
|
-
|
|
23
|
+
# @parameter refresh [Boolean] Preserve and regenerate an existing release branch.
|
|
24
|
+
# @returns [String] The release PR URL.
|
|
25
|
+
def major(refresh: false)
|
|
26
|
+
Bake::Gem::GitHub::Project.new(context.root).prepare(context, "major", refresh: refresh)
|
|
21
27
|
end
|
|
22
28
|
|
|
23
29
|
# Resolve and validate a merged PR, emitting a commit output for the publishing job.
|
|
30
|
+
# @parameter number [String] The merged PR number; defaults to `RELEASE_PR`.
|
|
31
|
+
# @returns [Hash | Nil] Release metadata, or nil for an ordinary PR.
|
|
24
32
|
def resolve(number: ENV.fetch("RELEASE_PR"))
|
|
25
33
|
result = Bake::Gem::GitHub::Project.new(context.root).inspect_release(number)
|
|
34
|
+
|
|
26
35
|
if path = ENV["GITHUB_OUTPUT"]
|
|
27
36
|
File.open(path, "a") do |file|
|
|
28
37
|
file.puts "release=#{!result.nil?}"
|
|
29
38
|
file.puts "commit=#{result.fetch(:commit)}" if result
|
|
30
39
|
end
|
|
31
40
|
end
|
|
32
|
-
|
|
41
|
+
|
|
42
|
+
return result
|
|
33
43
|
end
|
|
34
44
|
|
|
35
45
|
# Build or restore the exact artifact for a merged release PR.
|
|
46
|
+
# @parameter number [String] The merged release PR number; defaults to `RELEASE_PR`.
|
|
47
|
+
# @returns [Hash] The built or restored release receipt.
|
|
36
48
|
def build(number: ENV.fetch("RELEASE_PR"))
|
|
37
|
-
|
|
49
|
+
require "bake/gem/github/publisher"
|
|
50
|
+
|
|
51
|
+
return Bake::Gem::GitHub::Publisher.new(context.root).build(number)
|
|
38
52
|
end
|
|
39
53
|
|
|
40
54
|
# Verify, upload and finalize a merged release using its retained artifact.
|
|
55
|
+
# @parameter number [String] The merged release PR number; defaults to `RELEASE_PR`.
|
|
56
|
+
# @returns [Hash] The published release receipt.
|
|
41
57
|
def publish(number: ENV.fetch("RELEASE_PR"))
|
|
42
|
-
|
|
58
|
+
require "bake/gem/github/publisher"
|
|
59
|
+
|
|
60
|
+
return Bake::Gem::GitHub::Publisher.new(context.root).publish(number)
|
|
43
61
|
end
|
|
44
62
|
|
|
45
63
|
# Rerun the original publishing workflow, preserving event identity and artifact bytes.
|
|
64
|
+
# @parameter run [String] The original release-publish workflow run ID.
|
|
65
|
+
# @returns [Boolean] True when GitHub accepts the rerun request.
|
|
46
66
|
def resume(run:)
|
|
47
67
|
project = Bake::Gem::GitHub::Project.new(context.root)
|
|
48
68
|
details = project.api("actions/runs/#{Integer(run)}")
|
|
49
69
|
raise "Expected a release-publish workflow run." unless details.fetch("path") == ".github/workflows/release-publish.yaml"
|
|
50
|
-
|
|
70
|
+
|
|
71
|
+
return project.system("gh", "run", "rerun", run.to_s, "--repo", project.config.fetch("repository"), chdir: context.root)
|
|
51
72
|
end
|
data/bake/gem/github/setup.rb
CHANGED
|
@@ -4,18 +4,23 @@
|
|
|
4
4
|
# Copyright, 2026, by Samuel Williams.
|
|
5
5
|
|
|
6
6
|
# Inspect external settings before applying the generated policy.
|
|
7
|
+
# @returns [Hash] Desired and observed settings for review.
|
|
7
8
|
def plan
|
|
8
9
|
context.lookup("gem:github:doctor").call
|
|
9
10
|
end
|
|
10
11
|
|
|
11
|
-
# Apply the four managed rulesets using the current gh administrator credentials.
|
|
12
|
+
# Apply the four managed rulesets and configured environment reviewers using the current gh administrator credentials.
|
|
13
|
+
# @returns [Hash] The managed ruleset payloads after successful application.
|
|
12
14
|
def apply
|
|
13
|
-
|
|
15
|
+
require "bake/gem/github/project"
|
|
16
|
+
|
|
14
17
|
Bake::Gem::GitHub::Project.new(context.root).apply
|
|
15
18
|
end
|
|
16
19
|
|
|
17
20
|
# Update generated files in the working tree using config/release.yaml and the installed templates.
|
|
21
|
+
# @returns [Array(String)] Changed paths relative to the repository root.
|
|
18
22
|
def update
|
|
19
|
-
|
|
23
|
+
require "bake/gem/github/setup"
|
|
24
|
+
|
|
20
25
|
Bake::Gem::GitHub::Setup.new(context.root).update
|
|
21
26
|
end
|
data/bake/gem/github.rb
CHANGED
|
@@ -3,29 +3,43 @@
|
|
|
3
3
|
# Released under the MIT License.
|
|
4
4
|
# Copyright, 2026, by Samuel Williams.
|
|
5
5
|
|
|
6
|
-
# Generate local release workflows, policy, and
|
|
6
|
+
# Generate local release workflows, policy payloads, and configuration for review.
|
|
7
7
|
# @parameter checks [Array(String)] Required CI check names, including matrix entries.
|
|
8
|
-
# @parameter repository [String] Canonical owner/repository.
|
|
9
|
-
# @parameter branch [String] Default branch name.
|
|
8
|
+
# @parameter repository [String] Canonical owner/repository; discovered through GitHub when omitted.
|
|
9
|
+
# @parameter branch [String] Default branch name; discovered through GitHub when omitted.
|
|
10
10
|
# @parameter approvals [Integer] Number of approving reviews.
|
|
11
|
-
# @parameter
|
|
11
|
+
# @parameter reviewers [Array(String)] Publishing environment reviewers: user logins or organization/team names. Omit to leave environment settings unmanaged.
|
|
12
|
+
# @parameter signing [Boolean] Require certificate signing; when omitted, enable it if `release.cert` exists.
|
|
12
13
|
# @parameter ruby [String] Ruby version for release workflows.
|
|
13
|
-
|
|
14
|
-
|
|
14
|
+
# @returns [Array(String)] Generated paths relative to the repository root.
|
|
15
|
+
def setup(checks:, repository: nil, branch: nil, approvals: 2, reviewers: nil, signing: nil, ruby: "3.4")
|
|
16
|
+
require "bake/gem/github/setup"
|
|
15
17
|
require "bake/gem/shell"
|
|
18
|
+
|
|
16
19
|
helper = Object.new.extend(Bake::Gem::Shell)
|
|
17
20
|
remote = if repository && branch
|
|
18
21
|
{}
|
|
19
22
|
else
|
|
20
23
|
JSON.parse(helper.readlines("gh", "repo", "view", "--json", "nameWithOwner,defaultBranchRef", chdir: context.root).join)
|
|
21
24
|
end
|
|
22
|
-
|
|
25
|
+
|
|
26
|
+
options = {
|
|
27
|
+
repository: repository || remote.fetch("nameWithOwner"),
|
|
28
|
+
branch: branch || remote.fetch("defaultBranchRef").fetch("name"),
|
|
29
|
+
checks: checks,
|
|
30
|
+
approvals: approvals,
|
|
31
|
+
reviewers: reviewers,
|
|
32
|
+
ruby: ruby,
|
|
33
|
+
}
|
|
23
34
|
options[:signing] = signing unless signing.nil?
|
|
35
|
+
|
|
24
36
|
Bake::Gem::GitHub::Setup.new(context.root).generate(**options)
|
|
25
37
|
end
|
|
26
38
|
|
|
27
|
-
# Show the desired rules, existing
|
|
39
|
+
# Show the desired rules and environment reviewers, existing settings, and RubyGems bootstrap values.
|
|
40
|
+
# @returns [Hash] Desired and observed settings; RubyGems values describe the expected configuration.
|
|
28
41
|
def doctor
|
|
29
|
-
|
|
42
|
+
require "bake/gem/github/project"
|
|
43
|
+
|
|
30
44
|
Bake::Gem::GitHub::Project.new(context.root).doctor
|
|
31
45
|
end
|
data/context/getting-started.md
CHANGED
|
@@ -1,121 +1,122 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Getting Started
|
|
2
2
|
|
|
3
|
-
This guide explains how to
|
|
3
|
+
This guide explains how to configure reviewed Ruby gem releases and prepare the first release PR with `bake-gem-github`.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## How releases work
|
|
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; after merge, GitHub Actions builds the exact merged commit and publishes its verified artifact to RubyGems.
|
|
8
8
|
|
|
9
|
-
|
|
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.
|
|
10
10
|
|
|
11
|
-
##
|
|
11
|
+
## Installation
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
Add these dependencies to the maintenance group in `gems.rb`:
|
|
14
14
|
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
15
|
+
``` ruby
|
|
16
|
+
group :maintenance, optional: true do
|
|
17
|
+
gem "bake-gem-github"
|
|
18
|
+
gem "agent-context"
|
|
19
|
+
end
|
|
19
20
|
```
|
|
20
21
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
To adopt template fixes after upgrading the gem, start from a clean working tree, edit `config/release.yaml` as needed, and regenerate:
|
|
22
|
+
Install that group and its guidance:
|
|
24
23
|
|
|
25
24
|
``` bash
|
|
26
|
-
bundle
|
|
27
|
-
|
|
25
|
+
bundle config set --local with maintenance
|
|
26
|
+
bundle install
|
|
27
|
+
bundle exec bake agent:context:install
|
|
28
28
|
```
|
|
29
29
|
|
|
30
|
-
The
|
|
30
|
+
The companion requires `bake-gem` 0.15 or later. Its generated release workflows install maintenance dependencies with `BUNDLE_WITH=maintenance`.
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
Use a version constant in `lib/.../version.rb` and repeatable `after_gem_release_version_increment` hooks. Validation invokes these hooks from a clean base. Generation must not depend on changing network responses or the current time; review dependency updates that could alter generated content.
|
|
33
33
|
|
|
34
|
-
|
|
34
|
+
## Generate repository configuration
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
Run setup from the repository root with the actual required CI job names, including supported matrix entries:
|
|
37
37
|
|
|
38
38
|
``` bash
|
|
39
|
-
bundle exec bake gem:github:setup
|
|
39
|
+
bundle exec bake gem:github:setup 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"
|
|
40
|
+
bundle exec bake gem:github:setup:plan
|
|
40
41
|
```
|
|
41
42
|
|
|
42
|
-
This
|
|
43
|
+
This example follows the standard `bake modernize` test, RuboCop, and coverage job names. Select the jobs produced by your repository. Experimental Ruby jobs are not required. Setup adds `Release validation` automatically.
|
|
43
44
|
|
|
44
|
-
|
|
45
|
+
Setup discovers the canonical GitHub repository and default branch through `gh`. It generates three release workflows, `config/release.yaml`, and four native ruleset payloads. Identical reruns do nothing; conflicting existing files stop generation before any file is written. Review and commit the files in a setup PR, and remove conflicting publishing workflows.
|
|
45
46
|
|
|
46
|
-
|
|
47
|
+
The rules require two approvals by default, allow explicit administrator bypass, dismiss stale reviews, require approval of the last push, and require up-to-date CI. They protect default-branch history and release tags against deletion or replacement. These branch rules apply to **all PRs** into the default branch. An ordinary administrator approval counts as one review; bypass is a separate action.
|
|
47
48
|
|
|
48
|
-
|
|
49
|
+
## Configure publishing credentials
|
|
49
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
52
|
|
|
52
|
-
|
|
53
|
-
# Local branch and commit only:
|
|
54
|
-
bundle exec bake gem:release:branch:patch
|
|
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.
|
|
55
54
|
|
|
56
|
-
|
|
57
|
-
|
|
55
|
+
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
|
+
|
|
57
|
+
Ensure another maintainer can administer the repository and recover its RubyGems account and signing key.
|
|
58
58
|
|
|
59
|
-
|
|
60
|
-
|
|
59
|
+
## Authorize publishing
|
|
60
|
+
|
|
61
|
+
PR reviews approve the source changes. To require a release manager to authorize publication after merge, configure required reviewers on the `rubygems` environment. The publishing job waits for this separate approval before running.
|
|
62
|
+
|
|
63
|
+
Pass `reviewers=your-org/managers` to `gem:github:setup`, or add the reviewers to an existing `config/release.yaml`:
|
|
64
|
+
|
|
65
|
+
``` yaml
|
|
66
|
+
reviewers:
|
|
67
|
+
- your-org/managers
|
|
61
68
|
```
|
|
62
69
|
|
|
63
|
-
Replace `
|
|
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.
|
|
64
71
|
|
|
65
|
-
|
|
72
|
+
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.
|
|
66
73
|
|
|
67
|
-
|
|
74
|
+
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.
|
|
68
75
|
|
|
69
|
-
|
|
76
|
+
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.
|
|
70
77
|
|
|
71
|
-
|
|
78
|
+
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.
|
|
72
79
|
|
|
73
|
-
|
|
74
|
-
- 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.
|
|
80
|
+
## Enable the policy
|
|
75
81
|
|
|
76
|
-
|
|
82
|
+
Merge the setup PR and confirm every selected CI job, including **Release validation**, runs. Review the plan again, then apply its rules using an administrator's `gh` login:
|
|
77
83
|
|
|
78
|
-
|
|
84
|
+
``` bash
|
|
85
|
+
bundle exec bake gem:github:setup:plan
|
|
86
|
+
bundle exec bake gem:github:setup:apply
|
|
87
|
+
```
|
|
88
|
+
|
|
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.
|
|
90
|
+
|
|
91
|
+
## Prepare the first release PR
|
|
92
|
+
|
|
93
|
+
From an up-to-date default branch:
|
|
79
94
|
|
|
80
95
|
``` bash
|
|
81
|
-
|
|
82
|
-
for file in example-1.2.3.gem release.json; do
|
|
83
|
-
gh attestation verify "$file" \
|
|
84
|
-
--repo OWNER/REPOSITORY --bundle provenance.sigstore.json \
|
|
85
|
-
--cert-identity https://github.com/OWNER/REPOSITORY/.github/workflows/release-publish.yaml@refs/heads/main \
|
|
86
|
-
--source-ref refs/heads/main --deny-self-hosted-runners
|
|
87
|
-
done
|
|
88
|
-
|
|
89
|
-
jq -e --arg commit MERGED_SHA \
|
|
90
|
-
--arg digest "$(shasum -a 256 example-1.2.3.gem | cut -d ' ' -f1)" \
|
|
91
|
-
'.commit == $commit and .sha256 == $digest' release.json
|
|
92
|
-
|
|
93
|
-
gem exec sigstore-cli:0.2.3 verify example-1.2.3.gem \
|
|
94
|
-
--bundle example-1.2.3.gem.sigstore.json \
|
|
95
|
-
--certificate-identity https://github.com/OWNER/REPOSITORY/.github/workflows/release-publish.yaml@refs/heads/main \
|
|
96
|
-
--certificate-oidc-issuer https://token.actions.githubusercontent.com
|
|
96
|
+
bundle exec bake gem:github:release:patch
|
|
97
97
|
```
|
|
98
98
|
|
|
99
|
-
|
|
99
|
+
The task prepares, validates, pushes, and opens the release PR. Review its version and release notes, wait for CI, and merge under the repository's approval policy. If environment reviewers are configured, a release manager then approves the publishing job in GitHub Actions. The publish workflow builds the merged release, verifies and preserves the artifact, publishes to RubyGems, and finalizes the version tag and GitHub release.
|
|
100
100
|
|
|
101
|
-
|
|
101
|
+
See [Preparing Releases](../preparing-releases/index) for remote requests and stale-content refresh, [Verifying Releases](../verifying-releases/index) for artifact checks, and [Recovering Releases](../recovering-releases/index) when a workflow stops partway through.
|
|
102
102
|
|
|
103
|
-
##
|
|
103
|
+
## Update generated files
|
|
104
104
|
|
|
105
|
-
|
|
105
|
+
After upgrading the gem, start from a clean working tree and regenerate using your existing configuration:
|
|
106
106
|
|
|
107
107
|
``` bash
|
|
108
|
-
bundle exec bake gem:github:
|
|
108
|
+
bundle exec bake gem:github:setup:update
|
|
109
|
+
git diff
|
|
109
110
|
```
|
|
110
111
|
|
|
111
|
-
|
|
112
|
+
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.
|
|
112
113
|
|
|
113
|
-
|
|
114
|
+
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.
|
|
114
115
|
|
|
115
|
-
|
|
116
|
+
## Current scope
|
|
116
117
|
|
|
117
|
-
|
|
118
|
+
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.
|
|
118
119
|
|
|
119
|
-
|
|
120
|
+
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.
|
|
120
121
|
|
|
121
|
-
Edit
|
|
122
|
+
Edit source guides under `guides/` and regenerate the distributed guidance with `bundle exec bake utopia:project:agent:context:update`. Consumers install it through `agent-context`.
|
data/context/index.yaml
CHANGED
|
@@ -2,9 +2,25 @@
|
|
|
2
2
|
# Do not edit then files in this directory directly, instead edit the guides and then run `bake utopia:project:agent:context:update`.
|
|
3
3
|
---
|
|
4
4
|
description: Reviewable GitHub releases for Ruby gems.
|
|
5
|
-
metadata:
|
|
5
|
+
metadata:
|
|
6
|
+
documentation_uri: https://socketry.github.io/bake-gem-github/
|
|
7
|
+
bug_tracker_uri: https://github.com/socketry/bake-gem-github/issues
|
|
8
|
+
changelog_uri: https://github.com/socketry/bake-gem-github/blob/main/releases.md
|
|
9
|
+
source_code_uri: https://github.com/socketry/bake-gem-github.git
|
|
6
10
|
files:
|
|
7
11
|
- path: getting-started.md
|
|
8
|
-
title:
|
|
9
|
-
description: This guide explains how to
|
|
10
|
-
|
|
12
|
+
title: Getting Started
|
|
13
|
+
description: This guide explains how to configure reviewed Ruby gem releases and
|
|
14
|
+
prepare the first release PR with `bake-gem-github`.
|
|
15
|
+
- path: preparing-releases.md
|
|
16
|
+
title: Preparing Releases
|
|
17
|
+
description: This guide explains how to request a release PR, resume interrupted
|
|
18
|
+
preparation, and refresh generated content when the default branch changes.
|
|
19
|
+
- path: verifying-releases.md
|
|
20
|
+
title: Verifying Releases
|
|
21
|
+
description: This guide explains how publishing binds a gem to its reviewed source
|
|
22
|
+
and how to verify the downloaded artifact and attestations.
|
|
23
|
+
- path: recovering-releases.md
|
|
24
|
+
title: Recovering Releases
|
|
25
|
+
description: This guide explains how to resume an interrupted publishing workflow
|
|
26
|
+
using the original gem and its verification evidence.
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Preparing Releases
|
|
2
|
+
|
|
3
|
+
This guide explains how to request a release PR, resume interrupted preparation, and refresh generated content when the default branch changes.
|
|
4
|
+
|
|
5
|
+
Complete [Getting Started](../getting-started/index) first. Release preparation uses {ruby Bake::Gem::GitHub::Project#prepare} to coordinate the core Bake tasks and GitHub operations. Run local tasks from the repository root so gemspec paths resolve correctly.
|
|
6
|
+
|
|
7
|
+
## Request a release
|
|
8
|
+
|
|
9
|
+
``` bash
|
|
10
|
+
# Local branch and commit only:
|
|
11
|
+
bundle exec bake gem:release:branch:patch
|
|
12
|
+
|
|
13
|
+
# From the current default branch: prepare, validate, push and open PR:
|
|
14
|
+
bundle exec bake gem:github:release:patch
|
|
15
|
+
|
|
16
|
+
# Remote request (also available in the Actions UI):
|
|
17
|
+
gh workflow run release-prepare.yaml -f bump=patch
|
|
18
|
+
```
|
|
19
|
+
|
|
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
|
+
|
|
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.
|
|
23
|
+
|
|
24
|
+
## Resume interrupted preparation
|
|
25
|
+
|
|
26
|
+
If preparation stops after creating or pushing the release branch, return to the current default branch and repeat the same command. The existing branch is validated and reused, so retries do not create a second version bump or PR. Resolve any uncommitted changes before switching branches.
|
|
27
|
+
|
|
28
|
+
## Refresh stale content
|
|
29
|
+
|
|
30
|
+
When new changes alter the generated release notes or other artifacts, rebasing the branch alone does not regenerate them. Validation reports the stale content. Explicitly refresh the same release:
|
|
31
|
+
|
|
32
|
+
``` bash
|
|
33
|
+
git switch main
|
|
34
|
+
git pull --ff-only
|
|
35
|
+
bundle exec bake gem:github:release:patch refresh=true
|
|
36
|
+
# Or dispatch remotely:
|
|
37
|
+
gh workflow run release-prepare.yaml -f bump=patch -f refresh=true
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
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.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Recovering Releases
|
|
2
|
+
|
|
3
|
+
This guide explains how to resume an interrupted publishing workflow using the original gem and its verification evidence.
|
|
4
|
+
|
|
5
|
+
Use this when a workflow fails during preservation, upload, registry propagation, or tag/release finalization. For failures while creating the PR, see [Preparing Releases](../preparing-releases/index). Publishing recovery retains the original source and artifact bytes.
|
|
6
|
+
|
|
7
|
+
## Rerun the original workflow
|
|
8
|
+
|
|
9
|
+
Use **Re-run all jobs** on the original publishing run, or:
|
|
10
|
+
|
|
11
|
+
``` bash
|
|
12
|
+
bundle exec bake gem:github:release:resume run=RUN_ID
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Rerunning keeps the original event identity. GitHub may request publishing environment approval again. A retained artifact is downloaded and its source identity/digest checked. A matching registry version resumes tag/release finalization; different bytes or a conflicting tag stop. There is no automatic yank, retag, or rebuild of an already-published version. Registry propagation is retried every ten seconds for up to one minute; a digest or attestation mismatch fails immediately.
|
|
16
|
+
|
|
17
|
+
## Restore retained artifacts
|
|
18
|
+
|
|
19
|
+
Before uploading to RubyGems, the publisher stores the verified gem, receipt and both attestation bundles together in `release.tar`, uploaded as one draft-release asset before their individual assets. The draft targets the merged commit. It publishes the draft after registry verification and tag creation. Actions artifacts are also retained for 90 days, but can disappear on rerun. Recovery falls back to `release.tar` in the draft or published release, checking its digest and requiring exactly the four expected regular files before restoring them. It verifies the original bytes and attestations, then resumes any missing individual asset uploads. Older releases without an archive can still restore their four individual assets. Existing assets and backups are compared with the original files and never replaced with conflicting content.
|
|
20
|
+
|
|
21
|
+
## Handle incomplete preservation
|
|
22
|
+
|
|
23
|
+
A rerun can recover an interrupted individual asset upload once `release.tar` is available, even if the Actions artifact has disappeared. An available Actions artifact can also resume an interrupted archive upload. If neither backup completed, restore the missing original files manually; the publisher stops before uploading to RubyGems. Keep the draft until finalization succeeds. A published version is never rebuilt to fill a missing backup.
|
|
24
|
+
|
|
25
|
+
## Understand workflow reruns
|
|
26
|
+
|
|
27
|
+
GitHub concurrency does not guarantee a durable FIFO queue: rerun any publishing run displaced while pending. Resume reruns all jobs, including integrity checks; it does not repeat or second-guess the native review policy or a permitted administrator bypass. Older publishing runs execute their original code; adding this recovery support to the default branch does not change an already-triggered workflow.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Verifying Releases
|
|
2
|
+
|
|
3
|
+
This guide explains how publishing binds a gem to its reviewed source and how to verify the downloaded artifact and attestations.
|
|
4
|
+
|
|
5
|
+
Use this when checking a completed release or confirming which source commit produced a package. [Getting Started](../getting-started/index) describes publisher configuration; [Recovering Releases](../recovering-releases/index) covers interrupted workflows.
|
|
6
|
+
|
|
7
|
+
## What publishing verifies
|
|
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:
|
|
10
|
+
|
|
11
|
+
- A Sigstore bundle submitted explicitly with `gem push --attestation` using RubyGems 4.0.21.
|
|
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.
|
|
13
|
+
|
|
14
|
+
The workflow retains the gem, receipt and attestations before obtaining RubyGems publishing credentials. It verifies both attestations, checks the uploaded bytes and registry bundle, then pushes the specific version tag and creates the GitHub release. Existing tags/assets are checked and never overwritten. The old `after_gem_release` GitHub hook is not called by this pipeline, so there is one owner for release creation.
|
|
15
|
+
|
|
16
|
+
The draft release description includes the exact version's notes from `releases.md` in the merged release checkout, followed by the PR URL, source commit, and gem digest. Notes are extracted using `bake-releases`; a missing or empty section leaves the metadata as the description. Retries preserve the existing release description.
|
|
17
|
+
|
|
18
|
+
## Verify downloaded artifacts
|
|
19
|
+
|
|
20
|
+
Download the gem, its `.sigstore.json` bundle, `release.json`, and `provenance.sigstore.json` from the GitHub release. Replace the example package, owner/repository, and `MERGED_SHA` below with the release being checked. Run from the directory containing those files:
|
|
21
|
+
|
|
22
|
+
``` bash
|
|
23
|
+
set -e
|
|
24
|
+
for file in example-1.2.3.gem release.json; do
|
|
25
|
+
gh attestation verify "$file" \
|
|
26
|
+
--repo OWNER/REPOSITORY --bundle provenance.sigstore.json \
|
|
27
|
+
--cert-identity https://github.com/OWNER/REPOSITORY/.github/workflows/release-publish.yaml@refs/heads/main \
|
|
28
|
+
--source-ref refs/heads/main --deny-self-hosted-runners
|
|
29
|
+
done
|
|
30
|
+
|
|
31
|
+
jq -e --arg commit MERGED_SHA \
|
|
32
|
+
--arg digest "$(shasum -a 256 example-1.2.3.gem | cut -d ' ' -f1)" \
|
|
33
|
+
'.commit == $commit and .sha256 == $digest' release.json
|
|
34
|
+
|
|
35
|
+
gem exec sigstore-cli:0.2.3 verify example-1.2.3.gem \
|
|
36
|
+
--bundle example-1.2.3.gem.sigstore.json \
|
|
37
|
+
--certificate-identity https://github.com/OWNER/REPOSITORY/.github/workflows/release-publish.yaml@refs/heads/main \
|
|
38
|
+
--certificate-oidc-issuer https://token.actions.githubusercontent.com
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Download `release.json` and `provenance.sigstore.json` alongside the gem. Verify both subjects before reading the receipt's source commit and digest. GitHub CLI's `--source-digest` checks the workflow revision, which may differ from the release commit recorded in the signed receipt. Replace `main` with the configured default branch in these commands.
|
|
42
|
+
|
|
43
|
+
Native GitHub records the merge and any bypass; the signed artifact receipt includes the PR and merging actor. This version does not export organization audit-log evidence or infer bypass reasons from review counts.
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Released under the MIT License.
|
|
4
|
+
# Copyright, 2026, by Samuel Williams.
|
|
5
|
+
|
|
6
|
+
require "rubygems/package"
|
|
7
|
+
|
|
8
|
+
module Bake
|
|
9
|
+
module Gem
|
|
10
|
+
module GitHub
|
|
11
|
+
# Stores the original release files together so a single completed upload can recover them.
|
|
12
|
+
module Backup
|
|
13
|
+
# Write the release files to a tar archive.
|
|
14
|
+
# @parameter path [String] The destination archive path.
|
|
15
|
+
# @parameter files [Array(String)] Original release files, stored under their basenames.
|
|
16
|
+
# @returns [Nil] After writing and closing the archive.
|
|
17
|
+
def self.write(path, files)
|
|
18
|
+
File.open(path, "wb") do |output|
|
|
19
|
+
::Gem::Package::TarWriter.new(output) do |archive|
|
|
20
|
+
files.each do |file|
|
|
21
|
+
archive.add_file(File.basename(file), 0644){|entry| entry.write(File.binread(file))}
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Read only the expected regular files; reject missing, duplicate, or unexpected entries before extraction.
|
|
28
|
+
# @parameter path [String] The archive to inspect without extracting filesystem paths.
|
|
29
|
+
# @parameter names [Array(String)] Exactly the permitted basenames for the gem, receipt, and two attestation files.
|
|
30
|
+
# @returns [Hash(String, String)] Binary file contents keyed by basename.
|
|
31
|
+
# @raises [RuntimeError] If entries are missing, duplicated, unexpected, or not regular files.
|
|
32
|
+
def self.read(path, names)
|
|
33
|
+
files = {}
|
|
34
|
+
|
|
35
|
+
File.open(path, "rb") do |input|
|
|
36
|
+
::Gem::Package::TarReader.new(input) do |archive|
|
|
37
|
+
archive.each do |entry|
|
|
38
|
+
name = entry.full_name
|
|
39
|
+
raise "Unexpected release backup entry: #{name}" unless entry.file? && names.include?(name) && !files.key?(name)
|
|
40
|
+
files[name] = entry.read
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
raise "Release backup is incomplete." unless files.keys.sort == names.sort
|
|
45
|
+
|
|
46
|
+
return files
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|