kettle-dev 1.0.23 → 1.0.25

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.
@@ -0,0 +1,352 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kettle
4
+ module Dev
5
+ class ChangelogCLI
6
+ def initialize
7
+ @root = Kettle::Dev::CIHelpers.project_root
8
+ @changelog_path = File.join(@root, "CHANGELOG.md")
9
+ @coverage_path = File.join(@root, "coverage", "coverage.json")
10
+ end
11
+
12
+ def run
13
+ version = Kettle::Dev::Versioning.detect_version(@root)
14
+ today = Time.now.strftime("%Y-%m-%d")
15
+ owner, repo = Kettle::Dev::CIHelpers.repo_info
16
+ unless owner && repo
17
+ warn("Could not determine GitHub owner/repo from origin remote.")
18
+ warn("Make sure 'origin' points to github.com. Alternatively, set origin or update links manually afterward.")
19
+ end
20
+
21
+ line_cov_line, branch_cov_line = coverage_lines
22
+ yard_line = yard_percent_documented
23
+
24
+ changelog = File.read(@changelog_path)
25
+
26
+ # If the detected version already exists in the changelog, abort to avoid duplicates
27
+ if changelog =~ /^## \[#{Regexp.escape(version)}\]/
28
+ abort("CHANGELOG.md already has a section for version #{version}. Bump version.rb or remove the duplicate.")
29
+ end
30
+
31
+ unreleased_block, before, after = extract_unreleased(changelog)
32
+ if unreleased_block.nil?
33
+ abort("Could not find '## [Unreleased]' section in CHANGELOG.md")
34
+ end
35
+
36
+ if unreleased_block.strip.empty?
37
+ warn("No entries found under Unreleased. Creating an empty version section anyway.")
38
+ end
39
+
40
+ prev_version = detect_previous_version(after)
41
+
42
+ new_section = +""
43
+ new_section << "## [#{version}] - #{today}\n"
44
+ new_section << "- TAG: [v#{version}][#{version}t]\n"
45
+ new_section << "- #{line_cov_line}\n" if line_cov_line
46
+ new_section << "- #{branch_cov_line}\n" if branch_cov_line
47
+ new_section << "- #{yard_line}\n" if yard_line
48
+ new_section << filter_unreleased_sections(unreleased_block)
49
+ # Ensure exactly one blank line separates this new section from the next section
50
+ new_section.rstrip!
51
+ new_section << "\n\n"
52
+
53
+ # Reset the Unreleased section to empty category headings
54
+ unreleased_reset = <<~MD
55
+ ## [Unreleased]
56
+ ### Added
57
+ ### Changed
58
+ ### Deprecated
59
+ ### Removed
60
+ ### Fixed
61
+ ### Security
62
+ MD
63
+
64
+ # Preserve everything from the first released section down to the line containing the [Unreleased] link ref.
65
+ # Many real-world changelogs intersperse stray link refs between sections; we should keep them.
66
+ updated = before + unreleased_reset + "\n" + new_section
67
+ # Find the [Unreleased]: link-ref line and append everything from the start of the first released section
68
+ # through to the end of the file, but if a [Unreleased]: ref exists, ensure we do not duplicate the
69
+ # section content above it.
70
+ if after && !after.empty?
71
+ # Split 'after' by lines so we can locate the first link-ref to Unreleased
72
+ after_lines = after.lines
73
+ unreleased_ref_idx = after_lines.index { |l| l.start_with?("[Unreleased]:") }
74
+ if unreleased_ref_idx
75
+ # Keep all content prior to the link-ref (older releases and interspersed refs)
76
+ preserved_body = after_lines[0...unreleased_ref_idx].join
77
+ # Then append the tail starting from the Unreleased link-ref line to preserve the footer refs
78
+ preserved_footer = after_lines[unreleased_ref_idx..-1].join
79
+ updated << preserved_body << preserved_footer
80
+ else
81
+ # No Unreleased ref found; just append the remainder as-is
82
+ updated << after
83
+ end
84
+ end
85
+
86
+ updated = update_link_refs(updated, owner, repo, prev_version, version)
87
+
88
+ # Ensure exactly one trailing newline at EOF
89
+ updated = updated.rstrip + "\n"
90
+
91
+ File.write(@changelog_path, updated)
92
+ puts "CHANGELOG.md updated with v#{version} section."
93
+ end
94
+
95
+ private
96
+
97
+ def abort(msg)
98
+ Kettle::Dev::ExitAdapter.abort(msg)
99
+ rescue NameError
100
+ Kernel.abort(msg)
101
+ end
102
+
103
+ def detect_version
104
+ candidates = Dir[File.join(@root, "lib", "**", "version.rb")]
105
+ abort("Could not find version.rb under lib/**.") if candidates.empty?
106
+ versions = candidates.map do |path|
107
+ content = File.read(path)
108
+ m = content.match(/VERSION\s*=\s*(["'])([^"']+)\1/)
109
+ next unless m
110
+ m[2]
111
+ end.compact
112
+ abort("VERSION constant not found in #{@root}/lib/**/version.rb") if versions.none?
113
+ abort("Multiple VERSION constants found to be out of sync (#{versions.inspect}) in #{@root}/lib/**/version.rb") unless versions.uniq.length == 1
114
+ versions.first
115
+ end
116
+
117
+ def extract_unreleased(content)
118
+ lines = content.lines
119
+ start_i = lines.index { |l| l.start_with?("## [Unreleased]") }
120
+ return [nil, nil, nil] unless start_i
121
+ # Find the next version heading after Unreleased
122
+ next_i = (start_i + 1)
123
+ while next_i < lines.length && !lines[next_i].start_with?("## [")
124
+ next_i += 1
125
+ end
126
+ # Now next_i points to the next section heading or EOF
127
+ before = lines[0..(start_i - 1)].join
128
+ unreleased_block = lines[(start_i + 1)..(next_i - 1)].join
129
+ after = lines[next_i..-1]&.join || ""
130
+ [unreleased_block, before, after]
131
+ end
132
+
133
+ def detect_previous_version(after_text)
134
+ # after_text begins with the first released section following Unreleased
135
+ m = after_text.match(/^## \[(\d+\.\d+\.\d+)\]/)
136
+ return m[1] if m
137
+ nil
138
+ end
139
+
140
+ # From the Unreleased block, keep only sections that have content.
141
+ # We detect sections as lines starting with '### '. A section has content if there is at least
142
+ # one non-empty, non-heading line under it before the next '###' or '##'. Typically these are list items.
143
+ # Returns a string that includes only the non-empty sections with their content.
144
+ def filter_unreleased_sections(unreleased_block)
145
+ lines = unreleased_block.lines
146
+ out = []
147
+ i = 0
148
+ while i < lines.length
149
+ line = lines[i]
150
+ if line.start_with?("### ")
151
+ header = line
152
+ i += 1
153
+ chunk = []
154
+ while i < lines.length && !lines[i].start_with?("### ") && !lines[i].start_with?("## ")
155
+ chunk << lines[i]
156
+ i += 1
157
+ end
158
+ # Determine if chunk has any content (non-blank)
159
+ content_present = chunk.any? { |l| l.strip != "" }
160
+ if content_present
161
+ # Trim trailing blank lines
162
+ while chunk.any? && chunk.last.strip == ""
163
+ chunk.pop
164
+ end
165
+ out << header
166
+ out.concat(chunk)
167
+ out << "\n" unless out.last&.end_with?("\n")
168
+ end
169
+ next
170
+ else
171
+ # Lines outside sections are ignored for released sections
172
+ i += 1
173
+ end
174
+ end
175
+ out.join
176
+ end
177
+
178
+ def coverage_lines
179
+ unless File.file?(@coverage_path)
180
+ warn("Coverage JSON not found at #{@coverage_path}.")
181
+ warn("Run: K_SOUP_COV_FORMATTERS=\"json\" bin/rspec")
182
+ return [nil, nil]
183
+ end
184
+ data = JSON.parse(File.read(@coverage_path))
185
+ files = data["coverage"] || {}
186
+ file_count = 0
187
+ total_lines = 0
188
+ covered_lines = 0
189
+ total_branches = 0
190
+ covered_branches = 0
191
+ files.each_value do |h|
192
+ lines = h["lines"] || []
193
+ line_relevant = lines.count { |x| x.is_a?(Integer) }
194
+ line_covered = lines.count { |x| x.is_a?(Integer) && x > 0 }
195
+ if line_relevant > 0
196
+ file_count += 1
197
+ total_lines += line_relevant
198
+ covered_lines += line_covered
199
+ end
200
+ branches = h["branches"] || []
201
+ branches.each do |b|
202
+ next unless b.is_a?(Hash)
203
+ cov = b["coverage"]
204
+ next unless cov.is_a?(Numeric)
205
+ total_branches += 1
206
+ covered_branches += 1 if cov > 0
207
+ end
208
+ end
209
+ line_pct = (total_lines > 0) ? ((covered_lines.to_f / total_lines) * 100.0) : 0.0
210
+ branch_pct = (total_branches > 0) ? ((covered_branches.to_f / total_branches) * 100.0) : 0.0
211
+ line_str = format("COVERAGE: %.2f%% -- %d/%d lines in %d files", line_pct, covered_lines, total_lines, file_count)
212
+ branch_str = format("BRANCH COVERAGE: %.2f%% -- %d/%d branches in %d files", branch_pct, covered_branches, total_branches, file_count)
213
+ [line_str, branch_str]
214
+ rescue StandardError => e
215
+ warn("Failed to parse coverage: #{e.class}: #{e.message}")
216
+ [nil, nil]
217
+ end
218
+
219
+ def yard_percent_documented
220
+ cmd = File.join(@root, "bin", "yard")
221
+ unless File.executable?(cmd)
222
+ warn("bin/yard not found or not executable; ensure yard is installed via bundler")
223
+ return
224
+ end
225
+ out, _ = Open3.capture2(cmd)
226
+ # Look for a line containing e.g., "95.35% documented"
227
+ line = out.lines.find { |l| l =~ /\d+(?:\.\d+)?%\s+documented/ }
228
+ if line
229
+ line = line.strip
230
+ # Return exactly as requested: e.g. "95.35% documented"
231
+ line
232
+ else
233
+ warn("Could not find documented percentage in bin/yard output.")
234
+ nil
235
+ end
236
+ rescue StandardError => e
237
+ warn("Failed to run bin/yard: #{e.class}: #{e.message}")
238
+ nil
239
+ end
240
+
241
+ def update_link_refs(content, owner, repo, prev_version, new_version)
242
+ # Convert any GitLab links to GitHub
243
+ content = content.gsub(%r{https://gitlab\.com/([^/]+)/([^/]+)/-/compare/([^\.]+)\.\.\.([^\s]+)}) do
244
+ o = owner || Regexp.last_match(1)
245
+ r = repo || Regexp.last_match(2)
246
+ from = Regexp.last_match(3)
247
+ to = Regexp.last_match(4)
248
+ "https://github.com/#{o}/#{r}/compare/#{from}...#{to}"
249
+ end
250
+ content = content.gsub(%r{https://gitlab\.com/([^/]+)/([^/]+)/-/tags/(v[^\s\]]+)}) do
251
+ o = owner || Regexp.last_match(1)
252
+ r = repo || Regexp.last_match(2)
253
+ tag = Regexp.last_match(3)
254
+ "https://github.com/#{o}/#{r}/releases/tag/#{tag}"
255
+ end
256
+
257
+ # Append or update the bottom reference links
258
+ lines = content.lines
259
+
260
+ # Identify the true start of the footer reference block: the line with the [Unreleased] link-ref.
261
+ # Do NOT assume the first link-ref after the Unreleased heading starts the footer, because
262
+ # some changelogs contain interspersed link-refs within section bodies.
263
+ unreleased_ref_idx = lines.index { |l| l.start_with?("[Unreleased]:") }
264
+ first_ref = if unreleased_ref_idx
265
+ unreleased_ref_idx
266
+ else
267
+ # If no [Unreleased]: ref is present, consider the reference block to start at EOF
268
+ lines.length
269
+ end
270
+
271
+ # Ensure Unreleased points to GitHub compare from new tag to HEAD
272
+ if owner && repo
273
+ unreleased_ref = "[Unreleased]: https://github.com/#{owner}/#{repo}/compare/v#{new_version}...HEAD\n"
274
+ # Update an existing Unreleased ref only if it appears after Unreleased heading; otherwise append
275
+ idx = nil
276
+ lines.each_with_index do |l, i|
277
+ if l.start_with?("[Unreleased]:") && i >= first_ref
278
+ idx = i
279
+ break
280
+ end
281
+ end
282
+ if idx
283
+ lines[idx] = unreleased_ref
284
+ else
285
+ lines << unreleased_ref
286
+ end
287
+ end
288
+
289
+ if owner && repo
290
+ # Add compare link for the new version
291
+ from = prev_version ? "v#{prev_version}" : detect_initial_compare_base(lines)
292
+ new_compare = "[#{new_version}]: https://github.com/#{owner}/#{repo}/compare/#{from}...v#{new_version}\n"
293
+ unless lines.any? { |l| l.start_with?("[#{new_version}]:") }
294
+ lines << new_compare
295
+ end
296
+ # Add tag link for the new version
297
+ new_tag = "[#{new_version}t]: https://github.com/#{owner}/#{repo}/releases/tag/v#{new_version}\n"
298
+ unless lines.any? { |l| l.start_with?("[#{new_version}t]:") }
299
+ lines << new_tag
300
+ end
301
+ end
302
+
303
+ # Rebuild and sort the reference block so Unreleased is first, then newest to oldest versions, preserving everything above first_ref
304
+ ref_lines = lines[first_ref..-1].select { |l| l =~ /^\[[^\]]+\]:\s+http/ }
305
+ # Deduplicate by key (text inside the square brackets)
306
+ by_key = {}
307
+ ref_lines.each do |l|
308
+ if l =~ /^\[([^\]]+)\]:\s+/
309
+ by_key[$1] = l
310
+ end
311
+ end
312
+ unreleased_line = by_key.delete("Unreleased")
313
+ # Separate version compare and tag links
314
+ compares = {}
315
+ tags = {}
316
+ by_key.each do |k, v|
317
+ if k =~ /^(\d+\.\d+\.\d+)$/
318
+ compares[$1] = v
319
+ elsif k =~ /^(\d+\.\d+\.\d+)t$/
320
+ tags[$1] = v
321
+ end
322
+ end
323
+ # Build a unified set of versions that appear in either compares or tags
324
+ version_keys = (compares.keys | tags.keys)
325
+ # Sort versions descending (newest to oldest)
326
+ sorted_versions = version_keys.map { |s| Gem::Version.new(s) }.sort.reverse.map(&:to_s)
327
+
328
+ new_ref_block = []
329
+ new_ref_block << unreleased_line if unreleased_line
330
+ sorted_versions.each do |v|
331
+ new_ref_block << compares[v] if compares[v]
332
+ new_ref_block << tags[v] if tags[v]
333
+ end
334
+ # Replace the old block
335
+ rebuilt = lines[0...first_ref] + new_ref_block + ["\n"]
336
+ rebuilt.join
337
+ end
338
+
339
+ def detect_initial_compare_base(lines)
340
+ # Fallback when prev_version is unknown: try to find the first compare base used historically
341
+ # e.g., for 1.0.0 it may be a commit SHA instead of a tag
342
+ ref = lines.find { |l| l =~ /^\[1\.0\.0\]:\s+https:\/\/github\.com\// }
343
+ if ref && (m = ref.match(%r{compare/([^\.]+)\.\.\.v\d+})).is_a?(MatchData)
344
+ m[1]
345
+ else
346
+ # Default to previous tag name if none found (unlikely to be correct, but better than empty)
347
+ "HEAD^"
348
+ end
349
+ end
350
+ end
351
+ end
352
+ end
@@ -5,11 +5,6 @@ require "uri"
5
5
  require "json"
6
6
  require "net/http"
7
7
 
8
- # Internal
9
- require "kettle/dev/ci_helpers"
10
- require "kettle/dev/exit_adapter"
11
- require "kettle/dev/git_adapter"
12
-
13
8
  module Kettle
14
9
  module Dev
15
10
  # CIMonitor centralizes CI monitoring logic (GitHub Actions and GitLab pipelines)
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Exposed from lib/ so that exe/kettle-commit-msg can be a minimal wrapper.
4
-
5
3
  module Kettle
6
4
  module Dev
7
5
  class GitCommitFooter
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # External stdlib
4
- require "kettle/dev/exit_adapter"
5
4
  require "yaml"
6
5
  require "json"
7
6
  require "uri"
@@ -13,12 +13,6 @@ require "uri"
13
13
  # External gems
14
14
  require "ruby-progressbar"
15
15
 
16
- # Internal
17
- require "kettle/dev/git_adapter"
18
- require "kettle/dev/exit_adapter"
19
- require "kettle/dev/input_adapter"
20
- require "kettle/dev/versioning"
21
-
22
16
  module Kettle
23
17
  module Dev
24
18
  class ReleaseCLI
@@ -57,7 +51,9 @@ module Kettle
57
51
  gem_name = detect_gem_name
58
52
  latest_overall, latest_for_series = latest_released_versions(gem_name, version)
59
53
  rescue StandardError => e
60
- warn("Warning: failed to check RubyGems for latest version (#{e.class}: #{e.message}). Proceeding.")
54
+ warn("[kettle-release] RubyGems check failed: #{e.class}: #{e.message}")
55
+ warn(e.backtrace.first(3).map { |l| " " + l }.join("\n")) if ENV["DEBUG"]
56
+ warn("Proceeding without RubyGems latest version info.")
61
57
  end
62
58
 
63
59
  if latest_overall
@@ -460,7 +456,6 @@ module Kettle
460
456
 
461
457
  def monitor_workflows_after_push!
462
458
  # Delegate to shared CI monitor to keep logic DRY across release flow and rake tasks
463
- require "kettle/dev/ci_monitor"
464
459
  Kettle::Dev::CIMonitor.monitor_all!(restart_hint: "bundle exec kettle-release start_step=10")
465
460
  end
466
461
 
@@ -923,8 +918,11 @@ module Kettle
923
918
  # Title: v<version>
924
919
  # Body: the CHANGELOG section for this version, followed by the two link references for this version.
925
920
  def maybe_create_github_release!(version)
926
- token = ENV.fetch("GITHUB_TOKEN", "").to_s
927
- return if token.strip.empty?
921
+ token = ENV.fetch("GITHUB_TOKEN", "").strip.to_s
922
+ if token.empty?
923
+ warn("GITHUB_TOKEN not set; skipping GitHub release creation. Set GITHUB_TOKEN with repo:public_repo (classic) or contents:write scope.")
924
+ return
925
+ end
928
926
 
929
927
  gh_remote = preferred_github_remote
930
928
  url = remote_url(gh_remote || "origin")
@@ -1002,7 +1000,8 @@ module Kettle
1002
1000
  # Normalize: trim trailing whitespace but keep internal formatting
1003
1001
  block = block.lstrip # drop leading newline/space
1004
1002
  block.rstrip
1005
- rescue StandardError
1003
+ rescue StandardError => e
1004
+ warn("[kettle-release] Failed to extract release notes footer from FUNDING.md: #{e.class}: #{e.message}")
1006
1005
  nil
1007
1006
  end
1008
1007
 
@@ -1,12 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # External
4
- require "kettle/dev/exit_adapter"
5
- require "kettle/dev/input_adapter"
6
4
  require "open3"
7
5
  require "net/http"
8
6
  require "json"
9
7
  require "uri"
8
+ require "io/console"
10
9
 
11
10
  module Kettle
12
11
  module Dev
@@ -15,16 +14,17 @@ module Kettle
15
14
  module_function
16
15
 
17
16
  # Local abort indirection to enable mocking via ExitAdapter
18
- def abort(msg)
19
- Kettle::Dev::ExitAdapter.abort(msg)
17
+ def task_abort(msg)
18
+ if defined?(RSpec)
19
+ raise Kettle::Dev::Error, msg
20
+ else
21
+ Kettle::Dev::ExitAdapter.abort(msg)
22
+ end
20
23
  end
21
- module_function :abort
22
24
 
23
25
  # Runs `act` for a selected workflow. Option can be a short code or workflow basename.
24
- # Mirrors the behavior previously implemented in the ci:act rake task.
25
26
  # @param opt [String, nil]
26
27
  def act(opt = nil)
27
- require "io/console"
28
28
  choice = opt&.strip
29
29
 
30
30
  root_dir = Kettle::Dev::CIHelpers.project_root
@@ -100,7 +100,7 @@ module Kettle
100
100
 
101
101
  run_act_for = proc do |file_path|
102
102
  ok = system("act", "-W", file_path)
103
- abort("ci:act failed: 'act' command not found or exited with failure") unless ok
103
+ task_abort("ci:act failed: 'act' command not found or exited with failure") unless ok
104
104
  end
105
105
 
106
106
  if choice && !choice.empty?
@@ -128,17 +128,10 @@ module Kettle
128
128
  puts " (others) =>"
129
129
  dynamic_files.each { |v| puts " #{v}" }
130
130
  end
131
- abort("ci:act aborted")
131
+ task_abort("ci:act aborted")
132
132
  end
133
133
  fetch_and_print_status.call(file)
134
134
  run_act_for.call(file_path)
135
- # After running locally, check upstream GitLab pipeline status if configured
136
- begin
137
- require "kettle/dev/ci_monitor"
138
- Kettle::Dev::CIMonitor.monitor_gitlab!(restart_hint: "bundle exec rake ci:act")
139
- rescue LoadError
140
- # ignore if not available
141
- end
142
135
  return
143
136
  end
144
137
 
@@ -312,13 +305,13 @@ module Kettle
312
305
  end
313
306
 
314
307
  input = selected
315
- abort("ci:act aborted: no selection") if input.nil? || input.empty?
308
+ task_abort("ci:act aborted: no selection") if input.nil? || input.empty?
316
309
 
317
310
  chosen_file = nil
318
311
  if !!(/^\d+$/ =~ input)
319
312
  idx = input.to_i - 1
320
313
  if idx < 0 || idx >= options_with_quit.length
321
- abort("ci:act aborted: invalid selection #{input}")
314
+ task_abort("ci:act aborted: invalid selection #{input}")
322
315
  end
323
316
  code, val = options_with_quit[idx]
324
317
  if code == quit_code
@@ -334,20 +327,14 @@ module Kettle
334
327
  return
335
328
  end
336
329
  chosen_file = mapping[code]
337
- abort("ci:act aborted: unknown code '#{code}'") unless chosen_file
330
+ task_abort("ci:act aborted: unknown code '#{code}'") unless chosen_file
338
331
  end
339
332
 
340
333
  file_path = File.join(workflows_dir, chosen_file)
341
- abort("ci:act aborted: workflow not found: #{file_path}") unless File.file?(file_path)
334
+ task_abort("ci:act aborted: workflow not found: #{file_path}") unless File.file?(file_path)
342
335
  fetch_and_print_status.call(chosen_file)
343
336
  run_act_for.call(file_path)
344
- # After running locally, check upstream GitLab pipeline status if configured
345
- begin
346
- require "kettle/dev/ci_monitor"
347
- Kettle::Dev::CIMonitor.monitor_gitlab!(restart_hint: "bundle exec rake ci:act")
348
- rescue LoadError
349
- # ignore if not available
350
- end
337
+ Kettle::Dev::CIMonitor.monitor_gitlab!(restart_hint: "bundle exec rake ci:act")
351
338
  end
352
339
  end
353
340
  end
@@ -1,8 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "kettle/dev/exit_adapter"
4
- require "kettle/dev/input_adapter"
5
-
6
3
  module Kettle
7
4
  module Dev
8
5
  module Tasks
@@ -1,8 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "kettle/dev/exit_adapter"
4
- require "kettle/dev/input_adapter"
5
-
6
3
  module Kettle
7
4
  module Dev
8
5
  module Tasks
@@ -2,8 +2,6 @@
2
2
 
3
3
  # External stdlibs
4
4
  require "find"
5
- # Internal
6
- require "kettle/dev/input_adapter"
7
5
 
8
6
  module Kettle
9
7
  module Dev
@@ -346,6 +344,15 @@ module Kettle
346
344
  c = content.dup
347
345
  c = c.gsub("kettle-rb", org.to_s) if org && !org.empty?
348
346
  if gem_name && !gem_name.empty?
347
+ # Special-case: yard-head link uses the gem name as a subdomain and must be dashes-only.
348
+ # Apply this BEFORE other generic replacements so it isn't altered incorrectly.
349
+ begin
350
+ dashed = gem_name.tr("_", "-")
351
+ c = c.gsub("[🚎yard-head]: https://kettle-dev.galtzo.com", "[🚎yard-head]: https://#{dashed}.galtzo.com")
352
+ rescue StandardError
353
+ # ignore
354
+ end
355
+
349
356
  # Replace occurrences of the template gem name in text, including inside
350
357
  # markdown reference labels like [🖼️kettle-dev] and identifiers like kettle-dev-i
351
358
  c = c.gsub("kettle-dev", gem_name)
@@ -6,7 +6,7 @@ module Kettle
6
6
  module Version
7
7
  # The gem version.
8
8
  # @return [String]
9
- VERSION = "1.0.23"
9
+ VERSION = "1.0.25"
10
10
  end
11
11
  end
12
12
  end
data/lib/kettle/dev.rb CHANGED
@@ -112,21 +112,26 @@ end
112
112
  module Kettle
113
113
  autoload :EmojiRegex, "kettle/emoji_regex"
114
114
  module Dev
115
+ autoload :ChangelogCLI, "kettle/dev/changelog_cli"
115
116
  autoload :CIHelpers, "kettle/dev/ci_helpers"
117
+ autoload :CIMonitor, "kettle/dev/ci_monitor"
116
118
  autoload :CommitMsg, "kettle/dev/commit_msg"
119
+ autoload :ExitAdapter, "kettle/dev/exit_adapter"
117
120
  autoload :GemSpecReader, "kettle/dev/gem_spec_reader"
121
+ autoload :GitAdapter, "kettle/dev/git_adapter"
118
122
  autoload :GitCommitFooter, "kettle/dev/git_commit_footer"
123
+ autoload :InputAdapter, "kettle/dev/input_adapter"
119
124
  autoload :ReadmeBackers, "kettle/dev/readme_backers"
120
125
  autoload :ReleaseCLI, "kettle/dev/release_cli"
121
126
  autoload :TemplateHelpers, "kettle/dev/template_helpers"
122
- autoload :ExitAdapter, "kettle/dev/exit_adapter"
123
127
  autoload :Version, "kettle/dev/version"
128
+ autoload :Versioning, "kettle/dev/versioning"
124
129
 
125
130
  # Nested tasks namespace with autoloaded task modules
126
131
  module Tasks
127
- autoload :TemplateTask, "kettle/dev/tasks/template_task"
128
- autoload :InstallTask, "kettle/dev/tasks/install_task"
129
132
  autoload :CITask, "kettle/dev/tasks/ci_task"
133
+ autoload :InstallTask, "kettle/dev/tasks/install_task"
134
+ autoload :TemplateTask, "kettle/dev/tasks/template_task"
130
135
  end
131
136
  end
132
137
  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: 1.0.23
4
+ version: 1.0.25
5
5
  platform: ruby
6
6
  authors:
7
7
  - Peter H. Boling
@@ -297,6 +297,7 @@ files:
297
297
  - kettle-dev.gemspec.example
298
298
  - lib/kettle-dev.rb
299
299
  - lib/kettle/dev.rb
300
+ - lib/kettle/dev/changelog_cli.rb
300
301
  - lib/kettle/dev/ci_helpers.rb
301
302
  - lib/kettle/dev/ci_monitor.rb
302
303
  - lib/kettle/dev/commit_msg.rb
@@ -350,10 +351,10 @@ licenses:
350
351
  - MIT
351
352
  metadata:
352
353
  homepage_uri: https://kettle-dev.galtzo.com/
353
- source_code_uri: https://github.com/kettle-rb/kettle-dev/tree/v1.0.23
354
- changelog_uri: https://github.com/kettle-rb/kettle-dev/blob/v1.0.23/CHANGELOG.md
354
+ source_code_uri: https://github.com/kettle-rb/kettle-dev/tree/v1.0.25
355
+ changelog_uri: https://github.com/kettle-rb/kettle-dev/blob/v1.0.25/CHANGELOG.md
355
356
  bug_tracker_uri: https://github.com/kettle-rb/kettle-dev/issues
356
- documentation_uri: https://www.rubydoc.info/gems/kettle-dev/1.0.23
357
+ documentation_uri: https://www.rubydoc.info/gems/kettle-dev/1.0.25
357
358
  funding_uri: https://github.com/sponsors/pboling
358
359
  wiki_uri: https://github.com/kettle-rb/kettle-dev/wiki
359
360
  news_uri: https://www.railsbling.com/tags/kettle-dev
metadata.gz.sig CHANGED
Binary file