kettle-dev 3.0.25 → 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 +4 -4
- checksums.yaml.gz.sig +0 -0
- data/CHANGELOG.md +31 -1
- data/README.md +1 -1
- data/exe/kettle-gh-release +5 -14
- data/lib/kettle/dev/release_cli.rb +94 -17
- data/lib/kettle/dev/version.rb +1 -1
- data.tar.gz.sig +0 -0
- metadata +6 -6
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f2c4229bf9687d5eeb37b3969ae097bd1826819756082e26053970f9c953048f
|
|
4
|
+
data.tar.gz: 6eaf217455b0f4215bb3c4699e0a5651d1367524208567121ea869a482cf9ca9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 21ba6652f5ed37d6b67674b7869604c4700a94b8618185c8891e652271de5c71b85ea9eea576b5936ec1c43715c4a95f18ef9c117b3f2b74fac07546ad1d3fed
|
|
7
|
+
data.tar.gz: dc321c0c5531ff7b006bcbb83f3dee87a693b70b000619e4907e7e037fd597791446b1e75907a2895cc6135f0c25af62caaf4e782baa9ef4f089dca8ac504255
|
checksums.yaml.gz.sig
CHANGED
|
Binary file
|
data/CHANGELOG.md
CHANGED
|
@@ -30,6 +30,32 @@ 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
|
+
|
|
48
|
+
## [3.0.26] - 2026-08-31
|
|
49
|
+
|
|
50
|
+
- TAG: [v3.0.26][3.0.26t]
|
|
51
|
+
- COVERAGE: 91.60% -- 5967/6514 lines in 47 files
|
|
52
|
+
- BRANCH COVERAGE: 76.17% -- 2286/3001 branches in 47 files
|
|
53
|
+
- 50.47% documented
|
|
54
|
+
|
|
55
|
+
### Fixed
|
|
56
|
+
|
|
57
|
+
- Refresh existing GitHub Release notes while backfilling missing assets.
|
|
58
|
+
|
|
33
59
|
## [3.0.25] - 2026-08-31
|
|
34
60
|
|
|
35
61
|
- TAG: [v3.0.25][3.0.25t]
|
|
@@ -3736,7 +3762,11 @@ Please file a bug if you notice a violation of semantic versioning.
|
|
|
3736
3762
|
- Selecting will run the selected workflow via `act`
|
|
3737
3763
|
- This may move to its own gem in the future.
|
|
3738
3764
|
|
|
3739
|
-
[Unreleased]: https://github.com/kettle-dev/kettle-dev/compare/v3.0.
|
|
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
|
|
3768
|
+
[3.0.26]: https://github.com/kettle-dev/kettle-dev/compare/v3.0.25...v3.0.26
|
|
3769
|
+
[3.0.26t]: https://github.com/kettle-dev/kettle-dev/releases/tag/v3.0.26
|
|
3740
3770
|
[3.0.25]: https://github.com/kettle-dev/kettle-dev/compare/v3.0.24...v3.0.25
|
|
3741
3771
|
[3.0.25t]: https://github.com/kettle-dev/kettle-dev/releases/tag/v3.0.25
|
|
3742
3772
|
[3.0.24]: https://github.com/kettle-dev/kettle-dev/compare/v3.0.23...v3.0.24
|
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.
|
|
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
|
data/exe/kettle-gh-release
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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,
|
|
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,17 +3068,18 @@ 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
|
-
|
|
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?
|
|
3071
3075
|
|
|
3072
|
-
|
|
3076
|
+
update_existing_github_release_with_assets(
|
|
3073
3077
|
owner: owner,
|
|
3074
3078
|
repo: repo,
|
|
3075
3079
|
token: token,
|
|
3076
3080
|
tag: tag,
|
|
3081
|
+
title: title,
|
|
3082
|
+
body: body,
|
|
3077
3083
|
assets: assets
|
|
3078
3084
|
)
|
|
3079
3085
|
else
|
|
@@ -3084,20 +3090,36 @@ module Kettle
|
|
|
3084
3090
|
[false, "#{e.class}: #{e.message}"]
|
|
3085
3091
|
end
|
|
3086
3092
|
|
|
3087
|
-
def
|
|
3093
|
+
def update_existing_github_release_with_assets(owner:, repo:, token:, tag:, title:, body:, assets:)
|
|
3088
3094
|
release, error = github_release_for_tag(owner: owner, repo: repo, token: token, tag: tag)
|
|
3089
3095
|
return [false, error] unless release
|
|
3090
3096
|
|
|
3097
|
+
updated, error = github_update_release_by_id(
|
|
3098
|
+
release.fetch("id"),
|
|
3099
|
+
owner: owner,
|
|
3100
|
+
repo: repo,
|
|
3101
|
+
token: token,
|
|
3102
|
+
title: title,
|
|
3103
|
+
body: body
|
|
3104
|
+
)
|
|
3105
|
+
return [false, error] unless updated
|
|
3106
|
+
|
|
3091
3107
|
existing_names = Array(release["assets"]).filter_map { |asset| asset["name"] }.to_set
|
|
3092
3108
|
missing_assets = Array(assets).reject { |asset| existing_names.include?(File.basename(asset)) }
|
|
3093
|
-
return [true, "
|
|
3109
|
+
return [true, "updated existing release with all assets present"] if missing_assets.empty?
|
|
3094
3110
|
|
|
3095
3111
|
asset_messages = missing_assets.map do |asset|
|
|
3096
3112
|
github_upload_release_asset(release.fetch("id"), owner: owner, repo: repo, token: token, path: asset)
|
|
3097
3113
|
end
|
|
3098
|
-
|
|
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
|
+
|
|
3099
3121
|
asset_noun = asset_messages.one? ? "asset" : "assets"
|
|
3100
|
-
|
|
3122
|
+
[true, "#{success_prefix} with #{asset_messages.length} #{asset_noun} uploaded"]
|
|
3101
3123
|
end
|
|
3102
3124
|
|
|
3103
3125
|
def github_release_for_tag(owner:, repo:, token:, tag:)
|
|
@@ -3118,6 +3140,42 @@ module Kettle
|
|
|
3118
3140
|
path = File.expand_path(path)
|
|
3119
3141
|
return [false, "asset does not exist: #{path}"] unless File.file?(path)
|
|
3120
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:)
|
|
3121
3179
|
uri = URI("https://uploads.github.com/repos/#{owner}/#{repo}/releases/#{release_id}/assets")
|
|
3122
3180
|
uri.query = URI.encode_www_form(name: File.basename(path))
|
|
3123
3181
|
req = Net::HTTP::Post.new(uri)
|
|
@@ -3130,20 +3188,39 @@ module Kettle
|
|
|
3130
3188
|
return [true, File.basename(path)] if res.is_a?(Net::HTTPSuccess) || res.is_a?(Net::HTTPCreated)
|
|
3131
3189
|
|
|
3132
3190
|
[false, "asset #{File.basename(path)}: HTTP #{res.code}: #{res.body}"]
|
|
3133
|
-
rescue => e
|
|
3191
|
+
rescue OpenSSL::SSL::SSLError, IOError, SystemCallError, Timeout::Error => e
|
|
3134
3192
|
[false, "asset #{File.basename(path)}: #{e.class}: #{e.message}"]
|
|
3135
3193
|
end
|
|
3136
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
|
+
|
|
3137
3216
|
def github_update_release(owner:, repo:, token:, tag:, title:, body:)
|
|
3138
|
-
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
id = JSON.parse(release.body).fetch("id")
|
|
3217
|
+
release, error = github_release_for_tag(owner: owner, repo: repo, token: token, tag: tag)
|
|
3218
|
+
return [false, error] unless release
|
|
3219
|
+
|
|
3220
|
+
github_update_release_by_id(release.fetch("id"), owner: owner, repo: repo, token: token, title: title, body: body)
|
|
3221
|
+
end
|
|
3222
|
+
|
|
3223
|
+
def github_update_release_by_id(id, owner:, repo:, token:, title:, body:)
|
|
3147
3224
|
uri = URI("https://api.github.com/repos/#{owner}/#{repo}/releases/#{id}")
|
|
3148
3225
|
request = Net::HTTP::Patch.new(uri)
|
|
3149
3226
|
request["Accept"] = "application/vnd.github+json"
|
data/lib/kettle/dev/version.rb
CHANGED
|
@@ -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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
404
|
-
changelog_uri: https://github.com/kettle-dev/kettle-dev/blob/v3.0.
|
|
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.
|
|
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
|
Binary file
|