kettle-dev 2.3.7 → 2.3.8

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: 4e582c50d05608c5e338caeac74f7c339fb2d66010b58c8cce836a78d8e4ded5
4
- data.tar.gz: e9ce2e0857dbf506017c8ddab46c5049ffc5003af5778a2e2954dd759cee8183
3
+ metadata.gz: fb500cfcc82a5cb325eb83ae9a5464e8333cce1c3ec03814b597063d41ec0e8a
4
+ data.tar.gz: 124a6c4ad4a3dd9172c131d6d5276a685d7e15f2be8f5fc51722c028fc83f136
5
5
  SHA512:
6
- metadata.gz: cea8e2be14321cc1e70534197cd2b01dea382a62f6de651ebfb35ccf1025acdfb0a11ee74c31e7c5c78dfe216ec51fdbe21a02f4f1358b6e8b526365b9a5f5af
7
- data.tar.gz: 47dada022728fd8414d252df3d01ecb992f18ccea69ed20ed8670dbd87ab3cf7f205bb488a48ab95039a4548a0b278c5e8aaf7099b7d94248539db5e7630c26b
6
+ metadata.gz: 1dd9b51023413bc9301d87dfc989dcec0844190b0cccfa90d8344bcd428403c70837f3972fdedc0cf2b4dee103af88923cc41ec43f80a869098a8a20da8a579d
7
+ data.tar.gz: 94084430abf75c714d2b5be26a25f1a9c1be691fc9c4f2fa760fc767e7ce1698e01ab71f5b978ec744f9981eeedc7b474a663b51504c6d842c0569ffe7e79cb0
checksums.yaml.gz.sig CHANGED
Binary file
data/CHANGELOG.md CHANGED
@@ -30,6 +30,38 @@ Please file a bug if you notice a violation of semantic versioning.
30
30
 
31
31
  ### Security
32
32
 
33
+ ## [2.3.8] - 2026-07-21
34
+
35
+ - TAG: [v2.3.8][2.3.8t]
36
+ - COVERAGE: 91.98% -- 4379/4761 lines in 36 files
37
+ - BRANCH COVERAGE: 73.91% -- 1711/2315 branches in 36 files
38
+ - 56.04% documented
39
+
40
+ ### Added
41
+
42
+ - `kettle-changelog --release-state` now reports an `ahead` value counting
43
+ commits from the latest release tag to the local default branch when
44
+ available.
45
+ - `kettle-release` now accepts `--skip-remotes` and
46
+ `K_RELEASE_SKIP_REMOTES` to exclude temporarily unavailable git remotes from
47
+ release fetch/push parity checks.
48
+ - `kettle-release` now creates or reuses an open GitHub pull request for
49
+ non-trunk release branches before waiting for GitHub Actions, so
50
+ pull-request-only workflows start after release branch pushes.
51
+
52
+ ### Changed
53
+
54
+ - kettle-jem-template-20260720-001 - Generated READMEs can now render
55
+ template-managed corporate sponsor logos from project or family config.
56
+ - kettle-jem-template-20260720-002 - Generated development Gemfiles now use the
57
+ released `tree_sitter_language_pack` gem 1.13.3 or newer by default.
58
+ - kettle-jem-template-20260720-003 - Generated StructuredMerge Git diff driver
59
+ config now uses the installed `smorg-rb` Ruby driver name.
60
+ - kettle-jem-template-20260720-004 - Generated multi-engine workflow files now
61
+ omit JRuby and TruffleRuby jobs when project config declares MRI-only engines.
62
+ - kettle-jem-template-20260720-005 - Generated README Support & Community rows
63
+ now include a RubyForum help badge.
64
+
33
65
  ## [2.3.7] - 2026-07-18
34
66
 
35
67
  - TAG: [v2.3.7][2.3.7t]
@@ -2536,7 +2568,9 @@ Please file a bug if you notice a violation of semantic versioning.
2536
2568
  - Selecting will run the selected workflow via `act`
2537
2569
  - This may move to its own gem in the future.
2538
2570
 
2539
- [Unreleased]: https://github.com/kettle-dev/kettle-dev/compare/v2.3.7...HEAD
2571
+ [Unreleased]: https://github.com/kettle-dev/kettle-dev/compare/v2.3.8...HEAD
2572
+ [2.3.8]: https://github.com/kettle-dev/kettle-dev/compare/v2.3.7...v2.3.8
2573
+ [2.3.8t]: https://github.com/kettle-dev/kettle-dev/releases/tag/v2.3.8
2540
2574
  [2.3.7]: https://github.com/kettle-dev/kettle-dev/compare/v2.3.6...v2.3.7
2541
2575
  [2.3.7t]: https://github.com/kettle-dev/kettle-dev/releases/tag/v2.3.7
2542
2576
  [2.3.6]: https://github.com/kettle-dev/kettle-dev/compare/v2.3.5...v2.3.6
data/README.md CHANGED
@@ -93,7 +93,7 @@ bin/kettle-release
93
93
  | Works with MRI Ruby 4 | [![Ruby current Compat][💎ruby-c-i]][🚎11-c-wf] [![Ruby HEAD Compat][💎ruby-headi]][🚎3-hd-wf]|
94
94
  | Works with MRI Ruby 3 | [![Ruby 3.0 Compat][💎ruby-3.0i]][🚎ruby-3.0-wf] [![Ruby 3.1 Compat][💎ruby-3.1i]][🚎ruby-3.1-wf] [![Ruby 3.2 Compat][💎ruby-3.2i]][🚎ruby-3.2-wf] [![Ruby 3.3 Compat][💎ruby-3.3i]][🚎ruby-3.3-wf] [![Ruby 3.4 Compat][💎ruby-3.4i]][🚎ruby-3.4-wf]|
95
95
  | Works with MRI Ruby 2 | [![Ruby 2.4 Compat][💎ruby-2.4i]][🚎ruby-2.4-wf] [![Ruby 2.5 Compat][💎ruby-2.5i]][🚎ruby-2.5-wf] [![Ruby 2.6 Compat][💎ruby-2.6i]][🚎ruby-2.6-wf] [![Ruby 2.7 Compat][💎ruby-2.7i]][🚎ruby-2.7-wf]|
96
- | Support & Community | [![Join Me on Daily.dev's RubyFriends][✉️ruby-friends-img]][✉️ruby-friends] [![Live Chat on Discord][✉️discord-invite-img-ftb]][✉️discord-invite] [![Get help from me on Upwork][👨🏼‍🏫expsup-upwork-img]][👨🏼‍🏫expsup-upwork] [![Get help from me on Codementor][👨🏼‍🏫expsup-codementor-img]][👨🏼‍🏫expsup-codementor] |
96
+ | Support & Community | [![Join Me on Daily.dev's RubyFriends][✉️ruby-friends-img]][✉️ruby-friends] [![Get help from RubyForum][✉️ruby-forum-img]][✉️ruby-forum] [![Live Chat on Discord][✉️discord-invite-img-ftb]][✉️discord-invite] [![Get help from me on Upwork][👨🏼‍🏫expsup-upwork-img]][👨🏼‍🏫expsup-upwork] [![Get help from me on Codementor][👨🏼‍🏫expsup-codementor-img]][👨🏼‍🏫expsup-codementor] |
97
97
  | Source | [![Source on GitLab.com][📜src-gl-img]][📜src-gl] [![Source on CodeBerg.org][📜src-cb-img]][📜src-cb] [![Source on Github.com][📜src-gh-img]][📜src-gh] [![The best SHA: dQw4w9WgXcQ!][🧮kloc-img]][🧮kloc] |
98
98
  | Documentation | [![Current release on RubyDoc.info][📜docs-cr-rd-img]][🚎yard-current] [![YARD on Galtzo.com][📜docs-head-rd-img]][🚎yard-head] [![Maintainer Blog][🚂maint-blog-img]][🚂maint-blog] [![GitLab Wiki][📜gl-wiki-img]][📜gl-wiki] [![GitHub Wiki][📜gh-wiki-img]][📜gh-wiki] |
99
99
  | Compliance | [![License: AGPL-3.0-only][📄license-img]][📄license] [![Apache license compatibility: Category X][📄license-compat-img]][📄license-compat] [![📄ilo-declaration-img]][📄ilo-declaration] [![Security Policy][🔐security-img]][🔐security] [![Contributor Covenant 2.1][🪇conduct-img]][🪇conduct] [![SemVer 2.0.0][📌semver-img]][📌semver] |
@@ -734,6 +734,8 @@ Thanks for RTFM. ☺️
734
734
  [✉️discord-invite-img-ftb]: https://img.shields.io/discord/1373797679469170758?style=for-the-badge&logo=discord
735
735
  [✉️ruby-friends-img]: https://img.shields.io/badge/daily.dev-%F0%9F%92%8E_Ruby_Friends-0A0A0A?style=for-the-badge&logo=dailydotdev&logoColor=white
736
736
  [✉️ruby-friends]: https://app.daily.dev/squads/rubyfriends
737
+ [✉️ruby-forum-img]: https://img.shields.io/badge/RubyForum-Help-CC342D?style=for-the-badge&logo=ruby&logoColor=white
738
+ [✉️ruby-forum]: https://www.rubyforum.org/c/help/8
737
739
 
738
740
  [✇bundle-group-pattern]: https://gist.github.com/pboling/4564780
739
741
  [⛳️gem-namespace]: https://github.com/kettle-dev/kettle-dev
@@ -887,7 +889,7 @@ Thanks for RTFM. ☺️
887
889
  [📌gitmoji]: https://gitmoji.dev
888
890
  [📌gitmoji-img]: https://img.shields.io/badge/gitmoji_commits-%20%F0%9F%98%9C%20%F0%9F%98%8D-34495e.svg?style=flat-square
889
891
  [🧮kloc]: https://www.youtube.com/watch?v=dQw4w9WgXcQ
890
- [🧮kloc-img]: https://img.shields.io/badge/KLOC-4.694-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
892
+ [🧮kloc-img]: https://img.shields.io/badge/KLOC-4.761-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
891
893
  [🔐security]: https://github.com/kettle-dev/kettle-dev/blob/main/SECURITY.md
892
894
  [🔐security-img]: https://img.shields.io/badge/security-policy-259D6C.svg?style=flat
893
895
  [📄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] [--skip-bundle-audit] [--skip-steps STEPS] [--ci-workflows WORKFLOWS] [start_step=<0-19>]
46
+ Usage: kettle-release [--version VERSION] [--local-ci] [--appraisal-update] [--skip-bundle-audit] [--skip-steps STEPS] [--ci-workflows WORKFLOWS] [--skip-remotes REMOTES] [start_step=<0-19>]
47
47
 
48
48
  Automates the release flow for a Ruby gem in the host project.
49
49
 
@@ -82,6 +82,7 @@ if ARGV.include?("-h") || ARGV.include?("--help")
82
82
  --appraisal-update # Use slower appraisal:update instead of default appraisal:generate
83
83
  --skip-bundle-audit # Skip bundle:audit/update during release rake checks
84
84
  --ci-workflows WORKFLOWS # Comma-separated workflow files or stems to monitor at CI step 10
85
+ --skip-remotes REMOTES # Comma-separated git remote names to exclude from release fetch/push parity
85
86
  --local-ci # Sensitive release mode: run act locally, publish before any git push,
86
87
  # create the git tag locally, then push commits and tags after publish
87
88
 
@@ -91,6 +92,7 @@ if ARGV.include?("-h") || ARGV.include?("--help")
91
92
  K_RELEASE_LOCAL_CI=ask|1|0 # Use 'act' locally before push; 'ask' prompts, '1' forces, default off
92
93
  K_RELEASE_LOCAL_CI_WORKFLOW # Name of workflow (without .yml) for local CI; defaults to locked_deps or first
93
94
  K_RELEASE_CI_WORKFLOWS # Comma-separated workflow files or stems to monitor at CI step 10
95
+ K_RELEASE_SKIP_REMOTES # Comma-separated git remote names to exclude from release fetch/push parity
94
96
  KETTLE_RELEASE_APPRAISAL_TASK=appraisal:update
95
97
  # Use slower appraisal:update instead of default appraisal:generate
96
98
  KETTLE_DEV_SKIP_BUNDLE_AUDIT=true
@@ -168,6 +170,27 @@ def extract_ci_workflows_arg!(argv)
168
170
  workflows
169
171
  end
170
172
 
173
+ def extract_skip_remotes_arg!(argv)
174
+ remotes = nil
175
+ if (idx = argv.index("--skip-remotes"))
176
+ remotes = argv[idx + 1]
177
+ if remotes.to_s.empty? || remotes.to_s.start_with?("-")
178
+ Kettle::Dev::ExitAdapter.abort("--skip-remotes requires a comma-separated remote list")
179
+ end
180
+ argv.slice!(idx, 2)
181
+ end
182
+ argv.delete_if do |arg|
183
+ if arg.start_with?("--skip-remotes=", "skip_remotes=")
184
+ remotes = arg.split("=", 2)[1]
185
+ Kettle::Dev::ExitAdapter.abort("--skip-remotes requires a comma-separated remote list") if remotes.to_s.empty?
186
+ true
187
+ else
188
+ false
189
+ end
190
+ end
191
+ remotes
192
+ end
193
+
171
194
  local_ci = ARGV.include?("--local-ci")
172
195
  appraisal_task = ARGV.delete("--appraisal-update") ? "appraisal:update" : nil
173
196
  skip_bundle_audit = !!ARGV.delete("--skip-bundle-audit")
@@ -176,9 +199,10 @@ start_step = start_step_arg ? start_step_arg.split("=", 2)[1].to_i : 0
176
199
  version_override = extract_version_arg!(ARGV)
177
200
  skip_steps = extract_skip_steps_arg!(ARGV)
178
201
  ci_workflows = extract_ci_workflows_arg!(ARGV)
202
+ skip_remotes = extract_skip_remotes_arg!(ARGV)
179
203
 
180
204
  begin
181
- Kettle::Dev::ReleaseCLI.new(start_step: start_step, local_ci: local_ci, version: version_override, appraisal_task: appraisal_task, skip_steps: skip_steps, skip_bundle_audit: skip_bundle_audit, ci_workflows: ci_workflows).run
205
+ Kettle::Dev::ReleaseCLI.new(start_step: start_step, local_ci: local_ci, version: version_override, appraisal_task: appraisal_task, skip_steps: skip_steps, skip_bundle_audit: skip_bundle_audit, ci_workflows: ci_workflows, skip_remotes: skip_remotes).run
182
206
  rescue LoadError => e
183
207
  warn("#{script_basename}: could not load dependency: #{e.class}: #{e.message}")
184
208
  warn(Array(e.backtrace).join("\n")) if ENV["DEBUG"]
@@ -150,6 +150,7 @@ module Kettle
150
150
  unless changelog_present
151
151
  latest_overall, latest_for_series, latest_for_major = latest_released_versions(gem_name, version)
152
152
  latest_target = latest_release_target(version, latest_overall, latest_for_series, latest_for_major)
153
+ ahead = commits_ahead_of_release(latest_target || latest_overall)
153
154
  return {
154
155
  root: @root,
155
156
  gem_name: gem_name,
@@ -164,7 +165,8 @@ module Kettle
164
165
  latest_released_overall: latest_overall,
165
166
  latest_released_for_current_major: latest_for_major,
166
167
  latest_released_for_current_series: latest_for_series,
167
- latest_release_target: latest_target
168
+ latest_release_target: latest_target,
169
+ ahead: ahead
168
170
  }
169
171
  end
170
172
 
@@ -176,6 +178,7 @@ module Kettle
176
178
  latest_overall, latest_for_series, latest_for_major = latest_released_versions(gem_name, release_lookup_version)
177
179
  latest_target = latest_release_target(release_lookup_version, latest_overall, latest_for_series, latest_for_major)
178
180
  prepared_release_pending = !!latest_changelog_version && latest_target != latest_changelog_version
181
+ ahead = commits_ahead_of_release(latest_target || latest_overall)
179
182
 
180
183
  {
181
184
  root: @root,
@@ -191,18 +194,20 @@ module Kettle
191
194
  latest_released_overall: latest_overall,
192
195
  latest_released_for_current_major: latest_for_major,
193
196
  latest_released_for_current_series: latest_for_series,
194
- latest_release_target: latest_target
197
+ latest_release_target: latest_target,
198
+ ahead: ahead
195
199
  }
196
200
  end
197
201
 
198
202
  def release_state_table(state = release_state)
199
203
  rows = [
200
- ["gem", "version.rb", "latest released", "latest changelog", "unreleased", "prepared", "pending"],
204
+ ["gem", "version.rb", "latest released", "latest changelog", "ahead", "unreleased", "prepared", "pending"],
201
205
  [
202
206
  state.fetch(:gem_name),
203
207
  state.fetch(:version),
204
208
  state.fetch(:latest_released) || "unknown",
205
209
  state.fetch(:latest_changelog_version) || "none",
210
+ state.fetch(:ahead, nil).nil? ? "unknown" : state.fetch(:ahead).to_s,
206
211
  yes_no(state.fetch(:unreleased_entries)),
207
212
  yes_no(state.fetch(:prepared_release_pending)),
208
213
  yes_no(state.fetch(:pending_release))
@@ -239,6 +244,33 @@ module Kettle
239
244
  value ? "yes" : "no"
240
245
  end
241
246
 
247
+ def commits_ahead_of_release(version)
248
+ tag = release_tag_for_version(version)
249
+ branch = default_branch_ref
250
+ return nil unless tag && branch
251
+
252
+ stdout, _stderr, status = Open3.capture3("git", "rev-list", "--count", "#{tag}..#{branch}", chdir: @root)
253
+ status.success? ? stdout.to_i : nil
254
+ end
255
+
256
+ def release_tag_for_version(version)
257
+ return nil if version.to_s.empty?
258
+
259
+ ["v#{version}", version.to_s].find { |tag| git_ref_exists?("refs/tags/#{tag}^{commit}") }
260
+ end
261
+
262
+ def default_branch_ref
263
+ stdout, _stderr, status = Open3.capture3("git", "symbolic-ref", "--quiet", "--short", "refs/remotes/origin/HEAD", chdir: @root)
264
+ return stdout.strip if status.success? && !stdout.strip.empty?
265
+
266
+ %w[main master HEAD].find { |ref| git_ref_exists?(ref) }
267
+ end
268
+
269
+ def git_ref_exists?(ref)
270
+ _stdout, _stderr, status = Open3.capture3("git", "rev-parse", "--verify", "--quiet", ref, chdir: @root)
271
+ status.success?
272
+ end
273
+
242
274
  def detect_plan(changelog, version)
243
275
  latest_overall = nil
244
276
  latest_for_series = nil
@@ -9,7 +9,7 @@ begin
9
9
  "KETTLE_JEM_QUIET" => "true",
10
10
  "KETTLE_JEM_DEBUG" => "false",
11
11
  "KETTLE_DEV_DEBUG" => "false",
12
- "SMORG_RB_DEBUG" => "false",
12
+ "STRUCTUREDMERGE_DEBUG" => "false",
13
13
  "DEBUG" => nil,
14
14
  "BUNDLE_QUIET" => "true",
15
15
  "BUNDLE_DEBUG" => "false",
@@ -22,7 +22,7 @@ module Kettle
22
22
  "KETTLE_JEM_QUIET" => "true",
23
23
  "KETTLE_JEM_DEBUG" => "false",
24
24
  "KETTLE_DEV_DEBUG" => "false",
25
- "SMORG_RB_DEBUG" => "false",
25
+ "STRUCTUREDMERGE_DEBUG" => "false",
26
26
  "DEBUG" => nil,
27
27
  "BUNDLE_QUIET" => "true",
28
28
  "BUNDLE_DEBUG" => "false",
@@ -108,13 +108,14 @@ module Kettle
108
108
 
109
109
  public
110
110
 
111
- def initialize(start_step: 0, local_ci: false, version: nil, appraisal_task: nil, skip_steps: nil, skip_bundle_audit: nil, ci_workflows: nil, **options)
111
+ def initialize(start_step: 0, local_ci: false, version: nil, appraisal_task: nil, skip_steps: nil, skip_bundle_audit: nil, ci_workflows: nil, skip_remotes: nil, **options)
112
112
  @root = Kettle::Dev::CIHelpers.project_root
113
113
  @git = Kettle::Dev::GitAdapter.new
114
114
  @start_step = (start_step || 0).to_i
115
115
  @start_step = 0 if @start_step < 0
116
116
  @skip_steps = normalize_skip_steps(skip_steps)
117
117
  @ci_workflows = normalize_ci_workflows(ci_workflows || ENV["K_RELEASE_CI_WORKFLOWS"])
118
+ @skip_remotes = normalize_skip_remotes(skip_remotes || ENV["K_RELEASE_SKIP_REMOTES"])
118
119
  @local_ci = !!local_ci
119
120
  @skip_bundle_audit = truthy_value?(skip_bundle_audit) || truthy_value?(ENV["KETTLE_DEV_SKIP_BUNDLE_AUDIT"])
120
121
  @version_override = Kettle::Dev::Versioning.normalize_explicit_version(version)
@@ -411,6 +412,14 @@ module Kettle
411
412
  workflows.map { |workflow| workflow.match?(/\.ya?ml\z/) ? workflow : "#{workflow}.yml" }.uniq
412
413
  end
413
414
 
415
+ def normalize_skip_remotes(value)
416
+ remotes = Array(value).flat_map { |part| part.to_s.split(",") }.map(&:strip).reject(&:empty?)
417
+ invalid = remotes.find { |remote| !remote.match?(/\A[A-Za-z0-9_.-]+\z/) }
418
+ abort("Invalid skip remotes value #{invalid.inspect}; use comma-separated git remote names.") if invalid
419
+
420
+ remotes.uniq
421
+ end
422
+
414
423
  private
415
424
 
416
425
  def local_ci?
@@ -719,10 +728,85 @@ module Kettle
719
728
  end
720
729
 
721
730
  def monitor_workflows_after_push!
731
+ ensure_github_pull_request_for_ci!
722
732
  # Use abort-on-failure CI monitor to match historical behavior and specs
723
733
  Kettle::Dev::CIMonitor.monitor_all!(restart_hint: "bundle exec kettle-release start_step=10", workflows: @ci_workflows)
724
734
  end
725
735
 
736
+ def ensure_github_pull_request_for_ci!
737
+ branch = current_branch
738
+ return if branch.to_s.empty?
739
+
740
+ trunk = detect_trunk_branch
741
+ return if branch == trunk
742
+
743
+ gh_remote = preferred_github_remote
744
+ return unless gh_remote
745
+
746
+ owner, repo = parse_github_owner_repo(remote_url(gh_remote))
747
+ return unless owner && repo
748
+
749
+ pull_request = github_pull_request_for_branch(owner: owner, repo: repo, branch: branch, base: trunk)
750
+ if pull_request
751
+ puts "GitHub pull request ##{pull_request.fetch("number")} already open for #{branch} -> #{trunk}: #{pull_request.fetch("url")}"
752
+ return
753
+ end
754
+
755
+ create_github_pull_request!(owner: owner, repo: repo, branch: branch, base: trunk)
756
+ end
757
+
758
+ def github_pull_request_for_branch(owner:, repo:, branch:, base:)
759
+ output = gh_output!(
760
+ "pr",
761
+ "list",
762
+ "--repo",
763
+ "#{owner}/#{repo}",
764
+ "--head",
765
+ branch,
766
+ "--base",
767
+ base,
768
+ "--state",
769
+ "open",
770
+ "--json",
771
+ "number,url",
772
+ "--limit",
773
+ "1"
774
+ )
775
+ Array(JSON.parse(output)).first
776
+ rescue JSON::ParserError => e
777
+ Kettle::Dev.debug_error(e, __method__)
778
+ abort("Could not parse GitHub pull request list from gh CLI.")
779
+ end
780
+
781
+ def create_github_pull_request!(owner:, repo:, branch:, base:)
782
+ output = gh_output!(
783
+ "pr",
784
+ "create",
785
+ "--repo",
786
+ "#{owner}/#{repo}",
787
+ "--head",
788
+ branch,
789
+ "--base",
790
+ base,
791
+ "--title",
792
+ "Release #{branch}",
793
+ "--body",
794
+ "Automated release validation PR for `#{branch}`.\n\nThis PR lets GitHub Actions run before kettle-release merges the branch into `#{base}`."
795
+ )
796
+ puts "Created GitHub pull request for #{branch} -> #{base}: #{output.strip}"
797
+ end
798
+
799
+ def gh_output!(*args)
800
+ stdout_str, stderr_str, status = Open3.capture3(self.class.send(:command_env), "gh", *args)
801
+ return stdout_str if status.success?
802
+
803
+ exit_code = status.respond_to?(:exitstatus) ? status.exitstatus : 1
804
+ diag = stderr_str.to_s.empty? ? "" : "\n--- STDERR ---\n#{stderr_str}".rstrip
805
+ abort("GitHub pull request setup failed: gh #{args.join(" ")} (exit #{exit_code})#{diag}")
806
+ rescue Errno::ENOENT
807
+ abort("GitHub pull request setup failed: gh CLI is required to create or find the release PR.")
808
+ end
809
+
726
810
  def run_cmd!(cmd)
727
811
  cmd = bundle_audit_skip_command(cmd)
728
812
  with_bundle_audit_skip_env do
@@ -925,7 +1009,7 @@ module Kettle
925
1009
  branch = current_branch
926
1010
  abort("Could not determine current branch to push.") unless branch
927
1011
 
928
- if has_remote?("all")
1012
+ if use_all_remote?
929
1013
  puts "$ git push all #{branch}"
930
1014
  success = @git.push("all", branch)
931
1015
  unless success
@@ -936,7 +1020,7 @@ module Kettle
936
1020
  end
937
1021
 
938
1022
  remotes = []
939
- remotes << "origin" if has_remote?("origin")
1023
+ remotes << "origin" if has_remote?("origin") && !skipped_remote?("origin")
940
1024
  remotes |= github_remote_candidates
941
1025
  remotes |= gitlab_remote_candidates
942
1026
  remotes |= codeberg_remote_candidates
@@ -967,13 +1051,13 @@ module Kettle
967
1051
  # 1) If a remote named "all" exists, push tags only to it.
968
1052
  # 2) Otherwise, if other remotes exist, push tags to each of them.
969
1053
  # 3) If no remotes are configured, push tags using default remote.
970
- if has_remote?("all")
1054
+ if use_all_remote?
971
1055
  ok = @git.push_tags("all")
972
1056
  warn("Push tags to 'all' reported failure.") unless ok
973
1057
  return
974
1058
  end
975
1059
 
976
- remotes = list_remotes
1060
+ remotes = active_remotes
977
1061
  remotes -= ["all"] if remotes
978
1062
  if remotes.nil? || remotes.empty?
979
1063
  ok = @git.push_tags(nil)
@@ -1021,15 +1105,15 @@ module Kettle
1021
1105
  end
1022
1106
 
1023
1107
  def github_remote_candidates
1024
- remotes_with_urls.select { |n, u| u.include?("github.com") }.keys
1108
+ active_remote_candidates(remotes_with_urls.select { |n, u| u.include?("github.com") }.keys)
1025
1109
  end
1026
1110
 
1027
1111
  def gitlab_remote_candidates
1028
- remotes_with_urls.select { |n, u| u.include?("gitlab.com") }.keys
1112
+ active_remote_candidates(remotes_with_urls.select { |n, u| u.include?("gitlab.com") }.keys)
1029
1113
  end
1030
1114
 
1031
1115
  def codeberg_remote_candidates
1032
- remotes_with_urls.select { |n, u| u.include?("codeberg.org") }.keys
1116
+ active_remote_candidates(remotes_with_urls.select { |n, u| u.include?("codeberg.org") }.keys)
1033
1117
  end
1034
1118
 
1035
1119
  def preferred_github_remote
@@ -1052,6 +1136,22 @@ module Kettle
1052
1136
  list_remotes.include?(name)
1053
1137
  end
1054
1138
 
1139
+ def skipped_remote?(name)
1140
+ @skip_remotes.include?(name)
1141
+ end
1142
+
1143
+ def active_remotes
1144
+ list_remotes.reject { |remote| skipped_remote?(remote) }
1145
+ end
1146
+
1147
+ def active_remote_candidates(candidates)
1148
+ candidates.reject { |remote| skipped_remote?(remote) }
1149
+ end
1150
+
1151
+ def use_all_remote?
1152
+ has_remote?("all") && @skip_remotes.empty?
1153
+ end
1154
+
1055
1155
  def remote_branch_exists?(remote, branch)
1056
1156
  _out, ok = git_output(["show-ref", "--verify", "--quiet", "refs/remotes/#{remote}/#{branch}"])
1057
1157
  ok
@@ -1076,9 +1176,15 @@ module Kettle
1076
1176
 
1077
1177
  def ensure_trunk_synced_before_push!(trunk, feature)
1078
1178
  if has_remote?("all")
1079
- puts "Remote 'all' detected. Fetching from all remotes and enforcing strict trunk parity..."
1080
- run_cmd!("git fetch --all")
1081
- remotes = list_remotes
1179
+ remotes = active_remotes
1180
+ skipped = list_remotes.select { |remote| skipped_remote?(remote) }
1181
+ puts "Remote 'all' detected. Fetching from active remotes and enforcing strict trunk parity..."
1182
+ puts "Skipping configured remotes: #{skipped.join(", ")}" unless skipped.empty?
1183
+ remotes.each do |remote|
1184
+ next if remote == "all"
1185
+
1186
+ run_cmd!("git fetch #{Shellwords.escape(remote)}")
1187
+ end
1082
1188
  missing_from = []
1083
1189
  remotes.each do |r|
1084
1190
  next if r == "all"
@@ -3,7 +3,7 @@
3
3
  module Kettle
4
4
  module Dev
5
5
  module Version
6
- VERSION = "2.3.7"
6
+ VERSION = "2.3.8"
7
7
  end
8
8
  VERSION = Version::VERSION # Traditional Constant Location
9
9
  end
data/sig/kettle/dev.rbs CHANGED
@@ -21,7 +21,6 @@ module Kettle
21
21
  def self.defaults: () -> Array[String]
22
22
  def self.register_default: (String | Symbol task_name) -> Array[String]
23
23
  VERSION: String
24
- # AST-driven merger for Appraisals files using Prism
25
24
  module AppraisalsAstMerger
26
25
  TRACKED_METHODS: Array[Symbol]
27
26
 
@@ -382,7 +381,7 @@ module Kettle
382
381
  end
383
382
 
384
383
  class ReleaseCLI
385
- def initialize: (?start_step: Integer, ?local_ci: bool, ?version: String?, ?appraisal_task: String?, ?skip_steps: untyped, ?skip_bundle_audit: bool?, ?ci_workflows: untyped, **untyped) -> void
384
+ def initialize: (?start_step: Integer, ?local_ci: bool, ?version: String?, ?appraisal_task: String?, ?skip_steps: untyped, ?skip_bundle_audit: bool?, ?ci_workflows: untyped, ?skip_remotes: untyped, **untyped) -> void
386
385
  def run: () -> void
387
386
 
388
387
  private
@@ -469,9 +468,6 @@ module Kettle
469
468
  end
470
469
 
471
470
  module Tasks
472
- # Namespace module for task-related classes and Rake task loaders.
473
- # Namespace module for task-related classes and Rake task loaders.
474
- # Namespace module for task-related classes and Rake task loaders.
475
471
  # Namespace module for task-related classes and Rake task loaders.
476
472
  module CITask
477
473
  def self.act: (?String opt) -> void
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.3.7
4
+ version: 2.3.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Peter H. Boling
@@ -159,40 +159,40 @@ dependencies:
159
159
  requirements:
160
160
  - - "~>"
161
161
  - !ruby/object:Gem::Version
162
- version: '3.1'
162
+ version: '3.2'
163
163
  - - ">="
164
164
  - !ruby/object:Gem::Version
165
- version: 3.1.4
165
+ version: 3.2.0
166
166
  type: :development
167
167
  prerelease: false
168
168
  version_requirements: !ruby/object:Gem::Requirement
169
169
  requirements:
170
170
  - - "~>"
171
171
  - !ruby/object:Gem::Version
172
- version: '3.1'
172
+ version: '3.2'
173
173
  - - ">="
174
174
  - !ruby/object:Gem::Version
175
- version: 3.1.4
175
+ version: 3.2.0
176
176
  - !ruby/object:Gem::Dependency
177
177
  name: turbo_tests2
178
178
  requirement: !ruby/object:Gem::Requirement
179
179
  requirements:
180
180
  - - "~>"
181
181
  - !ruby/object:Gem::Version
182
- version: '3.1'
182
+ version: '3.2'
183
183
  - - ">="
184
184
  - !ruby/object:Gem::Version
185
- version: 3.1.14
185
+ version: 3.2.0
186
186
  type: :development
187
187
  prerelease: false
188
188
  version_requirements: !ruby/object:Gem::Requirement
189
189
  requirements:
190
190
  - - "~>"
191
191
  - !ruby/object:Gem::Version
192
- version: '3.1'
192
+ version: '3.2'
193
193
  - - ">="
194
194
  - !ruby/object:Gem::Version
195
- version: 3.1.14
195
+ version: 3.2.0
196
196
  - !ruby/object:Gem::Dependency
197
197
  name: stone_checksums
198
198
  requirement: !ruby/object:Gem::Requirement
@@ -313,10 +313,10 @@ licenses:
313
313
  - AGPL-3.0-only
314
314
  metadata:
315
315
  homepage_uri: https://kettle-dev.galtzo.com
316
- source_code_uri: https://github.com/kettle-dev/kettle-dev/tree/v2.3.7
317
- changelog_uri: https://github.com/kettle-dev/kettle-dev/blob/v2.3.7/CHANGELOG.md
316
+ source_code_uri: https://github.com/kettle-dev/kettle-dev/tree/v2.3.8
317
+ changelog_uri: https://github.com/kettle-dev/kettle-dev/blob/v2.3.8/CHANGELOG.md
318
318
  bug_tracker_uri: https://github.com/kettle-dev/kettle-dev/issues
319
- documentation_uri: https://www.rubydoc.info/gems/kettle-dev/2.3.7
319
+ documentation_uri: https://www.rubydoc.info/gems/kettle-dev/2.3.8
320
320
  funding_uri: https://github.com/sponsors/pboling
321
321
  wiki_uri: https://github.com/kettle-dev/kettle-dev/wiki
322
322
  news_uri: https://www.railsbling.com/tags/kettle-dev
metadata.gz.sig CHANGED
Binary file