release_ceremony 1.0.0 → 1.0.1

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: e73ee4ce1ed15cf0e500cf1599d8cd2c288ca37cffd4ef8c5f2267dcfb9f9653
4
- data.tar.gz: 87d220e6796d3acd7455651f4340215e7b0f5e4ad6927f8343dd373b8ca84b74
3
+ metadata.gz: 15c43fac7dba19c918054427d55e8799a7c029c347d487f3ef89428195112bd3
4
+ data.tar.gz: 43ca6ea3adcb1e615d0ca25c56f4bbb97aeaff3a6582897e6031fd8eb9e478b6
5
5
  SHA512:
6
- metadata.gz: 797b5474be23312a9d7f430338a57744f525e5c2226a7122a98dedc5c48fff62af7cc4a9132e7ff3edf67ef1c4777066300181b06e46312105990a18ad0cc708
7
- data.tar.gz: d9dba2117e6ba35a3ec8060c036e58f22eee3ffed0d56492b7708b8ff50e34998566c93db23a65bf6d0cf0da146eae94fdce190bf1f35af46475fb189b459b2d
6
+ metadata.gz: 88bcf75134463e12229886c261bd79d49965d87301a6ceec800f5712ba50ce9822a4e200a70c1b94851a1ec31e6490bb41c30d369a6dbfc45d4891e07b33289b
7
+ data.tar.gz: cf437bd36a3b1e3c1f29797d0331ceadb4bafdb0648731d190efc77bb5a3df4faa4643043be337fbc3e20c047498cddd59f13260910294fc28802287dde0e3dd
data/CHANGELOG.md CHANGED
@@ -8,6 +8,16 @@ and this project adheres to
8
8
 
9
9
  ## [Unreleased]
10
10
 
11
+ ## [1.0.1] - 2026-07-25
12
+
13
+ ### Fixed
14
+
15
+ - Child commands no longer inherit the ceremony's own Bundler environment.
16
+ Running the ceremony with `bundle exec` used to leak `RUBYOPT` and the
17
+ `BUNDLE_*` variables into the verification commands, so a nested
18
+ `bundle install` crashed with a `LoadError` in the `bundle` executable.
19
+ Every command now runs in an unbundled environment when Bundler is loaded.
20
+
11
21
  ## [1.0.0] - 2026-07-25
12
22
 
13
23
  ### Added
@@ -24,5 +34,6 @@ and this project adheres to
24
34
  overrides in `.release_ceremony.yml` (release file paths, verification
25
35
  commands, workflow file, default branch, and remote).
26
36
 
27
- [Unreleased]: https://github.com/dominicsayers/release_ceremony/compare/v1.0.0...HEAD
37
+ [Unreleased]: https://github.com/dominicsayers/release_ceremony/compare/v1.0.1...HEAD
38
+ [1.0.1]: https://github.com/dominicsayers/release_ceremony/compare/v1.0.0...v1.0.1
28
39
  [1.0.0]: https://github.com/dominicsayers/release_ceremony/compare/v0.0.0...v1.0.0
data/README.md CHANGED
@@ -20,17 +20,19 @@ until you type the confirmation phrase.
20
20
  ## The ceremony
21
21
 
22
22
  ```sh
23
- git switch -c release-v1.2.3
24
- release_ceremony prepare 1.2.3
23
+ VERSION=1.2.3
24
+
25
+ git switch -c "release-v$VERSION"
26
+ release_ceremony prepare "$VERSION"
25
27
  # inspect the changes, then commit and push the branch
26
28
  # open the pull request and merge it
27
- release_ceremony publish 1.2.3
29
+ release_ceremony publish "$VERSION"
28
30
  ```
29
31
 
30
32
  Publication itself happens in CI: the pushed tag triggers your release
31
- workflow, which should publish to RubyGems (for example with
32
- [Trusted Publishing](https://guides.rubygems.org/trusted-publishing/) and the
33
- `rubygems/release-gem` action). `publish` only tags, watches, and verifies.
33
+ workflow, which should publish to RubyGems (see
34
+ [Setting up Trusted Publishing](#setting-up-trusted-publishing)). `publish`
35
+ only tags, watches, and verifies.
34
36
 
35
37
  ## Installation
36
38
 
@@ -60,6 +62,59 @@ The gem name and repository URL are read from the repository's gemspec
60
62
  (`metadata['source_code_uri']`, falling back to `homepage`). Everything else
61
63
  above is a convention that `.release_ceremony.yml` can override.
62
64
 
65
+ ## Setting up Trusted Publishing
66
+
67
+ `publish` never pushes to RubyGems itself; the tag-triggered workflow does,
68
+ ideally with [Trusted Publishing](https://guides.rubygems.org/trusted-publishing/)
69
+ and the [`rubygems/release-gem`](https://github.com/rubygems/release-gem)
70
+ action, so that no API key exists to be stored or leaked. One-time setup:
71
+
72
+ - **Existing gem**: on rubygems.org, open the gem → Trusted publishers →
73
+ Create, and enter the repository owner and name, the workflow filename
74
+ (e.g. `release.yml`), and the environment name the workflow declares
75
+ (e.g. `release`).
76
+ - **New gem**: nothing is created on RubyGems manually. Instead, register a
77
+ [pending trusted publisher](https://rubygems.org/profile/oidc/pending_trusted_publishers)
78
+ (Profile → OIDC → Pending trusted publishers) with the same fields plus the
79
+ gem's name. The first successful push from the workflow creates the gem,
80
+ converts the pending publisher into a normal one, and makes you the owner.
81
+ Pending publishers expire 12 hours after creation, so register on the day
82
+ of the first release; if it lapses, just register it again.
83
+ - **GitHub environment**: if the workflow's publish job declares
84
+ `environment: release`, create that environment in the repository settings
85
+ (Settings → Environments). Trusted Publishing only accepts tokens minted
86
+ from the registered environment, and you can attach protection rules such
87
+ as required reviewers to it.
88
+
89
+ RubyGems also ships a CLI that automates the RubyGems side:
90
+ [`gem exec configure_trusted_publisher`](https://github.com/rubygems/configure_trusted_publisher).
91
+
92
+ ## Re-running publish
93
+
94
+ `publish` converges when reality matches expectations and stops loudly when
95
+ it doesn't, so after most failures you can simply run it again:
96
+
97
+ - Everything before the confirmation prompt is read-only validation; a
98
+ failure there has changed nothing.
99
+ - Tagging is guarded: if the remote tag already exists at the merge commit,
100
+ tagging and pushing are skipped; if only a local tag exists, it is pushed
101
+ as-is. A tag that exists anywhere but points to the wrong commit aborts the
102
+ ceremony before the prompt — `publish` never moves or deletes a tag.
103
+ - Watching the workflow and checking RubyGems are read-only, so a run that
104
+ died mid-watch or hit RubyGems index lag re-attaches to the completed
105
+ workflow run and re-verifies. Re-running after a fully successful publish
106
+ succeeds again without side effects.
107
+
108
+ The exception is a failed release workflow. Re-running `publish` does not
109
+ retry it: the tag is already on the remote, so nothing re-pushes, no new
110
+ workflow is triggered, and watching the same failed run fails again. Re-run
111
+ the workflow first, then re-run `publish` to watch and verify it:
112
+
113
+ ```sh
114
+ gh run rerun <run-id> --failed
115
+ release_ceremony publish "$VERSION"
116
+ ```
117
+
63
118
  ## Configuration
64
119
 
65
120
  Create `.release_ceremony.yml` in the repository root to override any of the
@@ -16,7 +16,7 @@ module ReleaseCeremony
16
16
 
17
17
  def capture(*command)
18
18
  validate_command!(command)
19
- captured_stdout, captured_stderr, status = Open3.capture3(*command)
19
+ captured_stdout, captured_stderr, status = unbundled { Open3.capture3(*command) }
20
20
  return captured_stdout if status.success?
21
21
 
22
22
  raise_command_error(command, captured_stderr)
@@ -26,7 +26,7 @@ module ReleaseCeremony
26
26
 
27
27
  def run(*command)
28
28
  validate_command!(command)
29
- result = system(*command)
29
+ result = unbundled { system(*command) }
30
30
  return true if result
31
31
 
32
32
  result.nil? ? raise_spawn_error(command) : raise_command_error(command)
@@ -56,6 +56,20 @@ module ReleaseCeremony
56
56
 
57
57
  private
58
58
 
59
+ # The ceremony normally runs under bundle exec, but its child processes
60
+ # must not inherit that Bundler context: a nested bundle command crashes
61
+ # when RUBYOPT and the BUNDLE_* variables point at the ceremony's own
62
+ # bundle instead of the repository being released
63
+ def unbundled(&)
64
+ return yield unless bundler_loaded?
65
+
66
+ Bundler.with_unbundled_env(&)
67
+ end
68
+
69
+ def bundler_loaded?
70
+ !defined?(Bundler).nil?
71
+ end
72
+
59
73
  def validate_command!(command)
60
74
  return if command.length > 1 && command.all?(String)
61
75
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ReleaseCeremony
4
- VERSION = '1.0.0'
4
+ VERSION = '1.0.1'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: release_ceremony
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dominic Sayers