kettle-dev 1.2.4 → 2.0.1

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.
Files changed (152) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/CHANGELOG.md +232 -3
  4. data/CITATION.cff +6 -6
  5. data/CONTRIBUTING.md +64 -46
  6. data/FUNDING.md +1 -1
  7. data/LICENSE.md +12 -0
  8. data/README.md +448 -457
  9. data/certs/pboling.pem +27 -0
  10. data/exe/kettle-changelog +24 -13
  11. data/exe/kettle-check-eof +7 -0
  12. data/exe/kettle-check-eof.sh +118 -0
  13. data/exe/kettle-dev-setup +12 -63
  14. data/exe/kettle-gh-release +82 -0
  15. data/lib/kettle/dev/changelog_cli.rb +373 -62
  16. data/lib/kettle/dev/ci_monitor.rb +2 -2
  17. data/lib/kettle/dev/dvcs_cli.rb +1 -1
  18. data/lib/kettle/dev/gem_spec_reader.rb +100 -10
  19. data/lib/kettle/dev/git_adapter.rb +37 -1
  20. data/lib/kettle/dev/open_collective_config.rb +15 -3
  21. data/lib/kettle/dev/pre_release_cli.rb +4 -4
  22. data/lib/kettle/dev/rakelib/reek.rake +9 -6
  23. data/lib/kettle/dev/rakelib/spec_test.rake +25 -25
  24. data/lib/kettle/dev/rakelib/yard.rake +17 -17
  25. data/lib/kettle/dev/readme_backers.rb +4 -4
  26. data/lib/kettle/dev/release_cli.rb +27 -24
  27. data/lib/kettle/dev/tasks/ci_task.rb +4 -4
  28. data/lib/kettle/dev/version.rb +2 -87
  29. data/lib/kettle/dev.rb +31 -17
  30. data/sig/kettle/dev/source_merger.rbs +40 -56
  31. data/sig/kettle/dev/version.rbs +8 -0
  32. data.tar.gz.sig +0 -0
  33. metadata +63 -165
  34. metadata.gz.sig +0 -0
  35. data/.aiignore.example +0 -19
  36. data/.devcontainer/apt-install/devcontainer-feature.json +0 -9
  37. data/.devcontainer/apt-install/install.sh +0 -11
  38. data/.devcontainer/devcontainer.json +0 -28
  39. data/.env.local.example +0 -31
  40. data/.envrc +0 -47
  41. data/.envrc.example +0 -51
  42. data/.envrc.no-osc.example +0 -51
  43. data/.git-hooks/commit-msg +0 -54
  44. data/.git-hooks/commit-subjects-goalie.txt +0 -8
  45. data/.git-hooks/footer-template.erb.txt +0 -16
  46. data/.git-hooks/prepare-commit-msg +0 -8
  47. data/.github/.codecov.yml.example +0 -14
  48. data/.github/FUNDING.yml +0 -13
  49. data/.github/FUNDING.yml.no-osc.example +0 -13
  50. data/.github/dependabot.yml +0 -13
  51. data/.github/workflows/ancient.yml +0 -83
  52. data/.github/workflows/ancient.yml.example +0 -81
  53. data/.github/workflows/auto-assign.yml +0 -21
  54. data/.github/workflows/codeql-analysis.yml +0 -70
  55. data/.github/workflows/coverage.yml +0 -127
  56. data/.github/workflows/coverage.yml.example +0 -127
  57. data/.github/workflows/current.yml +0 -116
  58. data/.github/workflows/current.yml.example +0 -115
  59. data/.github/workflows/dep-heads.yml +0 -117
  60. data/.github/workflows/dependency-review.yml +0 -20
  61. data/.github/workflows/discord-notifier.yml.example +0 -39
  62. data/.github/workflows/heads.yml +0 -117
  63. data/.github/workflows/heads.yml.example +0 -116
  64. data/.github/workflows/jruby.yml +0 -82
  65. data/.github/workflows/jruby.yml.example +0 -72
  66. data/.github/workflows/legacy.yml +0 -76
  67. data/.github/workflows/license-eye.yml +0 -40
  68. data/.github/workflows/locked_deps.yml +0 -85
  69. data/.github/workflows/opencollective.yml +0 -40
  70. data/.github/workflows/style.yml +0 -67
  71. data/.github/workflows/supported.yml +0 -75
  72. data/.github/workflows/truffle.yml +0 -99
  73. data/.github/workflows/unlocked_deps.yml +0 -84
  74. data/.github/workflows/unsupported.yml +0 -76
  75. data/.gitignore +0 -50
  76. data/.gitlab-ci.yml.example +0 -134
  77. data/.idea/.gitignore +0 -45
  78. data/.junie/guidelines-rbs.md +0 -49
  79. data/.junie/guidelines.md +0 -141
  80. data/.junie/guidelines.md.example +0 -140
  81. data/.licenserc.yaml +0 -7
  82. data/.opencollective.yml +0 -3
  83. data/.opencollective.yml.example +0 -3
  84. data/.qlty/qlty.toml +0 -79
  85. data/.rspec +0 -9
  86. data/.rubocop.yml +0 -13
  87. data/.rubocop_rspec.yml +0 -33
  88. data/.simplecov +0 -16
  89. data/.simplecov.example +0 -11
  90. data/.tool-versions +0 -1
  91. data/.yardignore +0 -13
  92. data/.yardopts +0 -14
  93. data/Appraisal.root.gemfile +0 -10
  94. data/Appraisals +0 -151
  95. data/Appraisals.example +0 -102
  96. data/CHANGELOG.md.example +0 -47
  97. data/CONTRIBUTING.md.example +0 -227
  98. data/FUNDING.md.no-osc.example +0 -63
  99. data/Gemfile +0 -40
  100. data/Gemfile.example +0 -34
  101. data/LICENSE.txt +0 -21
  102. data/README.md.example +0 -570
  103. data/README.md.no-osc.example +0 -536
  104. data/REEK +0 -0
  105. data/Rakefile.example +0 -68
  106. data/bin/setup +0 -8
  107. data/gemfiles/modular/coverage.gemfile +0 -6
  108. data/gemfiles/modular/debug.gemfile +0 -13
  109. data/gemfiles/modular/documentation.gemfile +0 -14
  110. data/gemfiles/modular/erb/r2/v3.0.gemfile +0 -1
  111. data/gemfiles/modular/erb/r2.3/default.gemfile +0 -6
  112. data/gemfiles/modular/erb/r2.6/v2.2.gemfile +0 -3
  113. data/gemfiles/modular/erb/r3/v5.0.gemfile +0 -1
  114. data/gemfiles/modular/erb/r3.1/v4.0.gemfile +0 -2
  115. data/gemfiles/modular/erb/vHEAD.gemfile +0 -2
  116. data/gemfiles/modular/injected.gemfile +0 -60
  117. data/gemfiles/modular/mutex_m/r2/v0.3.gemfile +0 -2
  118. data/gemfiles/modular/mutex_m/r2.4/v0.1.gemfile +0 -3
  119. data/gemfiles/modular/mutex_m/r3/v0.3.gemfile +0 -2
  120. data/gemfiles/modular/mutex_m/vHEAD.gemfile +0 -2
  121. data/gemfiles/modular/optional.gemfile +0 -8
  122. data/gemfiles/modular/optional.gemfile.example +0 -5
  123. data/gemfiles/modular/runtime_heads.gemfile +0 -10
  124. data/gemfiles/modular/runtime_heads.gemfile.example +0 -8
  125. data/gemfiles/modular/stringio/r2/v3.0.gemfile +0 -5
  126. data/gemfiles/modular/stringio/r2.4/v0.0.2.gemfile +0 -4
  127. data/gemfiles/modular/stringio/r3/v3.0.gemfile +0 -5
  128. data/gemfiles/modular/stringio/vHEAD.gemfile +0 -2
  129. data/gemfiles/modular/style.gemfile +0 -25
  130. data/gemfiles/modular/style.gemfile.example +0 -25
  131. data/gemfiles/modular/templating.gemfile +0 -3
  132. data/gemfiles/modular/x_std_libs/r2/libs.gemfile +0 -3
  133. data/gemfiles/modular/x_std_libs/r2.3/libs.gemfile +0 -3
  134. data/gemfiles/modular/x_std_libs/r2.4/libs.gemfile +0 -3
  135. data/gemfiles/modular/x_std_libs/r2.6/libs.gemfile +0 -3
  136. data/gemfiles/modular/x_std_libs/r3/libs.gemfile +0 -3
  137. data/gemfiles/modular/x_std_libs/r3.1/libs.gemfile +0 -3
  138. data/gemfiles/modular/x_std_libs/vHEAD.gemfile +0 -3
  139. data/gemfiles/modular/x_std_libs.gemfile +0 -2
  140. data/kettle-dev.gemspec.example +0 -154
  141. data/lib/kettle/dev/modular_gemfiles.rb +0 -119
  142. data/lib/kettle/dev/prism_appraisals.rb +0 -351
  143. data/lib/kettle/dev/prism_gemfile.rb +0 -177
  144. data/lib/kettle/dev/prism_gemspec.rb +0 -284
  145. data/lib/kettle/dev/prism_utils.rb +0 -201
  146. data/lib/kettle/dev/rakelib/install.rake +0 -10
  147. data/lib/kettle/dev/rakelib/template.rake +0 -10
  148. data/lib/kettle/dev/setup_cli.rb +0 -403
  149. data/lib/kettle/dev/source_merger.rb +0 -655
  150. data/lib/kettle/dev/tasks/install_task.rb +0 -553
  151. data/lib/kettle/dev/tasks/template_task.rb +0 -975
  152. data/lib/kettle/dev/template_helpers.rb +0 -685
@@ -21,7 +21,7 @@ module Kettle
21
21
  # For Bundler-invoked build/release, explicitly prefix SKIP_GEM_SIGNING so
22
22
  # the signing step is skipped even when Bundler scrubs ENV.
23
23
  # Always do this on CI to avoid interactive prompts; locally only when explicitly requested.
24
- if ENV["SKIP_GEM_SIGNING"] && cmd =~ /\Abundle(\s+exec)?\s+rake\s+(build|release)\b/
24
+ if ENV["SKIP_GEM_SIGNING"] && /\Abundle(\s+exec)?\s+rake\s+(build|release)\b/.match?(cmd)
25
25
  cmd = "SKIP_GEM_SIGNING=true #{cmd}"
26
26
  end
27
27
  puts "$ #{cmd}"
@@ -31,7 +31,7 @@ module Kettle
31
31
  # Some commands are interactive (e.g., `bundle exec rake release` prompting for RubyGems MFA).
32
32
  # Using capture3 detaches STDIN, preventing prompts from working. For such commands, use system
33
33
  # so they inherit the current TTY and can read the user's input.
34
- interactive = cmd =~ /\Abundle(\s+exec)?\s+rake\s+release\b/ || cmd =~ /\Agem\s+push\b/
34
+ interactive = /\Abundle(\s+exec)?\s+rake\s+release\b/.match?(cmd) || /\Agem\s+push\b/.match?(cmd)
35
35
  if interactive
36
36
  ok = system(env_hash, cmd)
37
37
  unless ok
@@ -97,9 +97,9 @@ module Kettle
97
97
  gem_name = detect_gem_name
98
98
  latest_overall, latest_for_series = latest_released_versions(gem_name, version)
99
99
  rescue StandardError => e
100
- warn("[kettle-release] RubyGems check failed: #{e.class}: #{e.message}")
101
- warn(e.backtrace.first(3).map { |l| " " + l }.join("\n")) if ENV["DEBUG"]
102
- warn("Proceeding without RubyGems latest version info.")
100
+ warn("[kettle-release] gem.coop release check failed: #{e.class}: #{e.message}")
101
+ warn(e.backtrace.first(3).map { |l| " " + l }.join("\n")) if ENV["KETTLE_DEV_DEBUG"]
102
+ warn("Proceeding without gem.coop latest version info.")
103
103
  end
104
104
 
105
105
  if latest_overall
@@ -121,7 +121,7 @@ module Kettle
121
121
  latest_for_series = nil unless lfs_series == cur_series
122
122
  end
123
123
  # Determine the sanity-check target correctly for the current series.
124
- # If RubyGems has a newer overall series than our current series, only compare
124
+ # If gem.coop has a newer overall series than our current series, only compare
125
125
  # against the latest published in our current series. If that cannot be determined
126
126
  # (e.g., offline), skip the sanity check rather than treating the overall as target.
127
127
  target = if (cur_series <=> overall_series) == -1
@@ -130,10 +130,10 @@ module Kettle
130
130
  latest_overall
131
131
  end
132
132
  # IMPORTANT: Never treat a higher different-series "latest_overall" as a downgrade target.
133
- # If our current series is behind overall and RubyGems does not report a latest_for_series,
133
+ # If our current series is behind overall and gem.coop does not report a latest_for_series,
134
134
  # then we cannot determine the correct target for this series and should skip the check.
135
135
  if (cur_series <=> overall_series) == -1 && target.nil?
136
- puts "Could not determine latest released version from RubyGems (offline?). Proceeding without sanity check."
136
+ puts "Could not determine latest released version from gem.coop (offline?). Proceeding without sanity check."
137
137
  elsif target
138
138
  bump = Kettle::Dev::Versioning.classify_bump(target, version)
139
139
  case bump
@@ -150,10 +150,10 @@ module Kettle
150
150
  puts "Proposed bump type: #{label} (from #{target} -> #{version})"
151
151
  end
152
152
  else
153
- puts "Could not determine latest released version from RubyGems (offline?). Proceeding without sanity check."
153
+ puts "Could not determine latest released version from gem.coop (offline?). Proceeding without sanity check."
154
154
  end
155
155
  else
156
- puts "Could not determine latest released version from RubyGems (offline?). Proceeding without sanity check."
156
+ puts "Could not determine latest released version from gem.coop (offline?). Proceeding without sanity check."
157
157
  end
158
158
 
159
159
  puts "Have you updated lib/**/version.rb and CHANGELOG.md for v#{version}? [y/N]"
@@ -184,15 +184,18 @@ module Kettle
184
184
  # 4. bin/rake
185
185
  run_cmd!("bin/rake") if @start_step <= 4
186
186
 
187
- # 5. appraisal:update (optional)
187
+ # 5. appraisal:update (optional) + canonical docs build
188
188
  if @start_step <= 5
189
189
  appraisals_path = File.join(@root, "Appraisals")
190
190
  if File.file?(appraisals_path)
191
- puts "Appraisals detected at #{appraisals_path}. Running: bin/rake appraisal:update"
191
+ puts "Appraisals detected at #{Kettle::Dev.display_path(appraisals_path)}. Running: bin/rake appraisal:update"
192
192
  run_cmd!("bin/rake appraisal:update")
193
193
  else
194
194
  puts "No Appraisals file found; skipping appraisal:update"
195
195
  end
196
+
197
+ puts "Generating docs site via canonical task: bin/rake yard"
198
+ run_cmd!("bin/rake yard")
196
199
  end
197
200
 
198
201
  # 6. git user + commit release prep
@@ -317,7 +320,7 @@ module Kettle
317
320
  return unless section
318
321
 
319
322
  # Example match: "- COVERAGE: 97.70% -- 2125/2175 lines in 20 files"
320
- m = section.lines.find { |l| l =~ /-\s*COVERAGE:\s*.+--\s*\d+\/(\d+)\s+lines/i }
323
+ m = section.lines.find { |l| /-\s*COVERAGE:\s*.+--\s*\d+\/(\d+)\s+lines/i.match?(l) }
321
324
  return unless m
322
325
 
323
326
  denom = m.match(/-\s*COVERAGE:\s*.+--\s*\d+\/(\d+)\s+lines/i)[1].to_i
@@ -408,7 +411,7 @@ module Kettle
408
411
  content = File.read(path)
409
412
  # Only consider lines that look like copyright notices to reduce false positives
410
413
  content.each_line do |line|
411
- next unless line =~ /copyright/i
414
+ next unless /copyright/i.match?(line)
412
415
 
413
416
  # Expand ranges first (supports hyphen-minus and en dash)
414
417
  line.scan(/\b(19\d{2}|20\d{2})\s*[\-–]\s*(19\d{2}|20\d{2})\b/).each do |a, b|
@@ -459,7 +462,7 @@ module Kettle
459
462
  changed = false
460
463
  canonical_all = collapse_years(years_set)
461
464
  new_lines = content.each_line.map do |line|
462
- unless line =~ /copyright/i
465
+ unless /copyright/i.match?(line)
463
466
  next line
464
467
  end
465
468
 
@@ -483,7 +486,7 @@ module Kettle
483
486
  content = File.read(path)
484
487
  changed = false
485
488
  new_lines = content.each_line.map do |line|
486
- unless line =~ /copyright/i
489
+ unless /copyright/i.match?(line)
487
490
  next line
488
491
  end
489
492
 
@@ -556,7 +559,7 @@ module Kettle
556
559
  when "ask"
557
560
  print("Run local CI with 'act' before pushing? [Y/n] ")
558
561
  ans = Kettle::Dev::InputAdapter.gets&.strip
559
- ans.nil? || ans.empty? || ans =~ /\Ay(es)?\z/i
562
+ ans.nil? || ans.empty? || /\Ay(es)?\z/i.match?(ans)
560
563
  else
561
564
  false
562
565
  end
@@ -579,7 +582,7 @@ module Kettle
579
582
 
580
583
  chosen = (ENV["K_RELEASE_LOCAL_CI_WORKFLOW"] || "").strip
581
584
  if !chosen.empty?
582
- chosen = "#{chosen}.yml" unless chosen =~ /\.ya?ml\z/
585
+ chosen = "#{chosen}.yml" unless /\.ya?ml\z/.match?(chosen)
583
586
  else
584
587
  chosen = if candidates.include?("locked_deps.yml")
585
588
  "locked_deps.yml"
@@ -597,7 +600,7 @@ module Kettle
597
600
 
598
601
  file_path = File.join(workflows_dir, chosen)
599
602
  unless File.file?(file_path)
600
- puts "Skipping local CI: selected workflow not found: #{file_path}"
603
+ puts "Skipping local CI: selected workflow not found: #{Kettle::Dev.display_path(file_path)}"
601
604
  return
602
605
  end
603
606
 
@@ -625,18 +628,18 @@ module Kettle
625
628
  path = gemspecs.min
626
629
  content = File.read(path)
627
630
  m = content.match(/spec\.name\s*=\s*(["'])([^"']+)\1/)
628
- abort("Could not determine gem name from #{path}.") unless m
631
+ abort("Could not determine gem name from #{Kettle::Dev.display_path(path)}.") unless m
629
632
  m[2]
630
633
  end
631
634
 
632
635
  def latest_released_versions(gem_name, current_version)
633
- uri = URI("https://rubygems.org/api/v1/versions/#{gem_name}.json")
636
+ uri = URI("https://gem.coop/api/v1/versions/#{gem_name}.json")
634
637
  res = Net::HTTP.get_response(uri)
635
638
  return [nil, nil] unless res.is_a?(Net::HTTPSuccess)
636
639
 
637
640
  data = JSON.parse(res.body)
638
641
  versions = data.map { |h| h["number"] }.compact
639
- versions.reject! { |v| v.to_s.include?("-pre") || v.to_s.include?(".pre") || v.to_s =~ /[a-zA-Z]/ }
642
+ versions.reject! { |v| v.to_s.include?("-pre") || v.to_s.include?(".pre") || /[a-zA-Z]/.match?(v.to_s) }
640
643
  gversions = versions.map { |s| Gem::Version.new(s) }.sort
641
644
  latest_overall = gversions.last&.to_s
642
645
 
@@ -924,11 +927,11 @@ module Kettle
924
927
  unless File.exist?(cert_path)
925
928
  abort(<<~MSG)
926
929
  Gem signing appears enabled but no public cert found at:
927
- #{cert_path}
930
+ #{Kettle::Dev.display_path(cert_path)}
928
931
  Add your public key to certs/<USER>.pem (or set GEM_CERT_USER), or set SKIP_GEM_SIGNING to build unsigned.
929
932
  MSG
930
933
  end
931
- puts "Found signing cert: #{cert_path}"
934
+ puts "Found signing cert: #{Kettle::Dev.display_path(cert_path)}"
932
935
  puts "When prompted during build/release, enter the PEM password for ~/.ssh/gem-private_key.pem"
933
936
  end
934
937
 
@@ -265,11 +265,11 @@ module Kettle
265
265
  selected = nil
266
266
  # Create input thread always so specs that assert its cleanup/exception behavior can exercise it,
267
267
  # but guard against non-interactive stdin by rescuing 'bad tty' and similar errors immediately.
268
- input_thread = Thread.new do
268
+ input_thread = Thread.new do # rubocop:disable ThreadSafety/NewThread
269
269
  begin
270
270
  selected = Kettle::Dev::InputAdapter.gets&.strip
271
- rescue Exception => error
272
- # Catch all exceptions in background thread, including SystemExit
271
+ rescue StandardError, SystemExit, Interrupt => error
272
+ # Catch exceptions in background thread, including SystemExit
273
273
  # NOTE: look into refactoring to minimize potential SystemExit.
274
274
  puts "Error in background thread: #{error.class}: #{error.message}" if Kettle::Dev::DEBUGGING
275
275
  selected = :input_error
@@ -281,7 +281,7 @@ module Kettle
281
281
  start_at = Process.clock_gettime(Process::CLOCK_MONOTONIC)
282
282
 
283
283
  options.each do |code, file|
284
- workers << Thread.new(code, file, owner, repo, branch, token, start_at) do |c, f, ow, rp, br, tk, st_at|
284
+ workers << Thread.new(code, file, owner, repo, branch, token, start_at) do |c, f, ow, rp, br, tk, st_at| # rubocop:disable ThreadSafety/NewThread
285
285
  begin
286
286
  now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
287
287
  delay = 0.12 - (now - st_at)
@@ -2,94 +2,9 @@
2
2
 
3
3
  module Kettle
4
4
  module Dev
5
- # Version namespace for kettle-dev.
6
5
  module Version
7
- # The gem version.
8
- # @return [String]
9
- VERSION = "1.2.4"
10
-
11
- module_function
12
-
13
- # rubocop:disable ThreadSafety/ClassInstanceVariable
14
- #
15
- # The logic below, through the end of the file, comes from version_gem.
16
- # Extracted because version_gem depends on this gem, and circular dependencies are bad.
17
- #
18
- # A Gem::Version for this version string
19
- #
20
- # Useful when you need to compare versions or pass a Gem::Version instance
21
- # to APIs that expect it. This is equivalent to `Gem::Version.new(to_s)`.
22
- #
23
- # @return [Gem::Version]
24
- def gem_version
25
- @gem_version ||= ::Gem::Version.new(to_s)
26
- end
27
-
28
- # The version number as a string
29
- #
30
- # @return [String]
31
- def to_s
32
- self::VERSION
33
- end
34
-
35
- # The major version
36
- #
37
- # @return [Integer]
38
- def major
39
- @major ||= _to_a[0].to_i
40
- end
41
-
42
- # The minor version
43
- #
44
- # @return [Integer]
45
- def minor
46
- @minor ||= _to_a[1].to_i
47
- end
48
-
49
- # The patch version
50
- #
51
- # @return [Integer]
52
- def patch
53
- @patch ||= _to_a[2].to_i
54
- end
55
-
56
- # The pre-release version, if any
57
- #
58
- # @return [String, NilClass]
59
- def pre
60
- @pre ||= _to_a[3]
61
- end
62
-
63
- # The version number as a hash
64
- #
65
- # @return [Hash]
66
- def to_h
67
- @to_h ||= {
68
- major: major,
69
- minor: minor,
70
- patch: patch,
71
- pre: pre,
72
- }
73
- end
74
-
75
- # The version number as an array of cast values
76
- #
77
- # @return [Array<[Integer, String, NilClass]>]
78
- def to_a
79
- @to_a ||= [major, minor, patch, pre]
80
- end
81
-
82
- private
83
-
84
- module_function
85
-
86
- # The version number as an array of strings
87
- #
88
- # @return [Array<String>]
89
- def _to_a
90
- @_to_a = self::VERSION.split(".")
91
- end
92
- # rubocop:enable ThreadSafety/ClassInstanceVariable
6
+ VERSION = "2.0.1"
93
7
  end
8
+ VERSION = Version::VERSION # Traditional Constant Location
94
9
  end
95
10
  end
data/lib/kettle/dev.rb CHANGED
@@ -1,6 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "version_gem"
4
+ require_relative "dev/version"
5
+
6
+ Kettle::Dev::Version.class_eval do
7
+ extend VersionGem::Basic
8
+ end
3
9
  # External gems
10
+
4
11
  # It's not reasonable to test this ENV variable
5
12
  # :nocov:
6
13
  require "require_bench" if ENV.fetch("REQUIRE_BENCH", "false").casecmp("true").zero?
@@ -21,35 +28,25 @@ module Kettle
21
28
  autoload :GitAdapter, "kettle/dev/git_adapter"
22
29
  autoload :GitCommitFooter, "kettle/dev/git_commit_footer"
23
30
  autoload :InputAdapter, "kettle/dev/input_adapter"
24
- autoload :PrismUtils, "kettle/dev/prism_utils"
25
- autoload :PrismGemspec, "kettle/dev/prism_gemspec"
26
- autoload :PrismGemfile, "kettle/dev/prism_gemfile"
27
- autoload :PrismAppraisals, "kettle/dev/prism_appraisals"
28
31
  autoload :ReadmeBackers, "kettle/dev/readme_backers"
29
32
  autoload :OpenCollectiveConfig, "kettle/dev/open_collective_config"
30
- autoload :SourceMerger, "kettle/dev/source_merger"
31
33
  autoload :ReleaseCLI, "kettle/dev/release_cli"
32
34
  autoload :PreReleaseCLI, "kettle/dev/pre_release_cli"
33
- autoload :SetupCLI, "kettle/dev/setup_cli"
34
- autoload :TemplateHelpers, "kettle/dev/template_helpers"
35
- autoload :ModularGemfiles, "kettle/dev/modular_gemfiles"
36
35
  autoload :Version, "kettle/dev/version"
37
36
  autoload :Versioning, "kettle/dev/versioning"
38
37
 
39
38
  # Nested tasks namespace with autoloaded task modules
40
39
  module Tasks
41
40
  autoload :CITask, "kettle/dev/tasks/ci_task"
42
- autoload :InstallTask, "kettle/dev/tasks/install_task"
43
- autoload :TemplateTask, "kettle/dev/tasks/template_task"
44
41
  end
45
42
 
46
43
  # Base error type for kettle-dev.
47
44
  class Error < StandardError; end
48
45
 
49
46
  # Whether debug logging is enabled for kettle-dev internals.
50
- # KETTLE_DEV_DEBUG overrides DEBUG.
47
+ # Controlled by KETTLE_DEV_DEBUG=true.
51
48
  # @return [Boolean]
52
- DEBUGGING = ENV.fetch("KETTLE_DEV_DEBUG", ENV.fetch("DEBUG", "false")).casecmp("true").zero?
49
+ DEBUGGING = ENV.fetch("KETTLE_DEV_DEBUG", "false").casecmp("true").zero?
53
50
  # Whether we are running on CI.
54
51
  # @return [Boolean]
55
52
  IS_CI = ENV.fetch("CI", "false").casecmp("true") == 0
@@ -76,25 +73,42 @@ module Kettle
76
73
  # @return [String]
77
74
  GEM_ROOT = File.expand_path("../..", __dir__)
78
75
 
79
- @defaults = []
76
+ @defaults = [].freeze
80
77
 
81
78
  class << self
79
+ VAR_HOME_PREFIX = %r{\A/var/home(?=/|\z)}
80
+ VAR_HOME_TEXT = %r{/var/home(?=/|\z)}
81
+
82
+ def display_path(path)
83
+ return path if path.nil?
84
+
85
+ path.to_s.sub(VAR_HOME_PREFIX, "/home")
86
+ end
87
+
88
+ def display_text(text)
89
+ return text if text.nil?
90
+
91
+ text.to_s.gsub(VAR_HOME_TEXT, "/home")
92
+ end
93
+
82
94
  # Emit a debug warning for rescued errors when kettle-dev debugging is enabled.
83
- # Controlled by KETTLE_DEV_DEBUG=true (or DEBUG=true as fallback).
95
+ # Controlled by KETTLE_DEV_DEBUG=true.
84
96
  # @param error [Exception]
85
97
  # @param context [String, Symbol, nil] optional label, often __method__
98
+ # @param backtrace [Boolean] whether to emit the rescued error backtrace
86
99
  # @return [void]
87
- def debug_error(error, context = nil)
100
+ def debug_error(error, context = nil, backtrace: true)
88
101
  return unless DEBUGGING
89
102
 
90
103
  ctx = context ? context.to_s : "KETTLE-DEV-RESCUE"
91
104
  Kernel.warn("[#{ctx}] #{error.class}: #{error.message}")
105
+ Kernel.warn(Array(error.backtrace).first(5).join("\n")) if backtrace && error.respond_to?(:backtrace) && error.backtrace
92
106
  rescue StandardError
93
107
  # never raise from debug logging
94
108
  end
95
109
 
96
110
  # Emit a debug log line when kettle-dev debugging is enabled.
97
- # Controlled by KETTLE_DEV_DEBUG=true (or DEBUG=true as fallback).
111
+ # Controlled by KETTLE_DEV_DEBUG=true.
98
112
  # @param msg [String]
99
113
  # @return [void]
100
114
  def debug_log(msg, context = nil)
@@ -128,7 +142,7 @@ module Kettle
128
142
  def register_default(task_name)
129
143
  task_name = task_name.to_s
130
144
  unless defaults.include?(task_name)
131
- defaults << task_name
145
+ @defaults = (defaults + [task_name]).freeze # rubocop:disable ThreadSafety/ClassInstanceVariable
132
146
  if defined?(Rake) && Rake::Task.task_defined?(:default)
133
147
  begin
134
148
  Rake::Task[:default].enhance([task_name])
@@ -2,15 +2,24 @@
2
2
 
3
3
  module Kettle
4
4
  module Dev
5
- # Prism-based AST merging for templated Ruby files
5
+ # Prism-based AST merging for templated Ruby files.
6
+ # Handles strategy dispatch (skip/replace/append/merge).
7
+ #
8
+ # Uses prism-merge for AST-aware merging with support for:
9
+ # - Freeze blocks (kettle-dev:freeze / kettle-dev:unfreeze)
10
+ # - Comment preservation
11
+ # - Signature-based node matching
6
12
  module SourceMerger
7
- FREEZE_START: Regexp
8
- FREEZE_END: Regexp
9
- FREEZE_BLOCK: Regexp
10
- FREEZE_REMINDER: String
11
13
  BUG_URL: String
12
14
 
13
15
  # Apply a templating strategy to merge source and destination Ruby files
16
+ #
17
+ # @param strategy [Symbol] Merge strategy - :skip, :replace, :append, or :merge
18
+ # @param src [String] Template source content
19
+ # @param dest [String] Destination file content
20
+ # @param path [String] File path (for error messages)
21
+ # @return [String] Merged content with comments preserved
22
+ # @raise [Kettle::Dev::Error] If strategy is unknown or merge fails
14
23
  def self.apply: (
15
24
  strategy: Symbol,
16
25
  src: String,
@@ -18,69 +27,44 @@ module Kettle
18
27
  path: String
19
28
  ) -> String
20
29
 
21
- # Ensure freeze reminder comment is present at the top of content
22
- def self.ensure_reminder: (String content) -> String
23
-
24
- # Normalize source code while preserving formatting
25
- def self.normalize_source: (String source) -> String
26
-
27
- # Check if freeze reminder is present in content
28
- def self.reminder_present?: (String content) -> bool
29
-
30
- # Find index where freeze reminder should be inserted
31
- def self.reminder_insertion_index: (String content) -> Integer
32
-
33
- # Check if line is a shebang
34
- def self.shebang?: (String line) -> bool
35
-
36
- # Check if line is a frozen_string_literal comment
37
- def self.frozen_comment?: (String line) -> bool
38
-
39
- # Merge kettle-dev:freeze blocks from destination into source content
40
- def self.merge_freeze_blocks: (String src_content, String dest_content) -> String
41
-
42
- # Extract freeze blocks from text
43
- def self.freeze_blocks: (String? text) -> Array[Hash[Symbol, untyped]]
44
-
45
30
  # Normalize strategy symbol
31
+ #
32
+ # @param strategy [Symbol, nil] Strategy to normalize
33
+ # @return [Symbol] Normalized strategy (:skip if nil)
46
34
  def self.normalize_strategy: (Symbol? strategy) -> Symbol
47
35
 
48
36
  # Warn about bugs and print error information
37
+ #
38
+ # @param path [String] File path that caused the error
39
+ # @param error [StandardError] The error that occurred
40
+ # @return [void]
49
41
  def self.warn_bug: (String path, StandardError error) -> void
50
42
 
51
43
  # Ensure text ends with newline
44
+ #
45
+ # @param text [String, nil] Text to process
46
+ # @return [String] Text with trailing newline
52
47
  def self.ensure_trailing_newline: (String? text) -> String
53
48
 
54
- # Apply append strategy
49
+ # Apply append strategy using prism-merge
50
+ #
51
+ # @param src_content [String] Template source content
52
+ # @param dest_content [String] Destination content
53
+ # @return [String] Merged content with destination preference
55
54
  def self.apply_append: (String src_content, String dest_content) -> String
56
55
 
57
- # Apply merge strategy
56
+ # Apply merge strategy using prism-merge
57
+ #
58
+ # @param src_content [String] Template source content
59
+ # @param dest_content [String] Destination content
60
+ # @return [String] Merged content with template preference
58
61
  def self.apply_merge: (String src_content, String dest_content) -> String
59
62
 
60
- # Merge node information
61
- def self.merge_node_info: (Array[untyped] signature, Hash[Symbol, untyped] dest_node_info, Hash[Symbol, untyped] src_node_info) -> Hash[Symbol, untyped]
62
-
63
- # Merge block node information
64
- def self.merge_block_node_info: (Hash[Symbol, untyped] src_node_info) -> Hash[Symbol, untyped]
65
-
66
- # Perform Prism-based merge with block
67
- def self.prism_merge: (String src_content, String dest_content) { (Array[Hash[Symbol, untyped]], Array[Hash[Symbol, untyped]], Prism::ParseResult, Prism::ParseResult) -> Array[Hash[Symbol, untyped]] } -> String
68
-
69
- # Extract nodes with comments from parse result
70
- def self.extract_nodes_with_comments: (Prism::ParseResult parse_result) -> Array[Hash[Symbol, untyped]]
71
-
72
- # Build source from node information array
73
- def self.build_source_from_nodes: (Array[Hash[Symbol, untyped]] node_infos) -> String
74
-
75
- # Generate signature for node
76
- def self.node_signature: (Prism::Node? node) -> Array[untyped]
77
-
78
- # Restore custom leading comments from destination
79
- def self.restore_custom_leading_comments: (String dest_content, String merged_content) -> String
80
-
81
- # Extract leading comment block from content
82
- def self.leading_comment_block: (String content) -> String
63
+ # Create a signature generator for prism-merge
64
+ # Handles various Ruby node types for proper matching during merge operations
65
+ #
66
+ # @return [Proc] Signature generator lambda
67
+ def self.create_signature_generator: () -> ^(Prism::Node) -> (Array[untyped] | Prism::Node)
83
68
  end
84
69
  end
85
- end
86
-
70
+ end
@@ -0,0 +1,8 @@
1
+ module Kettle
2
+ module Dev
3
+ module Version
4
+ VERSION: String
5
+ end
6
+ VERSION: String
7
+ end
8
+ end
data.tar.gz.sig CHANGED
Binary file