gemchain 0.2.1 → 0.2.3

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: 29cc49ef5619e2bf869d99e3785600c68fce13cac87d5f455103e82ad67964df
4
- data.tar.gz: e3ee5374417db2c3bf89028b0eb2527dd698217f850bfc9402e1c68c364aed61
3
+ metadata.gz: ab27d10beec845df41578b7b6c4d565b7bbc0f9cba1e6a54a0dea436e6e4a6b6
4
+ data.tar.gz: f174ef1730a64b7ccde749d7bd501718c9e01cbdb45811c364d7c3ee3ad69f65
5
5
  SHA512:
6
- metadata.gz: 5b37952e40c01005984baae5a1d077b62af99ab7b77f9a464578dd16a02ec33b6e4c3a371ab7bdeadd99fbbc6c1e9c590f0744d0ab80e4100aa431e5a9ab9c46
7
- data.tar.gz: 9f9e33a3c2cf12eeb19e0eb641523dfa6e46eadccef43a79352cb4446e6e8ffd77acad93fc1d9697de9c33fa6590634cbd67c20073ff67bafe8895cac62bec96
6
+ metadata.gz: 6496d418a3d2678a3c68235f2dba7b6e92e6f19e43f9b419b3a84274f2aea7d4dc630255957dbff5742fab85b15aaffc9a53851801d71104ae9efd26f9224279
7
+ data.tar.gz: aab0e316ac54d912ae20c37ed75da0520d6a8a00deb9969e553de4286c855db40fdf399968b76141b3b3e1f9ceeec65c0bfc963f4f07fba368e8c2cac496ca20
data/CHANGELOG.md CHANGED
@@ -1,3 +1,23 @@
1
+ ## [0.2.3] — 2026-08-13
2
+
3
+ ### Fixed
4
+
5
+ - **Release step is idempotent for already-published versions** — a re-run
6
+ (or a flaky RubyGems API check during mode detection) reached `:release`
7
+ with the version already published and collided on `git tag`. The release
8
+ now checks RubyGems first (with a 5s timeout) and skips build/push/tag
9
+ when the version is already published.
10
+
11
+ ## [0.2.2] — 2026-08-13
12
+
13
+ ### Fixed
14
+
15
+ - **Release skips the commit when nothing is staged** — a source gem whose
16
+ version bump was already committed (e.g. a fix shipped together with the
17
+ bump) crashed the cascade with "nothing to commit, working tree clean".
18
+ `:release` now checks for staged changes (`git diff --cached --quiet`)
19
+ and skips the commit while still building, pushing, and tagging.
20
+
1
21
  # Changelog
2
22
 
3
23
  ## [0.2.1] — 2026-08-12
@@ -109,13 +109,15 @@ module Gemchain
109
109
  lines.join("\n")
110
110
  end
111
111
 
112
- # True when +name+ is published at +version+ on RubyGems. When rubygems
113
- # is unreachable, assume not published so the full mode (bump → test →
114
- # release) is preferred — bumping an already-published version is a no-op.
112
+ # True when +name+ is published at +version+ on RubyGems. Bounded by a
113
+ # short timeout; when rubygems is unreachable, assume not published so
114
+ # the full mode (bump → test → release) is preferred — bumping an
115
+ # already-published version is a no-op, and the executor's release step
116
+ # additionally skips re-releases of published versions.
115
117
  def released_on_rubygems?(name, version)
116
118
  require "json"
117
119
  require "net/http"
118
- body = Net::HTTP.get(URI("https://rubygems.org/api/v1/gems/#{name}.json"))
120
+ body = Timeout.timeout(5) { Net::HTTP.get(URI("https://rubygems.org/api/v1/gems/#{name}.json")) }
119
121
  JSON.parse(body)["version"] == version
120
122
  rescue StandardError
121
123
  false
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "open3"
4
+ require "timeout"
4
5
 
5
6
  module Gemchain
6
7
  class ExecutionError < Error; end
@@ -23,20 +24,30 @@ module Gemchain
23
24
  detail = out.lines.first(5).join.strip if detail.empty?
24
25
  raise ExecutionError, "Command failed: #{command.join(' ')}#{detail.empty? ? '' : "\n#{detail}"}"
25
26
  end
27
+
28
+ # Runs a command and reports success without raising. Used for
29
+ # conditional checks (e.g. "are there staged changes?").
30
+ def success?(command, chdir: nil)
31
+ _out, _err, status = Open3.capture3(*command, chdir: chdir)
32
+ status.success?
33
+ end
26
34
  end
27
35
 
28
36
  BUMP_LEVELS = %i[patch minor major].freeze
29
37
 
30
38
  # @param workspace [Workspace] the gem ecosystem
31
39
  # @param steps [Array<Hash>] the plan from Cascade#update
32
- # @param runner [#run] command runner (defaults to the real one)
40
+ # @param runner [#run, #success?] command runner (defaults to the real one)
33
41
  # @param confirm [Proc] per-release confirmation, called with a message;
34
42
  # defaults to a [y/N] prompt on stdin
35
43
  # @param yes [Boolean] skip confirmations entirely
36
44
  # @param bump [Symbol] dependent version bump level (:patch, :minor, :major)
37
45
  # @param output [#puts] progress output stream
46
+ # @param released_check [Proc] (name, version) → published on RubyGems?
47
+ # Used to skip releases whose version is already published; defaults
48
+ # to a RubyGems API lookup with a short timeout.
38
49
  def initialize(workspace:, steps:, runner: nil, confirm: nil, yes: false,
39
- bump: :patch, output: $stdout)
50
+ bump: :patch, output: $stdout, released_check: nil)
40
51
  @workspace = workspace
41
52
  @steps = steps
42
53
  @runner = runner || Runner.new
@@ -44,6 +55,7 @@ module Gemchain
44
55
  @yes = yes
45
56
  @bump = bump.to_sym
46
57
  @output = output
58
+ @released_check = released_check || method(:released_on_rubygems?)
47
59
  raise ArgumentError, "bump must be one of #{BUMP_LEVELS.inspect}" unless BUMP_LEVELS.include?(@bump)
48
60
  end
49
61
 
@@ -162,6 +174,14 @@ module Gemchain
162
174
  name = step[:gem]
163
175
  version = step[:version] || bump_dependent(dir, name)
164
176
 
177
+ # Idempotency guard: a re-run (or flaky mode detection) can reach
178
+ # :release with the version already published — re-pushing/tagging
179
+ # would fail. Skip the release entirely in that case.
180
+ if @released_check.call(name, version)
181
+ @output.puts " ⏭ #{name} #{version} already on RubyGems — skipping release"
182
+ return
183
+ end
184
+
165
185
  files = release_files(dir)
166
186
  @runner.run(["git", "add", *files], chdir: dir)
167
187
 
@@ -170,7 +190,12 @@ module Gemchain
170
190
  else
171
191
  "Bump to #{version} (gemchain)"
172
192
  end
173
- @runner.run(["git", "commit", "-m", message], chdir: dir)
193
+ # Skip the commit when the release's own bump/constraint changes were
194
+ # already committed (e.g. a fix shipped together with the version
195
+ # bump) — `git commit` would fail on a clean tree.
196
+ unless @runner.success?(["git", "diff", "--cached", "--quiet"], chdir: dir)
197
+ @runner.run(["git", "commit", "-m", message], chdir: dir)
198
+ end
174
199
 
175
200
  @runner.run(["gem", "build", "#{name}.gemspec"], chdir: dir)
176
201
  @runner.run(["gem", "push", "#{name}-#{version}.gem"], chdir: dir)
@@ -193,6 +218,18 @@ module Gemchain
193
218
  status.success?
194
219
  end
195
220
 
221
+ # True when +name+ is published at +version+ on RubyGems. Bounded by a
222
+ # short timeout; on any failure assume NOT published so the release
223
+ # proceeds (a no-op re-push is safer than silently skipping a real one).
224
+ def released_on_rubygems?(name, version)
225
+ require "json"
226
+ require "net/http"
227
+ body = Timeout.timeout(5) { Net::HTTP.get(URI("https://rubygems.org/api/v1/gems/#{name}.json")) }
228
+ JSON.parse(body)["version"] == version
229
+ rescue StandardError
230
+ false
231
+ end
232
+
196
233
  # Dependents get an automatic version bump (they're being re-released
197
234
  # with a tightened constraint); the source gem's version comes from the
198
235
  # :bump step.
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Gemchain
4
- VERSION = "0.2.1"
4
+ VERSION = "0.2.3"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gemchain
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.2.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kaka Ruto
@@ -109,7 +109,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
109
109
  - !ruby/object:Gem::Version
110
110
  version: '0'
111
111
  requirements: []
112
- rubygems_version: 4.0.3
112
+ rubygems_version: 4.0.18
113
113
  specification_version: 4
114
114
  summary: Cascade gem updates through interdependent gem ecosystems
115
115
  test_files: []