kettle-dev 1.1.11 → 1.1.13

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: 84d3d2f6671ddba20e5cc07cb1b56051e9b6acaf6d3cb1157b8fc2bb3c76bb76
4
- data.tar.gz: f962f26bb64d242f670899b45831a66036c4ff1db497702f0446669374f1f580
3
+ metadata.gz: 6b2236329716c5ee01b5cf04dd99ee6b0595a7fa95a76c8ab959c04130aaada7
4
+ data.tar.gz: 9ea676b1fd79e9247c56bf3bb5c584870e572ed6ed462ba399227c3e03047ea5
5
5
  SHA512:
6
- metadata.gz: a6f1fc475a0aa076e614c7c612ba2d6fc06327de919af2c29c01173dc6a7191c25a57a0d8392b2847c75931804b60a5360bcd610c4acac8a42c6ae835f36275f
7
- data.tar.gz: d6163488e6f32e9633074faed8d8243f85edf29766871797678e84393a2d2dd575cad9c16ceccc7074a2e149256f2ea5861e4d739ac61381cd34a2a0e488556b
6
+ metadata.gz: f2d83150965bedec8642543e4c818447203c0c4d5de60d41a41b2bf0672995a1e80c8501c463d448746e9d76622553df389a6fecf1a1313358c1c203c2ea29a0
7
+ data.tar.gz: ba80dea5b32c6ae569e63ef2f5134683d6867c0857ed988c2bfe243076b02728c9ca12a5ae874cd94459794af63163ef215753243256c7d6157d91251f19e8ca
checksums.yaml.gz.sig CHANGED
Binary file
data/.envrc CHANGED
@@ -21,7 +21,7 @@ export K_SOUP_COV_DO=true # Means you want code coverage
21
21
  export K_SOUP_COV_COMMAND_NAME="Test Coverage"
22
22
  # Available formats are html, xml, rcov, lcov, json, tty
23
23
  export K_SOUP_COV_FORMATTERS="html,xml,rcov,lcov,json,tty"
24
- export K_SOUP_COV_MIN_BRANCH=81 # Means you want to enforce X% branch coverage
24
+ export K_SOUP_COV_MIN_BRANCH=80 # Means you want to enforce X% branch coverage
25
25
  export K_SOUP_COV_MIN_LINE=96 # Means you want to enforce X% line coverage
26
26
  export K_SOUP_COV_MIN_HARD=true # Means you want the build to fail if the coverage thresholds are not met
27
27
  export K_SOUP_COV_MULTI_FORMATTERS=true
@@ -0,0 +1,30 @@
1
+ RSpec/MultipleExpectations:
2
+ Enabled: false
3
+
4
+ RSpec/NamedSubject:
5
+ Enabled: false
6
+
7
+ RSpec/ExampleLength:
8
+ Enabled: false
9
+
10
+ RSpec/VerifiedDoubles:
11
+ Enabled: false
12
+
13
+ RSpec/MessageSpies:
14
+ Enabled: false
15
+
16
+ RSpec/InstanceVariable:
17
+ Enabled: false
18
+
19
+ RSpec/NestedGroups:
20
+ Enabled: false
21
+
22
+ RSpec/ExpectInHook:
23
+ Enabled: false
24
+
25
+ RSpec/DescribeClass:
26
+ Exclude:
27
+ - 'spec/examples/*'
28
+
29
+ RSpec/MultipleMemoizedHelpers:
30
+ Enabled: false
data/Appraisals CHANGED
@@ -143,7 +143,7 @@ end
143
143
  # Only run linter on the latest version of Ruby (but, in support of oldest supported Ruby version)
144
144
  appraise "style" do
145
145
  eval_gemfile "modular/style.gemfile"
146
- eval_gemfile "modular/x_std_libs/r3/libs.gemfile"
146
+ eval_gemfile "modular/x_std_libs.gemfile"
147
147
  # Dependencies injected by the kettle-dev-setup script & kettle:dev:install rake task
148
148
  # eval_gemfile "modular/injected.gemfile"
149
149
  end
data/CHANGELOG.md CHANGED
@@ -24,6 +24,24 @@ Please file a bug if you notice a violation of semantic versioning.
24
24
  ### Fixed
25
25
  ### Security
26
26
 
27
+ ## [1.1.13] - 2025-09-09
28
+ - TAG: [v1.1.13][1.1.13t]
29
+ - COVERAGE: 96.29% -- 3479/3613 lines in 25 files
30
+ - BRANCH COVERAGE: 80.96% -- 1424/1759 branches in 25 files
31
+ - 76.88% documented
32
+ ### Fixed
33
+ - include .rubocop_rspec.yml during install / template task's file copy
34
+ - kettle-dev-setup now honors `--force` option
35
+
36
+ ## [1.1.12] - 2025-09-09
37
+ - TAG: [v1.1.12][1.1.12t]
38
+ - COVERAGE: 94.84% -- 3422/3608 lines in 25 files
39
+ - BRANCH COVERAGE: 78.97% -- 1386/1755 branches in 25 files
40
+ - 76.88% documented
41
+ ### Changed
42
+ - improve Gemfile updates during kettle-dev-setup
43
+ - git origin-based funding_org derivation during setup
44
+
27
45
  ## [1.1.11] - 2025-09-08
28
46
  - TAG: [v1.1.11][1.1.11t]
29
47
  - COVERAGE: 96.56% -- 3396/3517 lines in 24 files
@@ -581,7 +599,11 @@ Please file a bug if you notice a violation of semantic versioning.
581
599
  - Selecting will run the selected workflow via `act`
582
600
  - This may move to its own gem in the future.
583
601
 
584
- [Unreleased]: https://github.com/kettle-rb/kettle-dev/compare/v1.1.11...HEAD
602
+ [Unreleased]: https://github.com/kettle-rb/kettle-dev/compare/v1.1.13...HEAD
603
+ [1.1.13]: https://github.com/kettle-rb/kettle-dev/compare/v1.1.12...v1.1.13
604
+ [1.1.13t]: https://github.com/kettle-rb/kettle-dev/releases/tag/v1.1.13
605
+ [1.1.12]: https://github.com/kettle-rb/kettle-dev/compare/v1.1.11...v1.1.12
606
+ [1.1.12t]: https://github.com/kettle-rb/kettle-dev/releases/tag/v1.1.12
585
607
  [1.1.11]: https://github.com/kettle-rb/kettle-dev/compare/v1.1.10...v1.1.11
586
608
  [1.1.11t]: https://github.com/kettle-rb/kettle-dev/releases/tag/v1.1.11
587
609
  [1.1.10]: https://github.com/kettle-rb/kettle-dev/compare/v1.1.9...v1.1.10
data/README.md CHANGED
@@ -907,7 +907,7 @@ Thanks for RTFM. ☺️
907
907
  [📌gitmoji]:https://gitmoji.dev
908
908
  [📌gitmoji-img]:https://img.shields.io/badge/gitmoji_commits-%20%F0%9F%98%9C%20%F0%9F%98%8D-34495e.svg?style=flat-square
909
909
  [🧮kloc]: https://www.youtube.com/watch?v=dQw4w9WgXcQ
910
- [🧮kloc-img]: https://img.shields.io/badge/KLOC-3.517-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
910
+ [🧮kloc-img]: https://img.shields.io/badge/KLOC-3.613-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
911
911
  [🔐security]: SECURITY.md
912
912
  [🔐security-img]: https://img.shields.io/badge/security-policy-259D6C.svg?style=flat
913
913
  [📄copyright-notice-explainer]: https://opensource.stackexchange.com/questions/5778/why-do-licenses-such-as-the-mit-license-specify-a-single-year
data/README.md.example CHANGED
@@ -507,7 +507,7 @@ Thanks for RTFM. ☺️
507
507
  [📌gitmoji]:https://gitmoji.dev
508
508
  [📌gitmoji-img]:https://img.shields.io/badge/gitmoji_commits-%20%F0%9F%98%9C%20%F0%9F%98%8D-34495e.svg?style=flat-square
509
509
  [🧮kloc]: https://www.youtube.com/watch?v=dQw4w9WgXcQ
510
- [🧮kloc-img]: https://img.shields.io/badge/KLOC-3.517-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
510
+ [🧮kloc-img]: https://img.shields.io/badge/KLOC-3.613-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
511
511
  [🔐security]: SECURITY.md
512
512
  [🔐security-img]: https://img.shields.io/badge/security-policy-259D6C.svg?style=flat
513
513
  [📄copyright-notice-explainer]: https://opensource.stackexchange.com/questions/5778/why-do-licenses-such-as-the-mit-license-specify-a-single-year
data/Rakefile.example CHANGED
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # kettle-dev Rakefile v1.1.11 - 2025-09-08
3
+ # kettle-dev Rakefile v1.1.13 - 2025-09-09
4
4
  # Ruby 2.3 (Safe Navigation) or higher required
5
5
  #
6
6
  # MIT License (see License.txt)
@@ -2,7 +2,7 @@
2
2
  # visibility and discoverability on RubyGems.org.
3
3
  # However, this gem sits underneath all my other gems, and also "depends on" many of them.
4
4
  # So instead of depending on them directly it injects them into the other gem's gemspec on install.
5
- # This gem its injected dev dependencies, will install on Ruby down to 2.3.x.
5
+ # This gem, and its injected dev dependencies, will install on Ruby down to 2.3.x.
6
6
  # This gem does not inject runtime dependencies.
7
7
  # Thus, dev dependencies injected into gemspecs must have
8
8
  #
@@ -106,6 +106,7 @@ module Kettle
106
106
  content = File.read(path)
107
107
  m = content.match(/VERSION\s*=\s*(["'])([^"']+)\1/)
108
108
  next unless m
109
+
109
110
  m[2]
110
111
  end.compact
111
112
  abort("VERSION constant not found in #{@root}/lib/**/version.rb") if versions.none?
@@ -117,6 +118,7 @@ module Kettle
117
118
  lines = content.lines
118
119
  start_i = lines.index { |l| l.start_with?("## [Unreleased]") }
119
120
  return [nil, nil, nil] unless start_i
121
+
120
122
  # Find the next version heading after Unreleased
121
123
  next_i = (start_i + 1)
122
124
  while next_i < lines.length && !lines[next_i].start_with?("## [")
@@ -133,6 +135,7 @@ module Kettle
133
135
  # after_text begins with the first released section following Unreleased
134
136
  m = after_text.match(/^## \[(\d+\.\d+\.\d+)\]/)
135
137
  return m[1] if m
138
+
136
139
  nil
137
140
  end
138
141
 
@@ -199,8 +202,10 @@ module Kettle
199
202
  branches = h["branches"] || []
200
203
  branches.each do |b|
201
204
  next unless b.is_a?(Hash)
205
+
202
206
  cov = b["coverage"]
203
207
  next unless cov.is_a?(Numeric)
208
+
204
209
  total_branches += 1
205
210
  covered_branches += 1 if cov > 0
206
211
  end
@@ -35,6 +35,7 @@ module Kettle
35
35
  def repo_info
36
36
  out, status = Open3.capture2("git", "config", "--get", "remote.origin.url")
37
37
  return unless status.success?
38
+
38
39
  url = out.strip
39
40
  if url =~ %r{git@github.com:(.+?)/(.+?)(\.git)?$}
40
41
  [Regexp.last_match(1), Regexp.last_match(2).sub(/\.git\z/, "")]
@@ -88,8 +89,10 @@ module Kettle
88
89
  # @return [Hash{String=>String,Integer}, nil] minimal run info or nil on error/none
89
90
  def latest_run(owner:, repo:, workflow_file:, branch: nil, token: default_token)
90
91
  return unless owner && repo
92
+
91
93
  b = branch || current_branch
92
94
  return unless b
95
+
93
96
  # Scope to the exact commit SHA when available to avoid picking up a previous run on the same branch.
94
97
  sha_out, status = Open3.capture2("git", "rev-parse", "HEAD")
95
98
  sha = status.success? ? sha_out.strip : nil
@@ -100,6 +103,7 @@ module Kettle
100
103
  req["Authorization"] = "token #{token}" if token && !token.empty?
101
104
  res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
102
105
  return unless res.is_a?(Net::HTTPSuccess)
106
+
103
107
  data = JSON.parse(res.body)
104
108
  runs = Array(data["workflow_runs"]) || []
105
109
  # Try to match by head_sha first; fall back to first run (branch-scoped) if none matches yet.
@@ -109,6 +113,7 @@ module Kettle
109
113
  runs.first
110
114
  end
111
115
  return unless run
116
+
112
117
  {
113
118
  "status" => run["status"],
114
119
  "conclusion" => run["conclusion"],
@@ -154,6 +159,7 @@ module Kettle
154
159
  def repo_info_gitlab
155
160
  url = origin_url
156
161
  return unless url
162
+
157
163
  if url =~ %r{git@gitlab.com:(.+?)/(.+?)(\.git)?$}
158
164
  [Regexp.last_match(1), Regexp.last_match(2).sub(/\.git\z/, "")]
159
165
  elsif url =~ %r{https://gitlab.com/(.+?)/(.+?)(\.git)?$}
@@ -176,8 +182,10 @@ module Kettle
176
182
  # @return [Hash{String=>String,Integer}, nil]
177
183
  def gitlab_latest_pipeline(owner:, repo:, branch: nil, host: "gitlab.com", token: default_gitlab_token)
178
184
  return unless owner && repo
185
+
179
186
  b = branch || current_branch
180
187
  return unless b
188
+
181
189
  project = URI.encode_www_form_component("#{owner}/#{repo}")
182
190
  uri = URI("https://#{host}/api/v4/projects/#{project}/pipelines?ref=#{URI.encode_www_form_component(b)}&per_page=1")
183
191
  req = Net::HTTP::Get.new(uri)
@@ -185,10 +193,13 @@ module Kettle
185
193
  req["PRIVATE-TOKEN"] = token if token && !token.empty?
186
194
  res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
187
195
  return unless res.is_a?(Net::HTTPSuccess)
196
+
188
197
  data = JSON.parse(res.body)
189
198
  return unless data.is_a?(Array)
199
+
190
200
  pipe = data.first
191
201
  return unless pipe.is_a?(Hash)
202
+
192
203
  # Attempt to enrich with failure_reason by querying the single pipeline endpoint
193
204
  begin
194
205
  if pipe["id"]
@@ -36,6 +36,7 @@ module Kettle
36
36
  # Some APIs report only a final state string like "success"/"failed"
37
37
  return "✅" if conclusion.to_s == "success" || status.to_s == "success"
38
38
  return "🍅" if conclusion.to_s == "failure" || status.to_s == "failed"
39
+
39
40
  "⏳️"
40
41
  end
41
42
  end
@@ -100,7 +101,7 @@ module Kettle
100
101
  emoji = status_emoji(it[:status], it[:conclusion])
101
102
  details = [it[:status], it[:conclusion]].compact.join("/")
102
103
  wf = it[:workflow]
103
- puts " - #{wf}: #{emoji} (#{details}) #{it[:url] ? "-> #{it[:url]}" : ""}"
104
+ puts " - #{wf}: #{emoji} (#{details}) #{"-> #{it[:url]}" if it[:url]}"
104
105
  all_ok &&= (it[:conclusion] == "success")
105
106
  end
106
107
  end
@@ -113,7 +114,7 @@ module Kettle
113
114
  end
114
115
  emoji = status_emoji(gl[:status], status)
115
116
  details = gl[:status].to_s
116
- puts "GitLab Pipeline: #{emoji} (#{details}) #{gl[:url] ? "-> #{gl[:url]}" : ""}"
117
+ puts "GitLab Pipeline: #{emoji} (#{details}) #{"-> #{gl[:url]}" if gl[:url]}"
117
118
  all_ok &&= (gl[:status] != "failed")
118
119
  end
119
120
  all_ok
@@ -209,6 +210,7 @@ module Kettle
209
210
  end
210
211
  end
211
212
  break if results.size == total
213
+
212
214
  idx = (idx + 1) % total
213
215
  sleep(1)
214
216
  end
@@ -313,6 +315,7 @@ module Kettle
313
315
  end
314
316
  end
315
317
  break if passed.size == total
318
+
316
319
  idx = (idx + 1) % total
317
320
  sleep(1)
318
321
  end
@@ -395,15 +398,18 @@ module Kettle
395
398
  def preferred_github_remote
396
399
  cands = github_remote_candidates
397
400
  return if cands.empty?
401
+
398
402
  explicit = cands.find { |n| n == "github" } || cands.find { |n| n == "gh" }
399
403
  return explicit if explicit
400
404
  return "origin" if cands.include?("origin")
405
+
401
406
  cands.first
402
407
  end
403
408
  module_function :preferred_github_remote
404
409
 
405
410
  def parse_github_owner_repo(url)
406
411
  return [nil, nil] unless url
412
+
407
413
  if url =~ %r{git@github.com:(.+?)/(.+?)(\.git)?$}
408
414
  [Regexp.last_match(1), Regexp.last_match(2).sub(/\.git\z/, "")]
409
415
  elsif url =~ %r{https://github.com/(.+?)/(.+?)(\.git)?$}
@@ -18,6 +18,7 @@ module Kettle
18
18
  validate = ENV.fetch("GIT_HOOK_BRANCH_VALIDATE", "false")
19
19
  branch_rule_type = (!validate.casecmp("false").zero? && validate) || nil
20
20
  return unless branch_rule_type
21
+
21
22
  branch_rule = BRANCH_RULES[branch_rule_type]
22
23
  return unless branch_rule
23
24
 
@@ -69,6 +69,7 @@ module Kettle
69
69
  %i[github gitlab codeberg].each do |forge|
70
70
  r = names[forge]
71
71
  next unless r && r != names[:origin]
72
+
72
73
  git.fetch(r)
73
74
  end
74
75
  show_status!(git, names, branch)
@@ -106,8 +107,10 @@ module Kettle
106
107
  def detect_default_branch!(git)
107
108
  _out, ok = git.capture(["rev-parse", "--verify", "origin/main"])
108
109
  return "main" if ok
110
+
109
111
  _out2, ok2 = git.capture(["rev-parse", "--verify", "origin/master"])
110
112
  return "master" if ok2
113
+
111
114
  # Default to main if neither verifies
112
115
  "main"
113
116
  end
@@ -125,6 +128,7 @@ module Kettle
125
128
  next unless remote
126
129
  next if remote == names[:origin]
127
130
  next unless existing.include?(remote)
131
+
128
132
  ref = "#{remote}/#{branch}"
129
133
  out, ok = git.capture(["rev-list", "--left-right", "--count", "#{base}...#{ref}"])
130
134
  if ok && !out.to_s.strip.empty?
@@ -232,6 +236,7 @@ module Kettle
232
236
  if org && repo
233
237
  return [org, repo]
234
238
  end
239
+
235
240
  # Try to infer from any existing remote url
236
241
  urls = git.remotes_with_urls
237
242
  sample = urls["origin"] || urls.values.first
@@ -252,7 +257,8 @@ module Kettle
252
257
 
253
258
  def prompt(label, default: nil)
254
259
  return default if @opts[:force]
255
- print("#{label}#{default ? " [#{default}]" : ""}: ")
260
+
261
+ print("#{label}#{" [#{default}]" if default}: ")
256
262
  ans = $stdin.gets&.strip
257
263
  ans = nil if ans == ""
258
264
  ans || default || abort!("#{label} is required")
@@ -341,6 +347,7 @@ module Kettle
341
347
  codeberg: names[:codeberg],
342
348
  }.each do |forge, remote_name|
343
349
  next unless remote_name
350
+
344
351
  ok = git.fetch(remote_name)
345
352
  results[forge] = !!ok
346
353
  say("Fetched from #{forge} (remote: #{remote_name}) => #{ok ? "OK" : "FAILED"}")
@@ -352,6 +359,7 @@ module Kettle
352
359
  def update_readme_federation_status!(org, repo, results)
353
360
  readme_path = File.join(Dir.pwd, "README.md")
354
361
  return unless File.exist?(readme_path)
362
+
355
363
  content = File.read(readme_path)
356
364
  # Determine if all succeeded
357
365
  forges = [:github, :gitlab, :codeberg]
@@ -376,6 +384,7 @@ module Kettle
376
384
  say("\nSome forges are not yet available. Use these import links to create mirrors:")
377
385
  [:github, :gitlab, :codeberg].each do |forge|
378
386
  next if results[forge]
387
+
379
388
  say(" - #{forge.capitalize} import: #{FORGE_MIGRATION_TOOLS[forge]}")
380
389
  end
381
390
  end
@@ -144,6 +144,7 @@ module Kettle
144
144
  else
145
145
  out, status = Open3.capture2("git", "remote", "-v")
146
146
  return {} unless status.success?
147
+
147
148
  urls = {}
148
149
  out.each_line do |line|
149
150
  # Example: origin https://github.com/me/repo.git (fetch)
@@ -32,6 +32,7 @@ module Kettle
32
32
  def local_hooks_dir
33
33
  top = git_toplevel
34
34
  return unless top && !top.empty?
35
+
35
36
  File.join(top, ".git-hooks")
36
37
  end
37
38
 
@@ -37,6 +37,7 @@ module Kettle
37
37
  def readline(*args)
38
38
  line = gets(*args)
39
39
  raise EOFError, "end of file reached" if line.nil?
40
+
40
41
  line
41
42
  end
42
43
  end
@@ -0,0 +1,110 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kettle
4
+ module Dev
5
+ # Utilities for copying modular Gemfiles and related directories
6
+ # in a DRY fashion. Used by both the template rake task and the
7
+ # setup CLI to ensure gemfiles/modular/* are present before use.
8
+ module ModularGemfiles
9
+ MODULAR_GEMFILE_DIR = "gemfiles/modular"
10
+
11
+ module_function
12
+
13
+ # Copy the modular gemfiles and nested directories from the gem
14
+ # checkout into the target project, prompting where appropriate
15
+ # via the provided helpers.
16
+ #
17
+ # @param helpers [Kettle::Dev::TemplateHelpers] helper API
18
+ # @param project_root [String] destination project root
19
+ # @param gem_checkout_root [String] kettle-dev checkout root (source)
20
+ # @param min_ruby [Gem::Version, nil] minimum Ruby version (for style.gemfile tuning)
21
+ # @return [void]
22
+ def sync!(helpers:, project_root:, gem_checkout_root:, min_ruby: nil)
23
+ # 4a) gemfiles/modular/*.gemfile except style.gemfile (handled below)
24
+ modular_gemfiles = %w[
25
+ coverage
26
+ debug
27
+ documentation
28
+ injected
29
+ optional
30
+ runtime_heads
31
+ x_std_libs
32
+ ]
33
+ modular_gemfiles.each do |base|
34
+ modular_gemfile = "#{base}.gemfile"
35
+ src = helpers.prefer_example(File.join(gem_checkout_root, MODULAR_GEMFILE_DIR, modular_gemfile))
36
+ dest = File.join(project_root, MODULAR_GEMFILE_DIR, modular_gemfile)
37
+ helpers.copy_file_with_prompt(src, dest, allow_create: true, allow_replace: true)
38
+ end
39
+
40
+ # 4b) gemfiles/modular/style.gemfile with dynamic rubocop constraints
41
+ modular_gemfile = "style.gemfile"
42
+ src = helpers.prefer_example(File.join(gem_checkout_root, MODULAR_GEMFILE_DIR, modular_gemfile))
43
+ dest = File.join(project_root, MODULAR_GEMFILE_DIR, modular_gemfile)
44
+ if File.basename(src).sub(/\.example\z/, "") == "style.gemfile"
45
+ helpers.copy_file_with_prompt(src, dest, allow_create: true, allow_replace: true) do |content|
46
+ # Adjust rubocop-lts constraint based on min_ruby
47
+ version_map = [
48
+ [Gem::Version.new("1.8"), "~> 0.1"],
49
+ [Gem::Version.new("1.9"), "~> 2.0"],
50
+ [Gem::Version.new("2.0"), "~> 4.0"],
51
+ [Gem::Version.new("2.1"), "~> 6.0"],
52
+ [Gem::Version.new("2.2"), "~> 8.0"],
53
+ [Gem::Version.new("2.3"), "~> 10.0"],
54
+ [Gem::Version.new("2.4"), "~> 12.0"],
55
+ [Gem::Version.new("2.5"), "~> 14.0"],
56
+ [Gem::Version.new("2.6"), "~> 16.0"],
57
+ [Gem::Version.new("2.7"), "~> 18.0"],
58
+ [Gem::Version.new("3.0"), "~> 20.0"],
59
+ [Gem::Version.new("3.1"), "~> 22.0"],
60
+ [Gem::Version.new("3.2"), "~> 24.0"],
61
+ [Gem::Version.new("3.3"), "~> 26.0"],
62
+ [Gem::Version.new("3.4"), "~> 28.0"],
63
+ ]
64
+ new_constraint = nil
65
+ rubocop_ruby_gem_version = nil
66
+ ruby1_8 = version_map.first
67
+ begin
68
+ if min_ruby
69
+ version_map.reverse_each do |min, req|
70
+ if min_ruby >= min
71
+ new_constraint = req
72
+ rubocop_ruby_gem_version = min.segments.join("_")
73
+ break
74
+ end
75
+ end
76
+ end
77
+ if !new_constraint || !rubocop_ruby_gem_version
78
+ # A gem with no declared minimum ruby is effectively >= 1.8.7
79
+ new_constraint = ruby1_8[1]
80
+ rubocop_ruby_gem_version = ruby1_8[0].segments.join("_")
81
+ end
82
+ rescue StandardError => e
83
+ Kettle::Dev.debug_error(e, __method__) if defined?(Kettle::Dev.debug_error)
84
+ # ignore, use default
85
+ ensure
86
+ new_constraint ||= ruby1_8[1]
87
+ rubocop_ruby_gem_version ||= ruby1_8[0].segments.join("_")
88
+ end
89
+ if new_constraint && rubocop_ruby_gem_version
90
+ token = "{RUBOCOP|LTS|CONSTRAINT}"
91
+ content.gsub!(token, new_constraint) if content.include?(token)
92
+ token = "{RUBOCOP|RUBY|GEM}"
93
+ content.gsub!(token, "rubocop-ruby#{rubocop_ruby_gem_version}") if content.include?(token)
94
+ end
95
+ content
96
+ end
97
+ else
98
+ helpers.copy_file_with_prompt(src, dest, allow_create: true, allow_replace: true)
99
+ end
100
+
101
+ # 4c) Copy modular directories with nested/versioned files
102
+ %w[erb mutex_m stringio x_std_libs].each do |dir|
103
+ src_dir = File.join(gem_checkout_root, MODULAR_GEMFILE_DIR, dir)
104
+ dest_dir = File.join(project_root, MODULAR_GEMFILE_DIR, dir)
105
+ helpers.copy_dir_with_prompt(src_dir, dest_dir)
106
+ end
107
+ end
108
+ end
109
+ end
110
+ end
@@ -71,6 +71,7 @@ module Kettle
71
71
  when Net::HTTPRedirection
72
72
  location = response["location"]
73
73
  return false unless location
74
+
74
75
  new_uri = parse_http_uri(location)
75
76
  new_uri = uri + location if new_uri.relative?
76
77
  head_ok?(new_uri.to_s, limit: limit - 1, timeout: timeout)
@@ -155,6 +156,7 @@ module Kettle
155
156
 
156
157
  start = @check_num
157
158
  raise ArgumentError, "check_num must be >= 1" if start < 1
159
+
158
160
  begin_idx = start - 1
159
161
  checks[begin_idx..-1].each_with_index do |check, i|
160
162
  idx = begin_idx + i + 1
@@ -119,6 +119,7 @@ module Kettle
119
119
  def readme_osc_tag
120
120
  env = ENV["KETTLE_DEV_BACKER_README_OSC_TAG"].to_s
121
121
  return env unless env.strip.empty?
122
+
122
123
  if File.file?(OC_YML_PATH)
123
124
  begin
124
125
  yml = YAML.safe_load(File.read(OC_YML_PATH))
@@ -149,6 +150,7 @@ module Kettle
149
150
  def resolve_handle
150
151
  env = ENV["OPENCOLLECTIVE_HANDLE"]
151
152
  return env unless env.nil? || env.strip.empty?
153
+
152
154
  if File.file?(OC_YML_PATH)
153
155
  yml = YAML.safe_load(File.read(OC_YML_PATH))
154
156
  handle = yml.is_a?(Hash) ? yml["collective"] || yml[:collective] : nil
@@ -167,6 +169,7 @@ module Kettle
167
169
  conn.request(req)
168
170
  end
169
171
  return [] unless response.is_a?(Net::HTTPSuccess)
172
+
170
173
  parsed = JSON.parse(response.body)
171
174
  Array(parsed).map do |h|
172
175
  Backer.new(
@@ -186,6 +189,7 @@ module Kettle
186
189
 
187
190
  def generate_markdown(members, empty_message:, default_name:)
188
191
  return empty_message if members.nil? || members.empty?
192
+
189
193
  members.map do |m|
190
194
  image_url = m.image || DEFAULT_AVATAR
191
195
  link = m.website || m.profile || "#"
@@ -196,14 +200,17 @@ module Kettle
196
200
 
197
201
  def replace_between_tags(content, start_tag, end_tag, new_content)
198
202
  return :not_found if start_tag == :not_found || end_tag == :not_found
203
+
199
204
  start_index = content.index(start_tag)
200
205
  end_index = content.index(end_tag)
201
206
  return :not_found if start_index.nil? || end_index.nil? || end_index < start_index
207
+
202
208
  before = content[0..start_index + start_tag.length - 1]
203
209
  after = content[end_index..-1]
204
210
  replacement = "#{start_tag}\n#{new_content}\n#{end_tag}"
205
211
  current_block = content[start_index..end_index + end_tag.length - 1]
206
212
  return :no_change if current_block == replacement
213
+
207
214
  trailing = after[end_tag.length..-1] || ""
208
215
  "#{before}\n#{new_content}\n#{end_tag}#{trailing}"
209
216
  end
@@ -230,9 +237,11 @@ module Kettle
230
237
 
231
238
  def extract_section_identities(content, start_tag, end_tag)
232
239
  return Set.new unless start_tag && end_tag && start_tag != :not_found && end_tag != :not_found
240
+
233
241
  start_index = content.index(start_tag)
234
242
  end_index = content.index(end_tag)
235
243
  return Set.new if start_index.nil? || end_index.nil? || end_index < start_index
244
+
236
245
  block = content[(start_index + start_tag.length)...end_index]
237
246
  identities = Set.new
238
247
  block.to_s.scan(/\[!\[[^\]]*\]\([^\)]*\)\]\(([^\)]+)\)/) do |m|
@@ -269,6 +278,7 @@ module Kettle
269
278
  def mention_for_member(m, default_name: "Member")
270
279
  handle = github_handle_from_urls(m.profile, m.website)
271
280
  return "@#{handle}" if handle
281
+
272
282
  name = (m.name && !m.name.strip.empty?) ? m.name.strip : default_name
273
283
  name
274
284
  end
@@ -281,8 +291,10 @@ module Kettle
281
291
  next
282
292
  end
283
293
  next unless uri&.host&.downcase&.end_with?("github.com")
294
+
284
295
  path = (uri.path || "").sub(%r{^/}, "").sub(%r{/$}, "")
285
296
  next if path.empty?
297
+
286
298
  parts = path.split("/")
287
299
  candidate = if parts[0].downcase == "sponsors" && parts[1]
288
300
  parts[1]
@@ -308,12 +320,14 @@ module Kettle
308
320
  if system("git", "diff", "--cached", "--quiet")
309
321
  return
310
322
  end
323
+
311
324
  system("git", "commit", "-m", message)
312
325
  end
313
326
 
314
327
  def commit_subject
315
328
  env = ENV["KETTLE_README_BACKERS_COMMIT_SUBJECT"].to_s
316
329
  return env unless env.strip.empty?
330
+
317
331
  if File.file?(OC_YML_PATH)
318
332
  begin
319
333
  yml = YAML.safe_load(File.read(OC_YML_PATH))