kettle-dev 3.0.26 → 3.0.27

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: 1582fd7f0bbd245a22585ae5194634a4196eba3a8f4fab15f7b021b07f26d369
4
- data.tar.gz: d2797c4e806c11031b7c981f5110b4d3b5e9ba06bed4b285ee383c16c9c60eec
3
+ metadata.gz: f2c4229bf9687d5eeb37b3969ae097bd1826819756082e26053970f9c953048f
4
+ data.tar.gz: 6eaf217455b0f4215bb3c4699e0a5651d1367524208567121ea869a482cf9ca9
5
5
  SHA512:
6
- metadata.gz: 561944e1c04ad428df2cf80c4bbd86ae65330b1a15f0ce749502a94ad788fa529602200a1db82df57e07ea8f003fb918da69661c2ff91d28b5598b12f7b6a3c5
7
- data.tar.gz: ad54887f5ca42fb2f494ab7545aa1adfb5804e3dfe8a37ca3ead4097580f29330ee68773f5a8c0fc37640770113a6af9073fcd45cef2611871e5d75e6ec1fa67
6
+ metadata.gz: 21ba6652f5ed37d6b67674b7869604c4700a94b8618185c8891e652271de5c71b85ea9eea576b5936ec1c43715c4a95f18ef9c117b3f2b74fac07546ad1d3fed
7
+ data.tar.gz: dc321c0c5531ff7b006bcbb83f3dee87a693b70b000619e4907e7e037fd597791446b1e75907a2895cc6135f0c25af62caaf4e782baa9ef4f089dca8ac504255
checksums.yaml.gz.sig CHANGED
Binary file
data/CHANGELOG.md CHANGED
@@ -30,6 +30,21 @@ Please file a bug if you notice a violation of semantic versioning.
30
30
 
31
31
  ### Security
32
32
 
33
+ ## [3.0.27] - 2026-08-31
34
+
35
+ - TAG: [v3.0.27][3.0.27t]
36
+ - COVERAGE: 91.68% -- 5998/6542 lines in 47 files
37
+ - BRANCH COVERAGE: 76.20% -- 2296/3013 branches in 47 files
38
+ - 50.24% documented
39
+
40
+ ### Added
41
+
42
+ - Emit GitHub Release creation and asset-upload lifecycle events, including retry attempts.
43
+
44
+ ### Fixed
45
+
46
+ - Retry transient GitHub Release asset uploads and report every failed asset.
47
+
33
48
  ## [3.0.26] - 2026-08-31
34
49
 
35
50
  - TAG: [v3.0.26][3.0.26t]
@@ -3747,7 +3762,9 @@ Please file a bug if you notice a violation of semantic versioning.
3747
3762
  - Selecting will run the selected workflow via `act`
3748
3763
  - This may move to its own gem in the future.
3749
3764
 
3750
- [Unreleased]: https://github.com/kettle-dev/kettle-dev/compare/v3.0.26...HEAD
3765
+ [Unreleased]: https://github.com/kettle-dev/kettle-dev/compare/v3.0.27...HEAD
3766
+ [3.0.27]: https://github.com/kettle-dev/kettle-dev/compare/v3.0.26...v3.0.27
3767
+ [3.0.27t]: https://github.com/kettle-dev/kettle-dev/releases/tag/v3.0.27
3751
3768
  [3.0.26]: https://github.com/kettle-dev/kettle-dev/compare/v3.0.25...v3.0.26
3752
3769
  [3.0.26t]: https://github.com/kettle-dev/kettle-dev/releases/tag/v3.0.26
3753
3770
  [3.0.25]: https://github.com/kettle-dev/kettle-dev/compare/v3.0.24...v3.0.25
data/README.md CHANGED
@@ -1066,7 +1066,7 @@ Thanks for RTFM. ☺️
1066
1066
  [📌gitmoji]: https://gitmoji.dev
1067
1067
  [📌gitmoji-img]: https://img.shields.io/badge/gitmoji_commits-%20%F0%9F%98%9C%20%F0%9F%98%8D-34495e.svg?style=flat-square
1068
1068
  [🧮kloc]: https://www.youtube.com/watch?v=dQw4w9WgXcQ
1069
- [🧮kloc-img]: https://img.shields.io/badge/KLOC-6.514-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
1069
+ [🧮kloc-img]: https://img.shields.io/badge/KLOC-6.542-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
1070
1070
  [🔐security]: https://github.com/kettle-dev/kettle-dev/blob/main/SECURITY.md
1071
1071
  [🔐security-img]: https://img.shields.io/badge/security-policy-259D6C.svg?style=flat
1072
1072
  [📄copyright-notice-explainer]: https://opensource.stackexchange.com/questions/5778/why-do-licenses-such-as-the-mit-license-specify-a-single-year
@@ -26,6 +26,7 @@ $LOAD_PATH.unshift(repo_lib) if File.directory?(repo_lib) && !$LOAD_PATH.include
26
26
 
27
27
  begin
28
28
  require "kettle/dev"
29
+ require "kettle/ndjson"
29
30
  require "kettle/dev/executable_version"
30
31
  Kettle::Dev::ExecutableVersion.print_and_exit!(script_basename, ARGV)
31
32
  Kettle::Dev::ExecutableVersion.print_header(script_basename)
@@ -52,7 +53,7 @@ if ARGV.include?("-h") || ARGV.include?("--help")
52
53
  version=<VERSION> # Backward-compatible explicit version form
53
54
  --check # Verify RubyGems, remote tag, changelog, and token without creating a release
54
55
  --update # Update an existing GitHub Release title/body from CHANGELOG.md
55
- --events # Emit the final result as one NDJSON event
56
+ --events # Emit GitHub Release lifecycle and asset NDJSON events
56
57
  --allow-unpublished # Skip the RubyGems publication requirement (for intentionally non-RubyGems projects)
57
58
  --asset PATH # Attach an artifact to the GitHub release (repeatable)
58
59
 
@@ -106,7 +107,8 @@ if !explicit_version.nil? && explicit_version.empty?
106
107
  end
107
108
 
108
109
  begin
109
- cli = Kettle::Dev::ReleaseCLI.new(start_step: 18)
110
+ event_stream = events ? Kettle::Ndjson.event_stream($stdout, types: "github_release") : nil
111
+ cli = Kettle::Dev::ReleaseCLI.new(start_step: 18, event_stream: event_stream)
110
112
  version = explicit_version || cli.send(:detect_version)
111
113
  result = cli.send(:github_release_backfill_check, version, require_rubygems: !allow_unpublished)
112
114
 
@@ -135,22 +137,11 @@ begin
135
137
  exit 1 unless result.fetch("ok")
136
138
  exit 0 if check_only
137
139
 
138
- created, message = if update_only
140
+ created, _message = if update_only
139
141
  cli.send(:maybe_update_github_release!, version)
140
142
  else
141
143
  cli.send(:maybe_create_github_release!, version, assets: assets)
142
144
  end
143
- if events
144
- puts JSON.generate(
145
- event_version: 1,
146
- type: "github_release",
147
- action: update_only ? "update" : "create",
148
- status: created ? "ok" : "failed",
149
- version: result.fetch("version"),
150
- tag: result.fetch("tag"),
151
- result: message
152
- )
153
- end
154
145
  exit(created ? 0 : 1)
155
146
  rescue LoadError => e
156
147
  warn("#{script_basename}: could not load dependency: #{e.class}: #{e.message}")
@@ -7,6 +7,7 @@ require "shellwords"
7
7
  require "time"
8
8
  require "fileutils"
9
9
  require "net/http"
10
+ require "openssl"
10
11
  require "json"
11
12
  require "uri"
12
13
  require "yaml"
@@ -27,6 +28,8 @@ module Kettle
27
28
  class ReleaseCLI
28
29
  RUBYGEMS_INVALID_OTP = /Your OTP code is incorrect\. Please check it and retry\./.freeze
29
30
  OTP_RETRY_DELAY_SECONDS = 2
31
+ GITHUB_RELEASE_ASSET_UPLOAD_ATTEMPTS = 3
32
+ GITHUB_RELEASE_ASSET_UPLOAD_RETRY_DELAY_SECONDS = 1
30
33
  QUIET_ENV = {
31
34
  "KETTLE_JEM_QUIET" => "true",
32
35
  "KETTLE_JEM_DEBUG" => "false",
@@ -2880,7 +2883,9 @@ module Kettle
2880
2883
 
2881
2884
  tag = "v#{version}"
2882
2885
  puts "Creating GitHub release #{owner}/#{repo} #{tag}..."
2886
+ emit_github_release_event(action: "create", status: "started", tag: tag, assets: Array(assets).length)
2883
2887
  ok, msg = github_create_release(owner: owner, repo: repo, token: token, tag: tag, title: tag, body: body, assets: assets)
2888
+ emit_github_release_event(action: "create", status: ok ? "ok" : "failed", tag: tag, assets: Array(assets).length, reason: msg)
2884
2889
  if ok
2885
2890
  puts "GitHub release created for #{tag}."
2886
2891
  else
@@ -3063,8 +3068,7 @@ module Kettle
3063
3068
 
3064
3069
  release = JSON.parse(res.body)
3065
3070
  asset_messages = Array(assets).map { |asset| github_upload_release_asset(release.fetch("id"), owner: owner, repo: repo, token: token, path: asset) }
3066
- failed_asset = asset_messages.find { |ok, _message| !ok }
3067
- failed_asset || [true, "created with #{asset_messages.length} assets"]
3071
+ github_release_asset_result(asset_messages, "created")
3068
3072
  else
3069
3073
  if res.code.to_s == "422" && res.body.to_s.include?("already_exists")
3070
3074
  return [true, "already exists"] if Array(assets).empty?
@@ -3107,9 +3111,15 @@ module Kettle
3107
3111
  asset_messages = missing_assets.map do |asset|
3108
3112
  github_upload_release_asset(release.fetch("id"), owner: owner, repo: repo, token: token, path: asset)
3109
3113
  end
3110
- failed_asset = asset_messages.find { |ok, _message| !ok }
3114
+ github_release_asset_result(asset_messages, "updated existing release")
3115
+ end
3116
+
3117
+ def github_release_asset_result(asset_messages, success_prefix)
3118
+ failures = asset_messages.reject(&:first).map(&:last)
3119
+ return [false, failures.join("; ")] unless failures.empty?
3120
+
3111
3121
  asset_noun = asset_messages.one? ? "asset" : "assets"
3112
- failed_asset || [true, "updated existing release with #{asset_messages.length} #{asset_noun} uploaded"]
3122
+ [true, "#{success_prefix} with #{asset_messages.length} #{asset_noun} uploaded"]
3113
3123
  end
3114
3124
 
3115
3125
  def github_release_for_tag(owner:, repo:, token:, tag:)
@@ -3130,6 +3140,42 @@ module Kettle
3130
3140
  path = File.expand_path(path)
3131
3141
  return [false, "asset does not exist: #{path}"] unless File.file?(path)
3132
3142
 
3143
+ attempts = GITHUB_RELEASE_ASSET_UPLOAD_ATTEMPTS
3144
+ attempts.times do |attempt|
3145
+ emit_github_release_event(
3146
+ action: "asset_upload",
3147
+ status: "started",
3148
+ asset: File.basename(path),
3149
+ attempt: attempt + 1,
3150
+ attempts: attempts
3151
+ )
3152
+ result = github_upload_release_asset_once(release_id, owner: owner, repo: repo, token: token, path: path)
3153
+ retrying = !result.first && retryable_github_release_asset_upload_result?(result.last) && attempt < attempts - 1
3154
+ status = if result.first
3155
+ "ok"
3156
+ elsif retrying
3157
+ "retrying"
3158
+ else
3159
+ "failed"
3160
+ end
3161
+ emit_github_release_event(
3162
+ action: "asset_upload",
3163
+ status: status,
3164
+ asset: File.basename(path),
3165
+ attempt: attempt + 1,
3166
+ attempts: attempts,
3167
+ reason: result.first ? nil : result.last
3168
+ )
3169
+ return result unless retrying
3170
+
3171
+ warn("GitHub release asset upload failed for #{File.basename(path)}; retrying #{attempt + 2}/#{attempts}.")
3172
+ sleep(GITHUB_RELEASE_ASSET_UPLOAD_RETRY_DELAY_SECONDS)
3173
+ end
3174
+ rescue => e
3175
+ [false, "asset #{File.basename(path)}: #{e.class}: #{e.message}"]
3176
+ end
3177
+
3178
+ def github_upload_release_asset_once(release_id, owner:, repo:, token:, path:)
3133
3179
  uri = URI("https://uploads.github.com/repos/#{owner}/#{repo}/releases/#{release_id}/assets")
3134
3180
  uri.query = URI.encode_www_form(name: File.basename(path))
3135
3181
  req = Net::HTTP::Post.new(uri)
@@ -3142,10 +3188,31 @@ module Kettle
3142
3188
  return [true, File.basename(path)] if res.is_a?(Net::HTTPSuccess) || res.is_a?(Net::HTTPCreated)
3143
3189
 
3144
3190
  [false, "asset #{File.basename(path)}: HTTP #{res.code}: #{res.body}"]
3145
- rescue => e
3191
+ rescue OpenSSL::SSL::SSLError, IOError, SystemCallError, Timeout::Error => e
3146
3192
  [false, "asset #{File.basename(path)}: #{e.class}: #{e.message}"]
3147
3193
  end
3148
3194
 
3195
+ def retryable_github_release_asset_upload_result?(message)
3196
+ message.match?(/(?:OpenSSL::SSL::SSLError|IOError|Errno::|(?:Net::)?(?:Open|Read)Timeout|HTTP (?:429|5\d\d))/)
3197
+ end
3198
+
3199
+ def emit_github_release_event(action:, status:, tag: nil, asset: nil, assets: nil, attempt: nil, attempts: nil, reason: nil)
3200
+ Kettle::Ndjson.emit_event(
3201
+ @event_recorder,
3202
+ "github_release",
3203
+ action: action,
3204
+ phase: "release",
3205
+ status: status,
3206
+ tag: tag,
3207
+ asset: asset,
3208
+ assets: assets,
3209
+ attempt: attempt,
3210
+ attempts: attempts,
3211
+ reason: reason,
3212
+ mark: release_progress_mark(status)
3213
+ )
3214
+ end
3215
+
3149
3216
  def github_update_release(owner:, repo:, token:, tag:, title:, body:)
3150
3217
  release, error = github_release_for_tag(owner: owner, repo: repo, token: token, tag: tag)
3151
3218
  return [false, error] unless release
@@ -5,7 +5,7 @@ module Kettle
5
5
  # Version namespace for this gem.
6
6
  module Version
7
7
  # Current gem version.
8
- VERSION = "3.0.26"
8
+ VERSION = "3.0.27"
9
9
  end
10
10
  # Current gem version exposed at the traditional constant location.
11
11
  VERSION = Version::VERSION # Traditional Constant Location
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: 3.0.26
4
+ version: 3.0.27
5
5
  platform: ruby
6
6
  authors:
7
7
  - Peter H. Boling
@@ -80,7 +80,7 @@ dependencies:
80
80
  version: '0.1'
81
81
  - - ">="
82
82
  - !ruby/object:Gem::Version
83
- version: 0.1.14
83
+ version: 0.1.15
84
84
  type: :runtime
85
85
  prerelease: false
86
86
  version_requirements: !ruby/object:Gem::Requirement
@@ -90,7 +90,7 @@ dependencies:
90
90
  version: '0.1'
91
91
  - - ">="
92
92
  - !ruby/object:Gem::Version
93
- version: 0.1.14
93
+ version: 0.1.15
94
94
  - !ruby/object:Gem::Dependency
95
95
  name: kettle-rb
96
96
  requirement: !ruby/object:Gem::Requirement
@@ -400,10 +400,10 @@ licenses:
400
400
  - AGPL-3.0-only
401
401
  metadata:
402
402
  homepage_uri: https://kettle-dev.galtzo.com
403
- source_code_uri: https://github.com/kettle-dev/kettle-dev/tree/v3.0.26
404
- changelog_uri: https://github.com/kettle-dev/kettle-dev/blob/v3.0.26/CHANGELOG.md
403
+ source_code_uri: https://github.com/kettle-dev/kettle-dev/tree/v3.0.27
404
+ changelog_uri: https://github.com/kettle-dev/kettle-dev/blob/v3.0.27/CHANGELOG.md
405
405
  bug_tracker_uri: https://github.com/kettle-dev/kettle-dev/issues
406
- documentation_uri: https://www.rubydoc.info/gems/kettle-dev/3.0.26
406
+ documentation_uri: https://www.rubydoc.info/gems/kettle-dev/3.0.27
407
407
  funding_uri: https://github.com/sponsors/pboling
408
408
  wiki_uri: https://github.com/kettle-dev/kettle-dev/wiki
409
409
  news_uri: https://www.railsbling.com/tags/kettle-dev
metadata.gz.sig CHANGED
@@ -1,2 +1,2 @@
1
- ����\��Y�"� �f2f+��O/�B!k��B�����&S���I�����E�M��2Z�_�W�}x��H���Y�@�[K��W����fƌ �4��l��ܭ�w���H���T^��mZ/}<}��"B7�ž�_t ��$���&�fr����I���0�l��d�]�cJH�6stc���p�� @ac��j z�Ԛ�4�D)���Ӈ$���1'ݸt4Fc����T�7�2���Y� ��E�SŎF�{K
2
- o{:6��M����Q���� �}���Ӑ��v��FCD�V��T�D8Fkp����<�|S-R��/�|��G�_g4۲��=��o��8���qP:���s���/�dl:) �y��{�>� F f�ŭF
1
+ �n�v��zmbb��L@b}B^z-wW�‘�ba*�s�ɰ�F<��hl4Uo��n���[�'�0!�����]�lMg�!�*�z�r�.��� �@;2����V�
2
+ c4Ȱ�ě9_��