kettle-dev 2.2.19 → 2.2.20

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: 309dfdff5a98f7d5d2f189a3b51a4d0f231d037b61ea37023918a70ec436d997
4
- data.tar.gz: 9dff167d60de601c34738beb35c81df5e3c1a8874630ccaebf753507fa205a41
3
+ metadata.gz: 22a0bb59de4bca05c100302f2050a300c2a60a90a2124f2f5aa07c215c6b6919
4
+ data.tar.gz: 6b314236d27b59baa482317413c5b5c95303b9530b33693ee03841491ed00bc6
5
5
  SHA512:
6
- metadata.gz: baf9a43aeac7238588295facc22c7a151c68abeed3b754872d539bc25571ea43e87eaac9ffb81ea4f5133206d1e9f1d0ef7d2fc609b666fa0bfad54df27069d0
7
- data.tar.gz: 6f607059c8cd545bad791de6277f40f0b75d956fbc7654677cdcfb53345c2b658e98881e80f694992041e872e96b4e643d69f61a578ca305821fe449fe6c21ae
6
+ metadata.gz: 1f8a2d831b26d101a1ea8e381ff3cfc7bbce6d88682e70ec43c22a37b3ba047da5376247b5f32b1184f088514da8a1377f54ffcbe4def383b249515c70c3c518
7
+ data.tar.gz: 1aa2b1ceb5886238f9034491256e3c7fbdd5cbba121c27393288bced653f92051e43fec7f242a35b4cab7e316731e422a46c73c054548c96ee4b1537c6b371ac
checksums.yaml.gz.sig CHANGED
Binary file
data/CHANGELOG.md CHANGED
@@ -30,6 +30,34 @@ Please file a bug if you notice a violation of semantic versioning.
30
30
 
31
31
  ### Security
32
32
 
33
+ ## [2.2.20] - 2026-06-27
34
+
35
+ - TAG: [v2.2.20][2.2.20t]
36
+ - COVERAGE: 91.82% -- 4131/4499 lines in 33 files
37
+ - BRANCH COVERAGE: 72.73% -- 1632/2244 branches in 33 files
38
+ - 61.04% documented
39
+
40
+ ### Added
41
+
42
+ - `kettle-release` now accepts `skip_steps=...` / `--skip-steps ...` to skip
43
+ selected numbered release steps while running the rest of the release flow.
44
+
45
+ ### Fixed
46
+
47
+ - `kettle-release` now suppresses inherited Bundler/debug verbosity when running
48
+ release child commands and Appraisal bundle installs.
49
+
50
+ - `kettle-release` now switches a local `RUBOCOP_LTS_LOCAL` checkout to the
51
+ branch matching the project's selected `rubocop-rubyN_N` style gem before
52
+ running setup and default rake tasks.
53
+
54
+ - Restored Ruby 2.4 parser compatibility for recent release and CI helper code.
55
+
56
+ ### Changed
57
+
58
+ - `kettle-release` now reads RuboCop LTS branch selection from
59
+ `Kettle::Rb::CompatMatrix`.
60
+
33
61
  ## [2.2.19] - 2026-06-26
34
62
 
35
63
  - TAG: [v2.2.19][2.2.19t]
@@ -2293,7 +2321,9 @@ Please file a bug if you notice a violation of semantic versioning.
2293
2321
  - Selecting will run the selected workflow via `act`
2294
2322
  - This may move to its own gem in the future.
2295
2323
 
2296
- [Unreleased]: https://github.com/kettle-dev/kettle-dev/compare/v2.2.19...HEAD
2324
+ [Unreleased]: https://github.com/kettle-dev/kettle-dev/compare/v2.2.20...HEAD
2325
+ [2.2.20]: https://github.com/kettle-dev/kettle-dev/compare/v2.2.19...v2.2.20
2326
+ [2.2.20t]: https://github.com/kettle-dev/kettle-dev/releases/tag/v2.2.20
2297
2327
  [2.2.19]: https://github.com/kettle-dev/kettle-dev/compare/v2.2.18...v2.2.19
2298
2328
  [2.2.19t]: https://github.com/kettle-dev/kettle-dev/releases/tag/v2.2.19
2299
2329
  [2.2.18]: https://github.com/kettle-dev/kettle-dev/compare/v2.2.17...v2.2.18
data/README.md CHANGED
@@ -874,7 +874,7 @@ Thanks for RTFM. ☺️
874
874
  [📌gitmoji]: https://gitmoji.dev
875
875
  [📌gitmoji-img]: https://img.shields.io/badge/gitmoji_commits-%20%F0%9F%98%9C%20%F0%9F%98%8D-34495e.svg?style=flat-square
876
876
  [🧮kloc]: https://www.youtube.com/watch?v=dQw4w9WgXcQ
877
- [🧮kloc-img]: https://img.shields.io/badge/KLOC-4.449-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
877
+ [🧮kloc-img]: https://img.shields.io/badge/KLOC-4.499-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
878
878
  [🔐security]: https://github.com/kettle-dev/kettle-dev/blob/main/SECURITY.md
879
879
  [🔐security-img]: https://img.shields.io/badge/security-policy-259D6C.svg?style=flat
880
880
  [📄copyright-notice-explainer]: https://opensource.stackexchange.com/questions/5778/why-do-licenses-such-as-the-mit-license-specify-a-single-year
data/exe/kettle-release CHANGED
@@ -43,7 +43,7 @@ end
43
43
  # Do not guard with __FILE__ == $PROGRAM_NAME because binstubs use Kernel.load.
44
44
  if ARGV.include?("-h") || ARGV.include?("--help")
45
45
  puts <<~USAGE
46
- Usage: kettle-release [--version VERSION] [--local-ci] [--appraisal-update] [start_step=<0-19>]
46
+ Usage: kettle-release [--version VERSION] [--local-ci] [--appraisal-update] [--skip-steps STEPS] [start_step=<0-19>]
47
47
 
48
48
  Automates the release flow for a Ruby gem in the host project.
49
49
 
@@ -76,6 +76,8 @@ if ARGV.include?("-h") || ARGV.include?("--help")
76
76
 
77
77
  Options:
78
78
  start_step=<number> # Begin at the numbered step above (e.g., 10 to resume at CI monitoring)
79
+ skip_steps=<numbers> # Comma-separated numbered steps to skip while running the rest
80
+ --skip-steps NUMBERS # Alias for skip_steps=NUMBERS (e.g., --skip-steps 10 or --skip-steps 10,18)
79
81
  --version VERSION # Use this version instead of detecting VERSION from lib/**/version.rb
80
82
  --appraisal-update # Use slower appraisal:update instead of default appraisal:generate
81
83
  --local-ci # Sensitive release mode: run act locally, publish before any git push,
@@ -97,12 +99,13 @@ if ARGV.include?("-h") || ARGV.include?("--help")
97
99
  kettle-release --local-ci # run local act CI, publish gem, then push commits/tags
98
100
  kettle-release start_step=1 # skip pre-release checks and start numbered release steps
99
101
  kettle-release start_step=10 # resume by monitoring CI for the current branch
102
+ kettle-release skip_steps=10 # run the release but skip remote CI monitoring
100
103
  SKIP_GEM_SIGNING=true kettle-release start_step=14 # build+release without signing
101
104
  USAGE
102
105
  exit 0
103
106
  end
104
107
 
105
- # Parse start_step=<n> from ARGV
108
+ # Parse release options from ARGV
106
109
  def extract_version_arg!(argv)
107
110
  version = nil
108
111
  if (idx = argv.index("--version"))
@@ -121,14 +124,33 @@ def extract_version_arg!(argv)
121
124
  Kettle::Dev::Versioning.normalize_explicit_version(version)
122
125
  end
123
126
 
127
+ def extract_skip_steps_arg!(argv)
128
+ skip_steps = nil
129
+ if (idx = argv.index("--skip-steps"))
130
+ skip_steps = argv[idx + 1]
131
+ Kettle::Dev::ExitAdapter.abort("--skip-steps requires a comma-separated step list") if skip_steps.to_s.empty?
132
+ argv.slice!(idx, 2)
133
+ end
134
+ argv.delete_if do |arg|
135
+ if arg.start_with?("--skip-steps=", "skip_steps=")
136
+ skip_steps = arg.split("=", 2)[1]
137
+ true
138
+ else
139
+ false
140
+ end
141
+ end
142
+ skip_steps
143
+ end
144
+
124
145
  local_ci = ARGV.include?("--local-ci")
125
146
  appraisal_task = ARGV.delete("--appraisal-update") ? "appraisal:update" : nil
126
147
  start_step_arg = ARGV.find { |a| a.start_with?("start_step=") }
127
148
  start_step = start_step_arg ? start_step_arg.split("=", 2)[1].to_i : 0
128
149
  version_override = extract_version_arg!(ARGV)
150
+ skip_steps = extract_skip_steps_arg!(ARGV)
129
151
 
130
152
  begin
131
- Kettle::Dev::ReleaseCLI.new(start_step: start_step, local_ci: local_ci, version: version_override, appraisal_task: appraisal_task).run
153
+ Kettle::Dev::ReleaseCLI.new(start_step: start_step, local_ci: local_ci, version: version_override, appraisal_task: appraisal_task, skip_steps: skip_steps).run
132
154
  rescue LoadError => e
133
155
  warn("#{script_basename}: could not load dependency: #{e.class}: #{e.message}")
134
156
  warn(Array(e.backtrace).join("\n")) if ENV["DEBUG"]
@@ -264,9 +264,11 @@ module Kettle
264
264
  @options[:progress] = bool
265
265
  end
266
266
  opt.on("--skip-pattern PATTERN", "Skip workflow paths matching pattern (repeatable)") do |pattern|
267
- @options[:reject_patterns] << Regexp.new(pattern)
268
- rescue RegexpError => e
269
- Kettle::Dev::ExitAdapter.abort("Invalid --skip-pattern #{pattern.inspect}: #{e.message}")
267
+ begin
268
+ @options[:reject_patterns] << Regexp.new(pattern)
269
+ rescue RegexpError => e
270
+ Kettle::Dev::ExitAdapter.abort("Invalid --skip-pattern #{pattern.inspect}: #{e.message}")
271
+ end
270
272
  end
271
273
  opt.on("--[no-]validate", "Validate YAML after editing") do |bool|
272
274
  @options[:validate] = bool
@@ -282,25 +284,27 @@ module Kettle
282
284
  def load_workflows(paths, state)
283
285
  file_progress = progress_bar(title: "Files", total: paths.length)
284
286
  paths.each_with_object([]) do |path, workflows|
285
- state[:files_scanned] += 1
286
- text = begin
287
- File.read(path)
288
- rescue Errno::EACCES => e
289
- record_failure(state, path: path, error: "read_error: #{e.message}")
290
- next
291
- end
287
+ begin
288
+ state[:files_scanned] += 1
289
+ text = begin
290
+ File.read(path)
291
+ rescue Errno::EACCES => e
292
+ record_failure(state, path: path, error: "read_error: #{e.message}")
293
+ next
294
+ end
292
295
 
293
- parsed = begin
294
- Psych.parse_stream(text)
295
- rescue Psych::Exception => e
296
- record_failure(state, path: path, error: "yaml_parse_error: #{e.message}")
297
- next
298
- end
296
+ parsed = begin
297
+ Psych.parse_stream(text)
298
+ rescue Psych::Exception => e
299
+ record_failure(state, path: path, error: "yaml_parse_error: #{e.message}")
300
+ next
301
+ end
299
302
 
300
- uses_nodes = extract_uses_nodes(parsed, text)
301
- workflows << {path: path, text: text, uses_nodes: uses_nodes} unless uses_nodes.empty?
302
- ensure
303
- file_progress&.increment
303
+ uses_nodes = extract_uses_nodes(parsed, text)
304
+ workflows << {path: path, text: text, uses_nodes: uses_nodes} unless uses_nodes.empty?
305
+ ensure
306
+ file_progress&.increment
307
+ end
304
308
  end
305
309
  end
306
310
 
@@ -198,10 +198,12 @@ module Kettle
198
198
  def remotes_with_urls
199
199
  if @backend == :gem
200
200
  @git.remotes.each_with_object({}) do |r, h|
201
- h[r.name] = r.url
202
- rescue => e
203
- Kettle::Dev.debug_error(e, __method__)
204
- # ignore
201
+ begin
202
+ h[r.name] = r.url
203
+ rescue => e
204
+ Kettle::Dev.debug_error(e, __method__)
205
+ # ignore
206
+ end
205
207
  end
206
208
  else
207
209
  out, status = Open3.capture2("git", "remote", "-v")
@@ -253,10 +253,12 @@ module Kettle
253
253
  Array(glob_pattern)
254
254
  end
255
255
  urls = files.flat_map do |f|
256
- extract_image_urls_from_text(File.read(f))
257
- rescue => e
258
- warn("[kettle-pre-release] Could not read #{Kettle::Dev.display_path(f)}: #{e.class}: #{e.message}")
259
- []
256
+ begin
257
+ extract_image_urls_from_text(File.read(f))
258
+ rescue => e
259
+ warn("[kettle-pre-release] Could not read #{Kettle::Dev.display_path(f)}: #{e.class}: #{e.message}")
260
+ []
261
+ end
260
262
  end
261
263
  urls.uniq
262
264
  end
@@ -5,7 +5,27 @@ begin
5
5
  require "appraisal/task"
6
6
 
7
7
  bundle = "bundle"
8
- appraisal_env = {"BUNDLE_GEMFILE" => "Appraisal.root.gemfile"}
8
+ quiet_env = {
9
+ "KETTLE_JEM_QUIET" => "true",
10
+ "KETTLE_JEM_DEBUG" => "false",
11
+ "KETTLE_DEV_DEBUG" => "false",
12
+ "SMORG_RB_DEBUG" => "false",
13
+ "DEBUG" => nil,
14
+ "BUNDLE_QUIET" => "true",
15
+ "BUNDLE_DEBUG" => "false",
16
+ "BUNDLER_DEBUG" => "false",
17
+ "BUNDLE_VERBOSE" => "false",
18
+ "DEBUG_RESOLVER" => nil,
19
+ "DEBUG_RESOLVER_TREE" => nil,
20
+ "BUNDLER_DEBUG_RESOLVER" => nil,
21
+ "BUNDLER_DEBUG_RESOLVER_TREE" => nil,
22
+ "DEBUG_COMPACT_INDEX" => nil,
23
+ "MOLINILLO_DEBUG" => nil,
24
+ "BUNDLE_SILENCE_DEPRECATIONS" => "true",
25
+ "BUNDLE_SILENCE_ROOT_WARNING" => "true",
26
+ "BUNDLE_SUPPRESS_INSTALL_USING_MESSAGES" => "true"
27
+ }
28
+ appraisal_env = quiet_env.merge("BUNDLE_GEMFILE" => "Appraisal.root.gemfile")
9
29
 
10
30
  run_command = lambda do |failure_message, *args|
11
31
  ok = system(*args)
@@ -18,7 +38,8 @@ begin
18
38
  "appraisal:generate failed: BUNDLE_GEMFILE=Appraisal.root.gemfile bundle install",
19
39
  appraisal_env,
20
40
  bundle,
21
- "install"
41
+ "install",
42
+ "--quiet"
22
43
  )
23
44
 
24
45
  # 2) BUNDLE_GEMFILE=Appraisal.root.gemfile bundle exec appraisal generate
@@ -33,18 +54,20 @@ begin
33
54
  end
34
55
 
35
56
  run_appraisal_task = lambda do |task_name, primary_steps = nil|
36
- if primary_steps
37
- begin
38
- primary_steps.call
39
- rescue RuntimeError => e
40
- warn("[kettle-dev][#{task_name}] #{e.message}; falling back to appraisal:generate")
57
+ begin
58
+ if primary_steps
59
+ begin
60
+ primary_steps.call
61
+ rescue RuntimeError => e
62
+ warn("[kettle-dev][#{task_name}] #{e.message}; falling back to appraisal:generate")
63
+ run_generate_steps.call
64
+ end
65
+ else
41
66
  run_generate_steps.call
42
67
  end
43
- else
44
- run_generate_steps.call
68
+ rescue RuntimeError => e
69
+ abort(e.message)
45
70
  end
46
- rescue RuntimeError => e
47
- abort(e.message)
48
71
  end
49
72
 
50
73
  desc("Install Appraisal gemfiles (initial setup for projects that didn't previously use Appraisal)")
@@ -58,7 +81,8 @@ begin
58
81
  "appraisal:install failed: BUNDLE_GEMFILE=Appraisal.root.gemfile bundle install",
59
82
  appraisal_env,
60
83
  bundle,
61
- "install"
84
+ "install",
85
+ "--quiet"
62
86
  )
63
87
 
64
88
  # 2) BUNDLE_GEMFILE=Appraisal.root.gemfile bundle exec appraisal generate-install
@@ -105,7 +129,8 @@ begin
105
129
  "appraisal:update failed: BUNDLE_GEMFILE=Appraisal.root.gemfile bundle install",
106
130
  appraisal_env,
107
131
  bundle,
108
- "install"
132
+ "install",
133
+ "--quiet"
109
134
  )
110
135
 
111
136
  # 2) BUNDLE_GEMFILE=Appraisal.root.gemfile bundle update --bundler
@@ -122,7 +147,8 @@ begin
122
147
  "appraisal:update failed: BUNDLE_GEMFILE=Appraisal.root.gemfile bundle install",
123
148
  appraisal_env,
124
149
  bundle,
125
- "install"
150
+ "install",
151
+ "--quiet"
126
152
  )
127
153
 
128
154
  # 4) BUNDLE_GEMFILE=Appraisal.root.gemfile bundle exec appraisal generate-update
@@ -157,11 +183,13 @@ begin
157
183
  else
158
184
  failures = []
159
185
  locks.each do |f|
160
- File.delete(f)
161
- rescue Errno::ENOENT
162
- # Ignore if already gone
163
- rescue => e
164
- failures << [f, e]
186
+ begin
187
+ File.delete(f)
188
+ rescue Errno::ENOENT
189
+ # Ignore if already gone
190
+ rescue => e
191
+ failures << [f, e]
192
+ end
165
193
  end
166
194
 
167
195
  unless failures.empty?
@@ -11,11 +11,33 @@ require "json"
11
11
  require "uri"
12
12
 
13
13
  # External gems
14
+ require "kettle/rb/compat_matrix"
14
15
  require "ruby-progressbar"
15
16
 
16
17
  module Kettle
17
18
  module Dev
18
19
  class ReleaseCLI
20
+ QUIET_ENV = {
21
+ "KETTLE_JEM_QUIET" => "true",
22
+ "KETTLE_JEM_DEBUG" => "false",
23
+ "KETTLE_DEV_DEBUG" => "false",
24
+ "SMORG_RB_DEBUG" => "false",
25
+ "DEBUG" => nil,
26
+ "BUNDLE_QUIET" => "true",
27
+ "BUNDLE_DEBUG" => "false",
28
+ "BUNDLER_DEBUG" => "false",
29
+ "BUNDLE_VERBOSE" => "false",
30
+ "DEBUG_RESOLVER" => nil,
31
+ "DEBUG_RESOLVER_TREE" => nil,
32
+ "BUNDLER_DEBUG_RESOLVER" => nil,
33
+ "BUNDLER_DEBUG_RESOLVER_TREE" => nil,
34
+ "DEBUG_COMPACT_INDEX" => nil,
35
+ "MOLINILLO_DEBUG" => nil,
36
+ "BUNDLE_SILENCE_DEPRECATIONS" => "true",
37
+ "BUNDLE_SILENCE_ROOT_WARNING" => "true",
38
+ "BUNDLE_SUPPRESS_INSTALL_USING_MESSAGES" => "true"
39
+ }.freeze
40
+ DEBUG_TRUE_VALUES = %w[1 true yes on].freeze
19
41
  class << self
20
42
  def run_cmd!(cmd)
21
43
  # For Bundler-invoked build/release, explicitly prefix SKIP_GEM_SIGNING so
@@ -26,7 +48,7 @@ module Kettle
26
48
  end
27
49
  puts "$ #{cmd}"
28
50
  # Pass a plain Hash for the environment to satisfy tests and avoid ENV object oddities
29
- env_hash = ENV.respond_to?(:to_hash) ? ENV.to_hash : ENV.to_h
51
+ env_hash = command_env
30
52
 
31
53
  # Some commands are interactive (e.g., `bundle exec rake release` prompting for RubyGems MFA).
32
54
  # Using capture3 detaches STDIN, preventing prompts from working. For such commands, use system
@@ -62,6 +84,19 @@ module Kettle
62
84
  abort("Command failed: #{cmd} (exit #{exit_code})#{diag}")
63
85
  end
64
86
  end
87
+
88
+ private
89
+
90
+ def command_env
91
+ env_hash = ENV.respond_to?(:to_hash) ? ENV.to_hash : ENV.to_h
92
+ return env_hash if debug_env_enabled?
93
+
94
+ env_hash.merge(QUIET_ENV)
95
+ end
96
+
97
+ def debug_env_enabled?
98
+ DEBUG_TRUE_VALUES.include?(ENV.fetch("KETTLE_DEV_DEBUG", "").downcase)
99
+ end
65
100
  end
66
101
 
67
102
  private
@@ -72,21 +107,22 @@ module Kettle
72
107
 
73
108
  public
74
109
 
75
- def initialize(start_step: 0, local_ci: false, version: nil, appraisal_task: nil)
110
+ def initialize(start_step: 0, local_ci: false, version: nil, appraisal_task: nil, skip_steps: nil)
76
111
  @root = Kettle::Dev::CIHelpers.project_root
77
112
  @git = Kettle::Dev::GitAdapter.new
78
113
  @start_step = (start_step || 0).to_i
79
114
  @start_step = 0 if @start_step < 0
115
+ @skip_steps = normalize_skip_steps(skip_steps)
80
116
  @local_ci = !!local_ci
81
117
  @version_override = Kettle::Dev::Versioning.normalize_explicit_version(version)
82
118
  @appraisal_task = normalize_appraisal_task(appraisal_task || ENV["KETTLE_RELEASE_APPRAISAL_TASK"])
83
119
  end
84
120
 
85
121
  def run
86
- run_pre_release_checks! if @start_step <= 0
122
+ run_pre_release_checks! if run_step?(0)
87
123
 
88
124
  # 1. Ensure Bundler version ✓
89
- ensure_bundler_2_7_plus!
125
+ ensure_bundler_2_7_plus! if run_step?(1)
90
126
 
91
127
  version = nil
92
128
  committed = nil
@@ -94,7 +130,7 @@ module Kettle
94
130
  feature = nil
95
131
 
96
132
  # 2. Version detection and sanity checks + prompt
97
- if @start_step <= 2
133
+ if run_step?(2)
98
134
  version = detect_version
99
135
  puts "Detected version: #{version.inspect}"
100
136
 
@@ -186,13 +222,15 @@ module Kettle
186
222
  end
187
223
  end
188
224
 
225
+ prepare_rubocop_lts_local_branch! if rubocop_lts_release_preflight_needed?
226
+
189
227
  # 3. bin/setup
190
- run_cmd!("bin/setup") if @start_step <= 3
228
+ run_cmd!("bin/setup") if run_step?(3)
191
229
  # 4. bin/rake
192
- run_cmd!("bin/rake") if @start_step <= 4
230
+ run_cmd!("bin/rake") if run_step?(4)
193
231
 
194
232
  # 5. appraisal:generate (optional) + canonical docs build
195
- if @start_step <= 5
233
+ if run_step?(5)
196
234
  appraisals_path = File.join(@root, "Appraisals")
197
235
  if File.file?(appraisals_path)
198
236
  puts "Appraisals detected at #{Kettle::Dev.display_path(appraisals_path)}. Running: bin/rake #{@appraisal_task}"
@@ -206,47 +244,47 @@ module Kettle
206
244
  end
207
245
 
208
246
  # 6. git user + commit release prep
209
- if @start_step <= 6
247
+ if run_step?(6)
210
248
  ensure_git_user!
211
249
  version ||= detect_version
212
250
  committed = commit_release_prep!(version)
213
251
  end
214
252
 
215
253
  # 7. optional local CI via act
216
- maybe_run_local_ci_before_push!(committed, force: local_ci?) if @start_step <= 7
254
+ maybe_run_local_ci_before_push!(committed, force: local_ci?) if run_step?(7)
217
255
 
218
256
  # 8. ensure trunk synced
219
- if @start_step <= 8 && !local_ci?
257
+ if run_step?(8) && !local_ci?
220
258
  trunk = detect_trunk_branch
221
259
  feature = current_branch
222
260
  puts "Trunk branch detected: #{trunk}"
223
261
  ensure_trunk_synced_before_push!(trunk, feature)
224
- elsif @start_step <= 8
262
+ elsif run_step?(8)
225
263
  puts "Local CI release mode: skipping remote trunk sync before publishing."
226
264
  end
227
265
 
228
266
  # 9. push branches
229
- push! if @start_step <= 9 && !local_ci?
267
+ push! if run_step?(9) && !local_ci?
230
268
 
231
269
  # 10. monitor CI after push
232
- monitor_workflows_after_push! if @start_step <= 10 && !local_ci?
270
+ monitor_workflows_after_push! if run_step?(10) && !local_ci?
233
271
 
234
272
  # 11. merge feature into trunk and push
235
- if @start_step <= 11 && !local_ci?
273
+ if run_step?(11) && !local_ci?
236
274
  trunk ||= detect_trunk_branch
237
275
  feature ||= current_branch
238
276
  merge_feature_into_trunk_and_push!(trunk, feature)
239
277
  end
240
278
 
241
279
  # 12. checkout trunk and pull
242
- if @start_step <= 12 && !local_ci?
280
+ if run_step?(12) && !local_ci?
243
281
  trunk ||= detect_trunk_branch
244
282
  checkout!(trunk)
245
283
  pull!(trunk)
246
284
  end
247
285
 
248
286
  # 13. signing guidance and checks
249
- if @start_step <= 13
287
+ if run_step?(13)
250
288
  if ENV.fetch("SKIP_GEM_SIGNING", "false").casecmp("false").zero?
251
289
  puts "TIP: For local dry-runs or testing the release workflow, set SKIP_GEM_SIGNING=true to avoid PEM password prompts."
252
290
  if Kettle::Dev::InputAdapter.tty?
@@ -267,13 +305,13 @@ module Kettle
267
305
  end
268
306
 
269
307
  # 14. build
270
- if @start_step <= 14
308
+ if run_step?(14)
271
309
  puts "Running build (you may be prompted for the signing key password)..."
272
310
  run_cmd!("bundle exec rake build")
273
311
  end
274
312
 
275
313
  # 15. release and tag
276
- if @start_step <= 15
314
+ if run_step?(15)
277
315
  if local_ci?
278
316
  version ||= detect_version
279
317
  release_gem_and_tag_locally!(version)
@@ -287,7 +325,7 @@ module Kettle
287
325
  # Checksums are generated after release to avoid including checksums/ in gem package
288
326
  # Rationale: Running gem_checksums before release may commit checksums/ and cause Bundler's
289
327
  # release build to include them in the gem, thus altering the artifact, and invalidating the checksums.
290
- if @start_step <= 16
328
+ if run_step?(16)
291
329
  # Generate checksums for the just-built artifact, commit them, then validate
292
330
  run_cmd!("bin/gem_checksums")
293
331
  version ||= detect_version
@@ -295,19 +333,19 @@ module Kettle
295
333
  end
296
334
 
297
335
  # 17. push checksum commit (gem_checksums already commits)
298
- if @start_step <= 17
336
+ if run_step?(17)
299
337
  push!
300
338
  push_tags! if local_ci?
301
339
  end
302
340
 
303
341
  # 18. create GitHub release (optional)
304
- if @start_step <= 18
342
+ if run_step?(18)
305
343
  version ||= detect_version
306
344
  maybe_create_github_release!(version)
307
345
  end
308
346
 
309
347
  # 19. push tags to remotes (final step)
310
- push_tags! if @start_step <= 19 && !local_ci?
348
+ push_tags! if run_step?(19) && !local_ci?
311
349
 
312
350
  # Final success message
313
351
  begin
@@ -330,12 +368,72 @@ module Kettle
330
368
  abort("Unsupported appraisal task #{value.inspect}; use appraisal:generate or appraisal:update.")
331
369
  end
332
370
 
371
+ def normalize_skip_steps(value)
372
+ raw_steps = Array(value).flat_map { |part| part.to_s.split(",") }.map(&:strip).reject(&:empty?)
373
+ raw_steps.map do |raw|
374
+ abort("Invalid skip_steps value #{raw.inspect}; use comma-separated release step numbers from 0 to 19.") unless raw.match?(/\A\d+\z/)
375
+
376
+ step = raw.to_i
377
+ abort("Invalid skip_steps value #{raw.inspect}; release steps are numbered 0 to 19.") unless step.between?(0, 19)
378
+
379
+ step
380
+ end.uniq
381
+ end
382
+
333
383
  private
334
384
 
335
385
  def local_ci?
336
386
  @local_ci
337
387
  end
338
388
 
389
+ def run_step?(step)
390
+ @start_step <= step && !@skip_steps.include?(step)
391
+ end
392
+
393
+ def rubocop_lts_release_preflight_needed?
394
+ (3..5).any? { |step| run_step?(step) }
395
+ end
396
+
397
+ def prepare_rubocop_lts_local_branch!
398
+ local_root = rubocop_lts_local_root
399
+ return unless local_root
400
+
401
+ ruby_gem = selected_rubocop_lts_ruby_gem
402
+ return unless ruby_gem
403
+
404
+ branch = Kettle::Rb::CompatMatrix.rubocop_lts_branch_for_gem(ruby_gem)
405
+ abort("Cannot select RUBOCOP_LTS_LOCAL branch for #{ruby_gem.inspect}.") unless branch
406
+
407
+ checkout = File.join(local_root, "rubocop-lts")
408
+ current, ok = git_output(["-C", checkout, "branch", "--show-current"])
409
+ abort("Cannot inspect RUBOCOP_LTS_LOCAL checkout at #{checkout}.") unless ok
410
+ return if current == branch
411
+
412
+ puts "Switching RUBOCOP_LTS_LOCAL checkout #{checkout} to #{branch} for #{ruby_gem}."
413
+ _stdout, switched = git_output(["-C", checkout, "switch", branch])
414
+ abort("Cannot switch RUBOCOP_LTS_LOCAL checkout at #{checkout} to #{branch}. Commit or stash local changes, then retry.") unless switched
415
+ end
416
+
417
+ def rubocop_lts_local_root
418
+ value = ENV["RUBOCOP_LTS_LOCAL"].to_s.strip
419
+ return nil if value.empty? || %w[false 0 no off].include?(value.downcase)
420
+ return File.join(Dir.home, "src", "rubocop-lts") if %w[true 1 yes on].include?(value.downcase)
421
+ return value if value.start_with?("/")
422
+
423
+ File.join(Dir.home, value)
424
+ end
425
+
426
+ def selected_rubocop_lts_ruby_gem
427
+ path = File.join(@root, "gemfiles", "modular", "style_local.gemfile")
428
+ return nil unless File.file?(path)
429
+
430
+ content = File.read(path)
431
+ # This reads the generated kettle-jem style_local.gemfile declaration
432
+ # without evaluating the Gemfile during release preflight.
433
+ local_gems = content[/\blocal_gems\s*=\s*%w\[(.*?)\]/m, 1].to_s.split
434
+ local_gems.find { |gem_name| Kettle::Rb::CompatMatrix.rubocop_ruby_gem?(gem_name) }
435
+ end
436
+
339
437
  def run_pre_release_checks!
340
438
  puts "Running pre-release checks via kettle-pre-release..."
341
439
  Kettle::Dev::PreReleaseCLI.new(check_num: 1).run
@@ -91,46 +91,48 @@ module Kettle
91
91
 
92
92
  # Print GitLab pipeline status (if configured) for the current branch.
93
93
  print_gitlab_status = proc do
94
- branch = Kettle::Dev::CIHelpers.current_branch
95
- # Detect any GitLab remote (not just origin), mirroring CIMonitor behavior
96
- gl_remotes = Kettle::Dev::CIMonitor.gitlab_remote_candidates
97
- if gl_remotes.nil? || gl_remotes.empty? || branch.nil?
98
- puts "Latest GL (#{branch || "n/a"}) pipeline: n/a"
99
- next
100
- end
94
+ begin
95
+ branch = Kettle::Dev::CIHelpers.current_branch
96
+ # Detect any GitLab remote (not just origin), mirroring CIMonitor behavior
97
+ gl_remotes = Kettle::Dev::CIMonitor.gitlab_remote_candidates
98
+ if gl_remotes.nil? || gl_remotes.empty? || branch.nil?
99
+ puts "Latest GL (#{branch || "n/a"}) pipeline: n/a"
100
+ next
101
+ end
101
102
 
102
- # Parse owner/repo from the first GitLab remote URL
103
- gl_url = Kettle::Dev::CIMonitor.remote_url(gl_remotes.first)
104
- owner = repo = nil
105
- if gl_url =~ %r{git@gitlab.com:(.+?)/(.+?)(\.git)?$}
106
- owner = Regexp.last_match(1)
107
- repo = Regexp.last_match(2).sub(/\.git\z/, "")
108
- elsif gl_url =~ %r{https://gitlab.com/(.+?)/(.+?)(\.git)?$}
109
- owner = Regexp.last_match(1)
110
- repo = Regexp.last_match(2).sub(/\.git\z/, "")
111
- end
103
+ # Parse owner/repo from the first GitLab remote URL
104
+ gl_url = Kettle::Dev::CIMonitor.remote_url(gl_remotes.first)
105
+ owner = repo = nil
106
+ if gl_url =~ %r{git@gitlab.com:(.+?)/(.+?)(\.git)?$}
107
+ owner = Regexp.last_match(1)
108
+ repo = Regexp.last_match(2).sub(/\.git\z/, "")
109
+ elsif gl_url =~ %r{https://gitlab.com/(.+?)/(.+?)(\.git)?$}
110
+ owner = Regexp.last_match(1)
111
+ repo = Regexp.last_match(2).sub(/\.git\z/, "")
112
+ end
112
113
 
113
- unless owner && repo
114
- puts "Latest GL (#{branch}) pipeline: n/a"
115
- next
116
- end
114
+ unless owner && repo
115
+ puts "Latest GL (#{branch}) pipeline: n/a"
116
+ next
117
+ end
117
118
 
118
- pipe = Kettle::Dev::CIHelpers.gitlab_latest_pipeline(owner: owner, repo: repo, branch: branch)
119
- if pipe
120
- st = pipe["status"].to_s
121
- status = if st == "success"
122
- "success"
119
+ pipe = Kettle::Dev::CIHelpers.gitlab_latest_pipeline(owner: owner, repo: repo, branch: branch)
120
+ if pipe
121
+ st = pipe["status"].to_s
122
+ status = if st == "success"
123
+ "success"
124
+ else
125
+ ((st == "failed") ? "failure" : nil)
126
+ end
127
+ emoji = Kettle::Dev::CIMonitor.status_emoji(st, status)
128
+ details = [st, pipe["failure_reason"]].compact.join("/")
129
+ puts "Latest GL (#{branch}) pipeline: #{emoji} (#{details})"
123
130
  else
124
- ((st == "failed") ? "failure" : nil)
131
+ puts "Latest GL (#{branch}) pipeline: none"
125
132
  end
126
- emoji = Kettle::Dev::CIMonitor.status_emoji(st, status)
127
- details = [st, pipe["failure_reason"]].compact.join("/")
128
- puts "Latest GL (#{branch}) pipeline: #{emoji} (#{details})"
129
- else
130
- puts "Latest GL (#{branch}) pipeline: none"
133
+ rescue => e
134
+ puts "GL status: error #{e.class}: #{e.message}"
131
135
  end
132
- rescue => e
133
- puts "GL status: error #{e.class}: #{e.message}"
134
136
  end
135
137
 
136
138
  run_act_for = proc do |file_path|
@@ -262,10 +264,12 @@ module Kettle
262
264
  selected = nil
263
265
  input_thread = nil
264
266
  read_input = proc do
265
- selected = Kettle::Dev::InputAdapter.gets&.strip
266
- rescue StandardError, SystemExit, Interrupt => error
267
- puts "Error reading input: #{error.class}: #{error.message}" if Kettle::Dev::DEBUGGING
268
- selected = :input_error
267
+ begin
268
+ selected = Kettle::Dev::InputAdapter.gets&.strip
269
+ rescue StandardError, SystemExit, Interrupt => error
270
+ puts "Error reading input: #{error.class}: #{error.message}" if Kettle::Dev::DEBUGGING
271
+ selected = :input_error
272
+ end
269
273
  end
270
274
  if tty
271
275
  input_thread = Thread.new(&read_input) # rubocop:disable ThreadSafety/NewThread
@@ -279,52 +283,54 @@ module Kettle
279
283
 
280
284
  options.each do |code, file|
281
285
  workers << Thread.new(code, file, owner, repo, branch, token, start_at) do |c, f, ow, rp, br, tk, st_at| # rubocop:disable ThreadSafety/NewThread
282
- now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
283
- delay = 0.12 - (now - st_at)
284
- sleep(delay) if delay && delay > 0
285
-
286
- if ow.nil? || rp.nil? || br.nil?
287
- status_q << [c, f, "n/a"]
288
- Thread.exit
289
- end
290
- uri = URI("https://api.github.com/repos/#{ow}/#{rp}/actions/workflows/#{f}/runs?branch=#{URI.encode_www_form_component(br)}&per_page=1")
291
- poll_interval = Integer(ENV["CI_ACT_POLL_INTERVAL"] || 5)
292
- loop do
293
- begin
294
- req = Net::HTTP::Get.new(uri)
295
- req["User-Agent"] = "ci:act rake task"
296
- req["Authorization"] = "token #{tk}" if tk && !tk.empty?
297
- res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
298
- if res.is_a?(Net::HTTPSuccess)
299
- data = JSON.parse(res.body)
300
- run = data["workflow_runs"]&.first
301
- if run
302
- st = run["status"]
303
- con = run["conclusion"]
304
- emoji = Kettle::Dev::CIMonitor.status_emoji(st, con)
305
- details = [st, con].compact.join("/")
306
- status_q << [c, f, "#{emoji} (#{details})"]
307
- break if st == "completed"
286
+ begin
287
+ now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
288
+ delay = 0.12 - (now - st_at)
289
+ sleep(delay) if delay && delay > 0
290
+
291
+ if ow.nil? || rp.nil? || br.nil?
292
+ status_q << [c, f, "n/a"]
293
+ Thread.exit
294
+ end
295
+ uri = URI("https://api.github.com/repos/#{ow}/#{rp}/actions/workflows/#{f}/runs?branch=#{URI.encode_www_form_component(br)}&per_page=1")
296
+ poll_interval = Integer(ENV["CI_ACT_POLL_INTERVAL"] || 5)
297
+ loop do
298
+ begin
299
+ req = Net::HTTP::Get.new(uri)
300
+ req["User-Agent"] = "ci:act rake task"
301
+ req["Authorization"] = "token #{tk}" if tk && !tk.empty?
302
+ res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
303
+ if res.is_a?(Net::HTTPSuccess)
304
+ data = JSON.parse(res.body)
305
+ run = data["workflow_runs"]&.first
306
+ if run
307
+ st = run["status"]
308
+ con = run["conclusion"]
309
+ emoji = Kettle::Dev::CIMonitor.status_emoji(st, con)
310
+ details = [st, con].compact.join("/")
311
+ status_q << [c, f, "#{emoji} (#{details})"]
312
+ break if st == "completed"
313
+ else
314
+ status_q << [c, f, "none"]
315
+ break
316
+ end
308
317
  else
309
- status_q << [c, f, "none"]
310
- break
318
+ status_q << [c, f, "fail #{res.code}"]
311
319
  end
312
- else
313
- status_q << [c, f, "fail #{res.code}"]
320
+ rescue Exception => e # rubocop:disable Lint/RescueException
321
+ Kettle::Dev.debug_error(e, __method__)
322
+ # Catch all exceptions to prevent crashing the process from a worker thread
323
+ status_q << [c, f, "err"]
314
324
  end
315
- rescue Exception => e # rubocop:disable Lint/RescueException
316
- Kettle::Dev.debug_error(e, __method__)
317
- # Catch all exceptions to prevent crashing the process from a worker thread
318
- status_q << [c, f, "err"]
325
+ sleep(poll_interval)
319
326
  end
320
- sleep(poll_interval)
327
+ rescue Exception => e # rubocop:disable Lint/RescueException
328
+ Kettle::Dev.debug_error(e, __method__)
329
+ # simplecov:disable
330
+ # Catch all exceptions in the worker thread boundary, including SystemExit
331
+ status_q << [c, f, "err"]
332
+ # simplecov:enable
321
333
  end
322
- rescue Exception => e # rubocop:disable Lint/RescueException
323
- Kettle::Dev.debug_error(e, __method__)
324
- # simplecov:disable
325
- # Catch all exceptions in the worker thread boundary, including SystemExit
326
- status_q << [c, f, "err"]
327
- # simplecov:enable
328
334
  end
329
335
  end
330
336
 
@@ -3,7 +3,7 @@
3
3
  module Kettle
4
4
  module Dev
5
5
  module Version
6
- VERSION = "2.2.19"
6
+ VERSION = "2.2.20"
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.2.19
4
+ version: 2.2.20
5
5
  platform: ruby
6
6
  authors:
7
7
  - Peter H. Boling
@@ -37,6 +37,26 @@ cert_chain:
37
37
  -----END CERTIFICATE-----
38
38
  date: 1980-01-02 00:00:00.000000000 Z
39
39
  dependencies:
40
+ - !ruby/object:Gem::Dependency
41
+ name: kettle-rb
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '0.1'
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: 0.1.0
50
+ type: :runtime
51
+ prerelease: false
52
+ version_requirements: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - "~>"
55
+ - !ruby/object:Gem::Version
56
+ version: '0.1'
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: 0.1.0
40
60
  - !ruby/object:Gem::Dependency
41
61
  name: kettle-test
42
62
  requirement: !ruby/object:Gem::Requirement
@@ -339,10 +359,10 @@ licenses:
339
359
  - AGPL-3.0-only
340
360
  metadata:
341
361
  homepage_uri: https://kettle-dev.galtzo.com
342
- source_code_uri: https://github.com/kettle-dev/kettle-dev/tree/v2.2.19
343
- changelog_uri: https://github.com/kettle-dev/kettle-dev/blob/v2.2.19/CHANGELOG.md
362
+ source_code_uri: https://github.com/kettle-dev/kettle-dev/tree/v2.2.20
363
+ changelog_uri: https://github.com/kettle-dev/kettle-dev/blob/v2.2.20/CHANGELOG.md
344
364
  bug_tracker_uri: https://github.com/kettle-dev/kettle-dev/issues
345
- documentation_uri: https://www.rubydoc.info/gems/kettle-dev/2.2.19
365
+ documentation_uri: https://www.rubydoc.info/gems/kettle-dev/2.2.20
346
366
  funding_uri: https://github.com/sponsors/pboling
347
367
  wiki_uri: https://github.com/kettle-dev/kettle-dev/wiki
348
368
  news_uri: https://www.railsbling.com/tags/kettle-dev
metadata.gz.sig CHANGED
Binary file