gemchain 0.2.0 → 0.2.2

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: cfa66ea15e541d38928f3f19469437e021e4e40566af6dbc68c17bdda991c986
4
- data.tar.gz: c9870c9615acd4f464302d282bf50ba722f098d5ed6e4e1da3c4ef60c46ab25e
3
+ metadata.gz: 7f57089a36952e2778e0ca90654ee97a6484ba1f6f9c002a91c1a30089c6973a
4
+ data.tar.gz: cd1f24b7232ba48c8f1135d8e80374eb656e49e3322d3773b1f3680020761160
5
5
  SHA512:
6
- metadata.gz: 0e4133d289e5b13d797ec17a8b57093e1cb0a4d74b5b65c865f9b7317d9e6714461e2b7d15cd873cbf0890fec66ec8cb7b1bd3c5290ebe247ffd63ea9c8bc9f4
7
- data.tar.gz: 0e00b7f943cf3ba4f39f831eebe8b128a4a5e6582d101f9b69cf46663ebfe8b639d74a858eb39921aa6d4c31eb7b336c6b8492a3aa016e15aca7389559611a70
6
+ metadata.gz: 989b65d9e09fc06ea8445c7137b5e641fd001f7ab332023132a075c91e3a1d7b9649d34470fe78650d2c258507e5f2729d253824d85ec38b082b2efd7121e829
7
+ data.tar.gz: 65efa9a75881d8880d950c2b880816e10d575c9cbf728f427a0679f5fc6fc77c37f41d836a44d5359dbd412b0d8e6bf18db25772c2cae317d37b53dc58dcef1b
data/CHANGELOG.md CHANGED
@@ -1,5 +1,29 @@
1
+ ## [0.2.2] — 2026-08-13
2
+
3
+ ### Fixed
4
+
5
+ - **Release skips the commit when nothing is staged** — a source gem whose
6
+ version bump was already committed (e.g. a fix shipped together with the
7
+ bump) crashed the cascade with "nothing to commit, working tree clean".
8
+ `:release` now checks for staged changes (`git diff --cached --quiet`)
9
+ and skips the commit while still building, pushing, and tagging.
10
+
1
11
  # Changelog
2
12
 
13
+ ## [0.2.1] — 2026-08-12
14
+
15
+ ### Fixed
16
+
17
+ - **Release staging skips gitignored `Gemfile.lock`** — `git add` failed for
18
+ gem repos that deliberately ignore their lockfile (e.g. ask-mcp), aborting
19
+ the cascade mid-release. Staging now skips files excluded via `.gitignore`.
20
+ - **Cascade mode detection verifies the release actually happened** — a
21
+ failed run can leave `version.rb` bumped while the gem was never pushed;
22
+ the previous `version.rb == target` check then skipped the source release
23
+ and released dependents against a nonexistent version. `update` now checks
24
+ RubyGems for the published version (injectable for tests) before assuming
25
+ post-release mode.
26
+
3
27
  ## [0.2.0] — 2026-08-10
4
28
 
5
29
  ### Added
@@ -4,8 +4,9 @@ module Gemchain
4
4
  class Cascade
5
5
  Result = Struct.new(:success, :message, :details, keyword_init: true)
6
6
 
7
- def initialize(workspace)
7
+ def initialize(workspace, released_check: nil)
8
8
  @workspace = workspace
9
+ @released_check = released_check || method(:released_on_rubygems?)
9
10
  end
10
11
 
11
12
  # Pre-release guard: warns about gems that depend on +gem_name+.
@@ -44,7 +45,9 @@ module Gemchain
44
45
  end
45
46
 
46
47
  # Performs the full cascade update:
47
- # 1. If the source gem isn't already at +new_version+: bump, test, release it
48
+ # 1. If the source gem isn't already at +new_version+ AND published on
49
+ # RubyGems: bump, test, release it (a version.rb match alone is not
50
+ # enough — a failed run can leave it bumped but unpublished)
48
51
  # 2. For each dependent (topological order): update constraint, test, bump, release
49
52
  #
50
53
  # With test_only: true, only the dependents' :test steps are emitted —
@@ -60,7 +63,7 @@ module Gemchain
60
63
  steps = []
61
64
  order = @workspace.cascade_order(gem_name)
62
65
 
63
- if !test_only && gem.version.to_s != new_version
66
+ if !test_only && (gem.version.to_s != new_version || !@released_check.call(gem_name, new_version))
64
67
  # The source must be released before dependents' `bundle update`
65
68
  # can resolve the new version from RubyGems.
66
69
  steps << {action: :bump, gem: gem_name, version: new_version}
@@ -105,5 +108,17 @@ module Gemchain
105
108
 
106
109
  lines.join("\n")
107
110
  end
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.
115
+ def released_on_rubygems?(name, version)
116
+ require "json"
117
+ require "net/http"
118
+ body = Net::HTTP.get(URI("https://rubygems.org/api/v1/gems/#{name}.json"))
119
+ JSON.parse(body)["version"] == version
120
+ rescue StandardError
121
+ false
122
+ end
108
123
  end
109
124
  end
@@ -23,6 +23,13 @@ module Gemchain
23
23
  detail = out.lines.first(5).join.strip if detail.empty?
24
24
  raise ExecutionError, "Command failed: #{command.join(' ')}#{detail.empty? ? '' : "\n#{detail}"}"
25
25
  end
26
+
27
+ # Runs a command and reports success without raising. Used for
28
+ # conditional checks (e.g. "are there staged changes?").
29
+ def success?(command, chdir: nil)
30
+ _out, _err, status = Open3.capture3(*command, chdir: chdir)
31
+ status.success?
32
+ end
26
33
  end
27
34
 
28
35
  BUMP_LEVELS = %i[patch minor major].freeze
@@ -162,8 +169,7 @@ module Gemchain
162
169
  name = step[:gem]
163
170
  version = step[:version] || bump_dependent(dir, name)
164
171
 
165
- files = [gemspec_file(dir), version_file(dir), File.join(dir, "Gemfile.lock")]
166
- .select { |f| File.exist?(f) }
172
+ files = release_files(dir)
167
173
  @runner.run(["git", "add", *files], chdir: dir)
168
174
 
169
175
  message = if step[:dependency]
@@ -171,7 +177,12 @@ module Gemchain
171
177
  else
172
178
  "Bump to #{version} (gemchain)"
173
179
  end
174
- @runner.run(["git", "commit", "-m", message], chdir: dir)
180
+ # Skip the commit when the release's own bump/constraint changes were
181
+ # already committed (e.g. a fix shipped together with the version
182
+ # bump) — `git commit` would fail on a clean tree.
183
+ unless @runner.success?(["git", "diff", "--cached", "--quiet"], chdir: dir)
184
+ @runner.run(["git", "commit", "-m", message], chdir: dir)
185
+ end
175
186
 
176
187
  @runner.run(["gem", "build", "#{name}.gemspec"], chdir: dir)
177
188
  @runner.run(["gem", "push", "#{name}-#{version}.gem"], chdir: dir)
@@ -179,6 +190,21 @@ module Gemchain
179
190
  @runner.run(["git", "push", "origin", "HEAD", "--tags"], chdir: dir)
180
191
  end
181
192
 
193
+ # Gemspec and version file are always staged; Gemfile.lock only when it
194
+ # exists and isn't deliberately excluded via .gitignore (gem repos
195
+ # commonly ignore it).
196
+ def release_files(dir)
197
+ files = [gemspec_file(dir), version_file(dir)]
198
+ lockfile = File.join(dir, "Gemfile.lock")
199
+ files << lockfile if File.exist?(lockfile) && !git_ignored?(dir, lockfile)
200
+ files
201
+ end
202
+
203
+ def git_ignored?(dir, file)
204
+ _out, _err, status = Open3.capture3("git", "check-ignore", "--no-index", "-q", file, chdir: dir)
205
+ status.success?
206
+ end
207
+
182
208
  # Dependents get an automatic version bump (they're being re-released
183
209
  # with a tightened constraint); the source gem's version comes from the
184
210
  # :bump step.
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Gemchain
4
- VERSION = "0.2.0"
4
+ VERSION = "0.2.2"
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.0
4
+ version: 0.2.2
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: []