kettle-dev 2.4.1 → 2.4.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: 77a7621fa0d906b716e933205ff585081b8746345530f540a8c83f6e67d3f347
4
- data.tar.gz: ae54a1e1f33347caa8c6d07eae632db0b88c95583a838fece942bd9bb1d84bfb
3
+ metadata.gz: a251345bb56780a5c5b9efcfb53d394ca469581b5798f10839ac8ed5b4cae682
4
+ data.tar.gz: 4a81578792afe11fd09299ea7d1836d66f0cd8c76af00235491b8234fcec1553
5
5
  SHA512:
6
- metadata.gz: 03b528fd8d85c047d18606f023a0f9f217ca88556ed26ddbc62504a282bceda6a42ea315c0cd0fdd0e2e4307fefeda357d34fcf4fab98d83dc5214c9d202bbb2
7
- data.tar.gz: d630c32d5f8cfbb28b74cdc552a9d331aa73d25de2d80298736f759f2bfe3181649c845a1d812c5ead41f9792a3169091f07ae33a2afb1610028b191effb08e7
6
+ metadata.gz: 0cfa39888af23e14856d9874d965248c8d68a83b88c42bd3cc722b1a84fe8564b3c1381aa714aaebe0df531822365b70e4f2e7e2eff913f5e781e201e5f49b13
7
+ data.tar.gz: aefab75e861bc0c2132385b4e7411904b01cb6562c82d3485cdbdbb27d407004450b8e0a7030f33abf869bc73df4074f2cbdd1c3ef7abcfb21e9222b5c1c7f9e
checksums.yaml.gz.sig CHANGED
Binary file
data/CHANGELOG.md CHANGED
@@ -30,6 +30,20 @@ Please file a bug if you notice a violation of semantic versioning.
30
30
 
31
31
  ### Security
32
32
 
33
+ ## [2.4.2] - 2026-07-24
34
+
35
+ - TAG: [v2.4.2][2.4.2t]
36
+ - COVERAGE: 92.36% -- 3991/4321 lines in 38 files
37
+ - BRANCH COVERAGE: 76.06% -- 1563/2055 branches in 38 files
38
+ - 59.00% documented
39
+
40
+ ### Fixed
41
+
42
+ - `kettle-release` now validates published gem availability with an isolated
43
+ Bundler probe against gem.coop before continuing past the publish step, and
44
+ uninstalls newly introduced local candidate gems when the release did not
45
+ happen or cannot be validated.
46
+
33
47
  ## [2.4.1] - 2026-07-24
34
48
 
35
49
  - TAG: [v2.4.1][2.4.1t]
@@ -2660,7 +2674,9 @@ Please file a bug if you notice a violation of semantic versioning.
2660
2674
  - Selecting will run the selected workflow via `act`
2661
2675
  - This may move to its own gem in the future.
2662
2676
 
2663
- [Unreleased]: https://github.com/kettle-dev/kettle-dev/compare/v2.4.1...HEAD
2677
+ [Unreleased]: https://github.com/kettle-dev/kettle-dev/compare/v2.4.2...HEAD
2678
+ [2.4.2]: https://github.com/kettle-dev/kettle-dev/compare/v2.4.1...v2.4.2
2679
+ [2.4.2t]: https://github.com/kettle-dev/kettle-dev/releases/tag/v2.4.2
2664
2680
  [2.4.1]: https://github.com/kettle-dev/kettle-dev/compare/v2.4.0...v2.4.1
2665
2681
  [2.4.1t]: https://github.com/kettle-dev/kettle-dev/releases/tag/v2.4.1
2666
2682
  [2.4.0]: https://github.com/kettle-dev/kettle-dev/compare/v2.3.11...v2.4.0
data/README.md CHANGED
@@ -891,7 +891,7 @@ Thanks for RTFM. ☺️
891
891
  [📌gitmoji]: https://gitmoji.dev
892
892
  [📌gitmoji-img]: https://img.shields.io/badge/gitmoji_commits-%20%F0%9F%98%9C%20%F0%9F%98%8D-34495e.svg?style=flat-square
893
893
  [🧮kloc]: https://www.youtube.com/watch?v=dQw4w9WgXcQ
894
- [🧮kloc-img]: https://img.shields.io/badge/KLOC-4.256-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
894
+ [🧮kloc-img]: https://img.shields.io/badge/KLOC-4.321-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
895
895
  [🔐security]: https://github.com/kettle-dev/kettle-dev/blob/main/SECURITY.md
896
896
  [🔐security-img]: https://img.shields.io/badge/security-policy-259D6C.svg?style=flat
897
897
  [📄copyright-notice-explainer]: https://opensource.stackexchange.com/questions/5778/why-do-licenses-such-as-the-mit-license-specify-a-single-year
@@ -40,6 +40,8 @@ module Kettle
40
40
  "BUNDLE_SUPPRESS_INSTALL_USING_MESSAGES" => "true"
41
41
  }.freeze
42
42
  DEBUG_TRUE_VALUES = %w[1 true yes on].freeze
43
+ RELEASE_VALIDATION_SOURCE = "https://gem.coop"
44
+
43
45
  class << self
44
46
  def run_cmd!(cmd)
45
47
  # For Bundler-invoked build/release, explicitly prefix SKIP_GEM_SIGNING so
@@ -121,6 +123,7 @@ module Kettle
121
123
  @skip_bundle_audit = truthy_value?(skip_bundle_audit) || truthy_value?(ENV["KETTLE_DEV_SKIP_BUNDLE_AUDIT"])
122
124
  @version_override = Kettle::Dev::Versioning.normalize_explicit_version(version)
123
125
  @appraisal_task = normalize_appraisal_task(appraisal_task || ENV["KETTLE_RELEASE_APPRAISAL_TASK"])
126
+ @release_candidate = nil
124
127
  end
125
128
 
126
129
  def run
@@ -346,13 +349,18 @@ module Kettle
346
349
 
347
350
  # 15. release and tag
348
351
  if run_step?(15)
352
+ version ||= detect_version
353
+ gem_name = detect_gem_name
354
+ @release_candidate = build_release_candidate(gem_name, version)
349
355
  if local_ci?
350
- version ||= detect_version
351
- release_gem_and_tag_locally!(version)
356
+ with_unpublished_candidate_cleanup { release_gem_and_tag_locally!(version) }
352
357
  else
353
358
  puts "Running release (you may be prompted for signing key password and RubyGems MFA OTP)..."
354
- run_cmd!("bundle exec rake release")
355
- mark_gem_coop_release_cache_bust(version || detect_version)
359
+ with_unpublished_candidate_cleanup do
360
+ run_cmd!("bundle exec rake release")
361
+ confirm_release_candidate_available!(@release_candidate)
362
+ end
363
+ mark_gem_coop_release_cache_bust(version)
356
364
  end
357
365
  end
358
366
 
@@ -1144,9 +1152,123 @@ module Kettle
1144
1152
 
1145
1153
  puts "Publishing #{File.basename(gem_path)} to RubyGems without pushing git refs..."
1146
1154
  run_cmd!("gem push #{Shellwords.escape(gem_path)}")
1155
+ confirm_release_candidate_available!(@release_candidate || build_release_candidate(gem_name_from_gem_path(gem_path, version), version))
1147
1156
  mark_gem_coop_release_cache_bust(version, gem_name: gem_name_from_gem_path(gem_path, version))
1148
1157
  end
1149
1158
 
1159
+ def build_release_candidate(gem_name, version)
1160
+ ReleaseCandidate.new(
1161
+ gem_name: gem_name.to_s,
1162
+ version: version.to_s,
1163
+ installed_before: gem_version_installed?(gem_name, version),
1164
+ published: false
1165
+ )
1166
+ end
1167
+
1168
+ def with_unpublished_candidate_cleanup
1169
+ yield
1170
+ rescue SystemExit, StandardError
1171
+ cleanup_unpublished_release_candidate(@release_candidate)
1172
+ raise
1173
+ end
1174
+
1175
+ def cleanup_unpublished_release_candidate(candidate)
1176
+ return unless candidate
1177
+ return if candidate.published
1178
+ return if candidate.installed_before
1179
+ return unless gem_version_installed?(candidate.gem_name, candidate.version)
1180
+
1181
+ warn(
1182
+ "Release of #{candidate.gem_name} #{candidate.version} was not validated; " \
1183
+ "uninstalling the local candidate gem so future bundle updates cannot resolve an unreleased version."
1184
+ )
1185
+ uninstall_release_candidate(candidate)
1186
+ end
1187
+
1188
+ def uninstall_release_candidate(candidate)
1189
+ run_cmd!(
1190
+ "gem uninstall #{Shellwords.escape(candidate.gem_name)} " \
1191
+ "-v #{Shellwords.escape(candidate.version)} -x -I --ignore-dependencies"
1192
+ )
1193
+ rescue SystemExit, StandardError => error
1194
+ warn("Could not uninstall local candidate #{candidate.gem_name} #{candidate.version}: #{error.message}")
1195
+ warn("Manual cleanup: gem uninstall #{candidate.gem_name} -v #{candidate.version} -x -I --ignore-dependencies")
1196
+ end
1197
+
1198
+ def gem_version_installed?(gem_name, version)
1199
+ Gem::Specification.reset
1200
+ Gem::Specification.find_all_by_name(gem_name.to_s, "= #{version}").any?
1201
+ rescue Gem::LoadError
1202
+ false
1203
+ end
1204
+
1205
+ def confirm_release_candidate_available!(candidate)
1206
+ run_release_availability_probe(candidate)
1207
+ candidate.published = true
1208
+ true
1209
+ rescue SystemExit
1210
+ raise
1211
+ rescue => error
1212
+ abort(<<~MSG)
1213
+ Published gem availability could not be validated for #{candidate.gem_name} #{candidate.version}: #{error.class}: #{error.message}
1214
+ kettle-release will clean up any local candidate install that was introduced by this failed release attempt.
1215
+ If the push actually succeeded, resolve the registry/checksum state manually before resuming.
1216
+ MSG
1217
+ end
1218
+
1219
+ def run_release_availability_probe(candidate)
1220
+ script_path = write_release_availability_probe(candidate)
1221
+ stdout_str = nil
1222
+ stderr_str = nil
1223
+ status = nil
1224
+ with_unbundled_release_probe_env do
1225
+ stdout_str, stderr_str, status = Open3.capture3(self.class.send(:command_env), Gem.ruby, script_path)
1226
+ end
1227
+ $stdout.print(stdout_str) unless stdout_str.to_s.empty?
1228
+ return true if status.success?
1229
+
1230
+ diag = stderr_str.to_s.empty? ? "" : "\n--- STDERR ---\n#{stderr_str}".rstrip
1231
+ abort("Published gem availability probe failed for #{candidate.gem_name} #{candidate.version} (exit #{status.exitstatus})#{diag}")
1232
+ ensure
1233
+ FileUtils.rm_f(script_path) if script_path
1234
+ end
1235
+
1236
+ def with_unbundled_release_probe_env(&block)
1237
+ if defined?(Bundler)
1238
+ Bundler.with_unbundled_env(&block)
1239
+ else
1240
+ yield
1241
+ end
1242
+ end
1243
+
1244
+ def write_release_availability_probe(candidate)
1245
+ dir = File.join(@root, "tmp", "kettle-release")
1246
+ FileUtils.mkdir_p(dir)
1247
+ path = File.join(dir, "validate-#{candidate.gem_name}-#{candidate.version}-#{$$}.rb")
1248
+ File.write(path, release_availability_probe_script(candidate))
1249
+ path
1250
+ end
1251
+
1252
+ def release_availability_probe_script(candidate)
1253
+ <<~RUBY
1254
+ # frozen_string_literal: true
1255
+
1256
+ gem_name = #{candidate.gem_name.dump}
1257
+ version = #{candidate.version.dump}
1258
+
1259
+ require "bundler/inline"
1260
+
1261
+ gemfile(true) do
1262
+ source #{RELEASE_VALIDATION_SOURCE.dump}
1263
+ gem gem_name, "= \#{version}"
1264
+ end
1265
+
1266
+ spec = Gem.loaded_specs.fetch(gem_name)
1267
+ abort("loaded \#{gem_name} \#{spec.version}, expected \#{version}") unless spec.version.to_s == version
1268
+ puts "Validated \#{gem_name} \#{version} from #{RELEASE_VALIDATION_SOURCE}"
1269
+ RUBY
1270
+ end
1271
+
1150
1272
  def detect_version
1151
1273
  Kettle::Dev::Versioning.detect_version(@root, override: @version_override)
1152
1274
  end
@@ -1709,3 +1831,14 @@ module Kettle
1709
1831
  end
1710
1832
  end
1711
1833
  end
1834
+
1835
+ class Kettle::Dev::ReleaseCLI::ReleaseCandidate
1836
+ attr_accessor :gem_name, :version, :installed_before, :published
1837
+
1838
+ def initialize(gem_name:, version:, installed_before:, published:)
1839
+ @gem_name = gem_name
1840
+ @version = version
1841
+ @installed_before = installed_before
1842
+ @published = published
1843
+ end
1844
+ end
@@ -3,7 +3,7 @@
3
3
  module Kettle
4
4
  module Dev
5
5
  module Version
6
- VERSION = "2.4.1"
6
+ VERSION = "2.4.2"
7
7
  end
8
8
  VERSION = Version::VERSION # Traditional Constant Location
9
9
  end
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kettle-dev
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.4.1
4
+ version: 2.4.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Peter H. Boling
@@ -349,10 +349,10 @@ licenses:
349
349
  - AGPL-3.0-only
350
350
  metadata:
351
351
  homepage_uri: https://kettle-dev.galtzo.com
352
- source_code_uri: https://github.com/kettle-dev/kettle-dev/tree/v2.4.1
353
- changelog_uri: https://github.com/kettle-dev/kettle-dev/blob/v2.4.1/CHANGELOG.md
352
+ source_code_uri: https://github.com/kettle-dev/kettle-dev/tree/v2.4.2
353
+ changelog_uri: https://github.com/kettle-dev/kettle-dev/blob/v2.4.2/CHANGELOG.md
354
354
  bug_tracker_uri: https://github.com/kettle-dev/kettle-dev/issues
355
- documentation_uri: https://www.rubydoc.info/gems/kettle-dev/2.4.1
355
+ documentation_uri: https://www.rubydoc.info/gems/kettle-dev/2.4.2
356
356
  funding_uri: https://github.com/sponsors/pboling
357
357
  wiki_uri: https://github.com/kettle-dev/kettle-dev/wiki
358
358
  news_uri: https://www.railsbling.com/tags/kettle-dev
metadata.gz.sig CHANGED
Binary file