kettle-dev 2.5.18 → 2.5.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6ce9b09d7f7b63282d83f98a7599f0e3a1e2f4253c4f342c5a386220ad957a1d
4
- data.tar.gz: 108a6343526873d70684091ae6fddb5b0663644b26a2428dfe89721f29767096
3
+ metadata.gz: 7f642698f24dc241a8f3286ff20b151dcdb30333c436290681740ffe24fb6148
4
+ data.tar.gz: 578361645c3b639cca69d6c120a7990b38afcbb55bc6c5c55df95e4ba998e292
5
5
  SHA512:
6
- metadata.gz: dc871e4686bc80667419140219bd8655c64375b6d24d556d562275437876f9fefa71bbf6b3dac7fc0c332dd23bdbc4311333700b6b151c683e943424b0ca628d
7
- data.tar.gz: 311013ce7d7751d9d7f6b8ac2ae165a73ea366711af9921ae70f6b2514c6689141f1ac06ab7481429f3e2dba3073b912fa4d9b94c48835a7b896c0e16e1d4c30
6
+ metadata.gz: bfd379df162aa902b96ba7f5fefc28170aee8f1995230d24268baccd8603dfa4a596cf4f29acf2a0230c8d1e755ec8a91ddc193cd4139a18e9bfc69143bd59e7
7
+ data.tar.gz: 62bd06604b31ea2d042e87ebb67ab9815d251c29e4606ea61cdcd5aad983868229e932dfad3bce5ea304e5a54523816723d1da3ce8a618f6e7eca88714f0aea0
checksums.yaml.gz.sig CHANGED
Binary file
data/CHANGELOG.md CHANGED
@@ -30,6 +30,36 @@ Please file a bug if you notice a violation of semantic versioning.
30
30
 
31
31
  ### Security
32
32
 
33
+ ## [2.5.20] - 2026-08-07
34
+
35
+ - TAG: [v2.5.20][2.5.20t]
36
+ - COVERAGE: 91.75% -- 5440/5929 lines in 47 files
37
+ - BRANCH COVERAGE: 76.14% -- 2049/2691 branches in 47 files
38
+ - 50.77% documented
39
+
40
+ ### Fixed
41
+
42
+ - Prefer non-cancelled exact-commit GitHub Actions runs when workflow concurrency creates duplicate runs.
43
+
44
+ - Skip YARD lint when a project has no .yard-lint.yml policy.
45
+
46
+ ## [2.5.19] - 2026-08-06
47
+
48
+ - TAG: [v2.5.19][2.5.19t]
49
+ - COVERAGE: 91.34% -- 5431/5946 lines in 47 files
50
+ - BRANCH COVERAGE: 75.28% -- 2037/2706 branches in 47 files
51
+ - 50.64% documented
52
+
53
+ ### Changed
54
+
55
+ - Track the released kettle-gha-pins client and its cache/tag-resolution contract in the development lockfile and specs.
56
+
57
+ ### Fixed
58
+
59
+ - Add release validation for Markdown reference labels and local heading anchors.
60
+
61
+ - Restore the Discord badge reference in CONTRIBUTING.md.
62
+
33
63
  ## [2.5.18] - 2026-08-03
34
64
 
35
65
  - TAG: [v2.5.18][2.5.18t]
@@ -3242,7 +3272,11 @@ Please file a bug if you notice a violation of semantic versioning.
3242
3272
  - Selecting will run the selected workflow via `act`
3243
3273
  - This may move to its own gem in the future.
3244
3274
 
3245
- [Unreleased]: https://github.com/kettle-dev/kettle-dev/compare/v2.5.18...HEAD
3275
+ [Unreleased]: https://github.com/kettle-dev/kettle-dev/compare/v2.5.20...HEAD
3276
+ [2.5.20]: https://github.com/kettle-dev/kettle-dev/compare/v2.5.19...v2.5.20
3277
+ [2.5.20t]: https://github.com/kettle-dev/kettle-dev/releases/tag/v2.5.20
3278
+ [2.5.19]: https://github.com/kettle-dev/kettle-dev/compare/v2.5.18...v2.5.19
3279
+ [2.5.19t]: https://github.com/kettle-dev/kettle-dev/releases/tag/v2.5.19
3246
3280
  [2.5.18]: https://github.com/kettle-dev/kettle-dev/compare/v2.5.17...v2.5.18
3247
3281
  [2.5.18t]: https://github.com/kettle-dev/kettle-dev/releases/tag/v2.5.18
3248
3282
  [2.5.17]: https://github.com/kettle-dev/kettle-dev/compare/v2.5.16...v2.5.17
data/README.md CHANGED
@@ -409,6 +409,8 @@ What it does:
409
409
  - Script: `exe/kettle-release` (run as `kettle-release`)
410
410
  - Purpose: guided release helper that:
411
411
  - Runs `kettle-pre-release` as step 0 on full releases, aborting before release setup if any pre-release gate fails.
412
+ - Validates Markdown reference definitions and local heading anchors across releasable documentation before network checks.
413
+ Markdown fixtures under `spec/` and `test/`, along with `tmp/`, are intentionally excluded.
412
414
  - Skips pre-release checks for any `start_step` greater than `0`, so resumptions begin directly at the requested numbered step.
413
415
  - Runs sanity checks (`bin/setup`, `bin/rake`), confirms version/changelog, optionally updates Appraisals, regenerates docs via `bin/rake yard`, commits “🔖 Prepare release vX.Y.Z”.
414
416
  - Optionally runs your CI locally with `act` before any push:
@@ -1005,7 +1007,7 @@ Thanks for RTFM. ☺️
1005
1007
  [📌gitmoji]: https://gitmoji.dev
1006
1008
  [📌gitmoji-img]: https://img.shields.io/badge/gitmoji_commits-%20%F0%9F%98%9C%20%F0%9F%98%8D-34495e.svg?style=flat-square
1007
1009
  [🧮kloc]: https://www.youtube.com/watch?v=dQw4w9WgXcQ
1008
- [🧮kloc-img]: https://img.shields.io/badge/KLOC-5.788-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
1010
+ [🧮kloc-img]: https://img.shields.io/badge/KLOC-5.929-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
1009
1011
  [🔐security]: https://github.com/kettle-dev/kettle-dev/blob/main/SECURITY.md
1010
1012
  [🔐security-img]: https://img.shields.io/badge/security-policy-259D6C.svg?style=flat
1011
1013
  [📄copyright-notice-explainer]: https://opensource.stackexchange.com/questions/5778/why-do-licenses-such-as-the-mit-license-specify-a-single-year
@@ -104,9 +104,10 @@ module Kettle
104
104
  return unless data
105
105
 
106
106
  runs = Array(data["workflow_runs"]) || []
107
- # Try to match by head_sha first; fall back to first run (branch-scoped) if none matches yet.
107
+ # Try to match by head_sha first; GitHub may return a cancelled duplicate
108
+ # before the replacement run created by workflow concurrency.
108
109
  run = if sha
109
- match = runs.find { |r| r["head_sha"] == sha }
110
+ match = preferred_head_run(runs.select { |r| r["head_sha"] == sha })
110
111
  match ||= latest_repository_workflow_run(owner: owner, repo: repo, workflow_file: workflow_file, branch: b, head_sha: sha, token: token)
111
112
  require_head ? match : (match || runs.first)
112
113
  else
@@ -134,9 +135,18 @@ module Kettle
134
135
  return unless data
135
136
 
136
137
  workflow_path = ".github/workflows/#{workflow_file}"
137
- Array(data["workflow_runs"]).find do |run|
138
- run["head_sha"] == head_sha && run["path"] == workflow_path
139
- end
138
+ preferred_head_run(
139
+ Array(data["workflow_runs"]).select do |run|
140
+ run["head_sha"] == head_sha && run["path"] == workflow_path
141
+ end
142
+ )
143
+ end
144
+
145
+ # Concurrency cancellation can leave an older run for the exact same
146
+ # commit ahead of its replacement in GitHub's API response. Prefer the
147
+ # replacement so a cancelled duplicate cannot mask a valid run.
148
+ def preferred_head_run(runs)
149
+ runs.find { |run| run["conclusion"] != "cancelled" } || runs.first
140
150
  end
141
151
 
142
152
  def github_get_json(url, token:)
@@ -0,0 +1,254 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "set"
4
+ require "uri"
5
+
6
+ module Kettle
7
+ module Dev
8
+ # Validates Markdown reference labels and local link destinations.
9
+ #
10
+ # This is intentionally a small source scanner rather than a second Markdown
11
+ # rendering pipeline. The release gate needs reference integrity and local
12
+ # anchor checks, while the existing Markdown merge/parser dependencies are
13
+ # not runtime dependencies of kettle-dev.
14
+ class MarkdownReferenceValidator
15
+ Issue = Struct.new(:path, :line, :message)
16
+ Report = Struct.new(:file_count, :reference_count, :local_target_count, :issues)
17
+
18
+ FENCE_RE = /\A\s*(`{3,}|~{3,})/.freeze
19
+ IGNORED_PATH_PREFIXES = %w[tmp/ .git/ spec/ test/].freeze
20
+ REFERENCE_DEFINITION_RE = /\A {0,3}\[([^\]]+)\]:\s*(?:<([^>]+)>|(\S+))/
21
+ REFERENCE_USAGE_RE = /!?\[([^\]]*)\]\[([^\]]*)\]/
22
+ INLINE_LINK_RE = /!?\[[^\]]*\]\((<[^>]+>|[^)]*)\)/
23
+ ATX_HEADING_RE = /\A {0,3}\#{1,6}\s+(.+?)\s*\#*\s*\z/
24
+ SETEXT_HEADING_RE = /\A {0,3}(=+|-+)\s*\z/
25
+
26
+ def initialize(root: Dir.pwd, files: nil)
27
+ @root = File.expand_path(root)
28
+ @files = (files || default_files).reject { |path| ignored_path?(path) }
29
+ end
30
+
31
+ def validate!
32
+ documents = @files.each_with_object({}) do |path, result|
33
+ absolute = absolute_path(path)
34
+ result[path] = parse_document(path, absolute) if File.file?(absolute)
35
+ end
36
+ report = build_report(documents)
37
+ return report if report.issues.empty?
38
+
39
+ warn("[kettle-pre-release] Markdown reference validation failed:")
40
+ report.issues.each do |issue|
41
+ warn(" - #{Kettle::Dev.display_path(issue.path)}:#{issue.line}: #{issue.message}")
42
+ end
43
+ raise Kettle::Dev::Error, "Markdown reference validation failed (#{report.issues.size} issue(s))"
44
+ end
45
+
46
+ private
47
+
48
+ def default_files
49
+ if defined?(Kettle::Dev::PreReleaseCLI::Markdown)
50
+ Kettle::Dev::PreReleaseCLI::Markdown.project_markdown_files.reject { |path| ignored_path?(path) }
51
+ else
52
+ Dir.glob(["**/*.md", "**/*.markdown", "**/*.md.example"]).reject { |path| ignored_path?(path) }.sort
53
+ end
54
+ end
55
+
56
+ def ignored_path?(path)
57
+ IGNORED_PATH_PREFIXES.any? { |prefix| path.to_s.start_with?(prefix) }
58
+ end
59
+
60
+ def absolute_path(path)
61
+ File.expand_path(path, @root)
62
+ end
63
+
64
+ def parse_document(path, absolute)
65
+ lines = File.readlines(absolute, chomp: true)
66
+ definitions = {}
67
+ usages = []
68
+ local_targets = []
69
+ headings = []
70
+ in_fence = false
71
+ fence_char = nil
72
+ fence_length = 0
73
+ previous_text = nil
74
+
75
+ lines.each_with_index do |line, index|
76
+ line_number = index + 1
77
+ fence = line.match(FENCE_RE)
78
+ if in_fence
79
+ if fence && fence[1][0] == fence_char && fence[1].length >= fence_length
80
+ in_fence = false
81
+ end
82
+ next
83
+ elsif fence
84
+ in_fence = true
85
+ fence_char = fence[1][0]
86
+ fence_length = fence[1].length
87
+ next
88
+ end
89
+
90
+ definition = line.match(REFERENCE_DEFINITION_RE)
91
+ if definition
92
+ label = normalize_label(definition[1])
93
+ target = definition[2] || definition[3]
94
+ definitions[label] ||= []
95
+ definitions[label] << [line_number, target]
96
+ local_targets << [line_number, target]
97
+ previous_text = nil
98
+ next
99
+ end
100
+
101
+ line.scan(REFERENCE_USAGE_RE) do |text, label|
102
+ resolved_label = label.empty? ? text : label
103
+ usages << [line_number, normalize_label(resolved_label)]
104
+ end
105
+ line.scan(INLINE_LINK_RE) do |destination|
106
+ target = destination[0]
107
+ target = target[1..-2] if target.start_with?("<") && target.end_with?(">")
108
+ target = target.split(/\s+/, 2).first
109
+ local_targets << [line_number, target] unless target.to_s.empty?
110
+ end
111
+
112
+ if line.match?(SETEXT_HEADING_RE) && previous_text
113
+ headings << [line_number - 1, previous_text]
114
+ previous_text = nil
115
+ else
116
+ heading = heading_for(line)
117
+ headings << heading if heading
118
+ previous_text = line.strip unless line.strip.empty?
119
+ end
120
+ end
121
+
122
+ {
123
+ path: path,
124
+ definitions: definitions,
125
+ usages: usages,
126
+ local_targets: local_targets,
127
+ headings: heading_slugs(headings)
128
+ }
129
+ rescue Errno::EACCES, Errno::ENOENT => error
130
+ {
131
+ path: path,
132
+ definitions: {},
133
+ usages: [],
134
+ local_targets: [],
135
+ headings: [],
136
+ read_error: error
137
+ }
138
+ end
139
+
140
+ def build_report(documents)
141
+ issues = []
142
+ reference_count = 0
143
+ local_target_count = 0
144
+ heading_cache = {}
145
+
146
+ documents.each_value do |document|
147
+ if document[:read_error]
148
+ issues << Issue.new(document[:path], 1, "could not read Markdown file: #{document[:read_error].message}")
149
+ next
150
+ end
151
+
152
+ document[:definitions].each do |label, definitions|
153
+ if definitions.length > 1
154
+ definitions.drop(1).each do |line_number, _target|
155
+ issues << Issue.new(document[:path], line_number, "duplicate reference definition #{label.inspect}")
156
+ end
157
+ end
158
+ end
159
+
160
+ document[:usages].each do |line_number, label|
161
+ next if label.include?("<") || label.include?(">")
162
+
163
+ reference_count += 1
164
+ next if document[:definitions].key?(label)
165
+
166
+ issues << Issue.new(document[:path], line_number, "undefined Markdown reference #{label.inspect}")
167
+ end
168
+
169
+ document[:local_targets].each do |line_number, target|
170
+ destination = local_destination(target)
171
+ next unless destination
172
+
173
+ local_target_count += 1
174
+ target_path, fragment = destination
175
+ target_document = target_path.empty? ? document : documents_for_path(documents, document[:path], target_path)
176
+ if target_document.nil?
177
+ issues << Issue.new(document[:path], line_number, "local Markdown target does not exist: #{target_path.inspect}")
178
+ next
179
+ end
180
+ next if fragment.empty?
181
+
182
+ slugs = heading_cache[target_document[:path]] ||= target_document[:headings]
183
+ next if slugs.include?(fragment)
184
+
185
+ issues << Issue.new(document[:path], line_number, "local heading target does not exist: ##{fragment}")
186
+ end
187
+ end
188
+
189
+ Report.new(documents.size, reference_count, local_target_count, issues)
190
+ end
191
+
192
+ def documents_for_path(documents, source_path, target_path)
193
+ candidate = File.expand_path(target_path, File.dirname(absolute_path(source_path)))
194
+ documents.values.find { |document| absolute_path(document[:path]) == candidate }
195
+ end
196
+
197
+ def local_destination(target)
198
+ value = target.to_s.strip
199
+ return nil if value.empty? || value.start_with?("//")
200
+
201
+ uri = URI.parse(value)
202
+ return nil if uri.scheme || uri.host
203
+ path = unescape(uri.path.to_s)
204
+ fragment = unescape(uri.fragment.to_s).downcase
205
+ return nil if fragment.empty?
206
+ return nil unless path.empty? || markdown_path?(path)
207
+
208
+ [path, fragment]
209
+ rescue URI::InvalidURIError
210
+ nil
211
+ end
212
+
213
+ def unescape(value)
214
+ parser = defined?(URI::RFC2396_PARSER) ? URI::RFC2396_PARSER : URI::DEFAULT_PARSER
215
+ parser.unescape(value)
216
+ end
217
+
218
+ def markdown_path?(path)
219
+ path.downcase.end_with?(".md", ".markdown", ".md.example")
220
+ end
221
+
222
+ def normalize_label(label)
223
+ label.to_s.downcase.gsub(/\s+/, " ").strip
224
+ end
225
+
226
+ def heading_for(line)
227
+ match = line.match(ATX_HEADING_RE)
228
+ match ? [line[/\A\s*/].to_s.length + 1, match[1]] : nil
229
+ end
230
+
231
+ def heading_slugs(headings)
232
+ counts = Hash.new(0)
233
+ headings.each_with_object(Set.new) do |(_line, text), slugs|
234
+ slug = slugify(text)
235
+ next if slug.empty?
236
+
237
+ suffix = counts[slug]
238
+ counts[slug] += 1
239
+ slugs.add(suffix.zero? ? slug : "#{slug}-#{suffix}")
240
+ end
241
+ end
242
+
243
+ def slugify(text)
244
+ text.to_s.gsub(/<[^>]+>/, "").downcase
245
+ .gsub(/[*_~`]/, "")
246
+ .gsub(/[^\p{Alnum}\s-]/u, "")
247
+ .gsub(/\s+/, "-")
248
+ .squeeze("-")
249
+ .sub(/\A-/, "")
250
+ .sub(/-\z/, "")
251
+ end
252
+ end
253
+ end
254
+ end
@@ -11,6 +11,7 @@ require "net/http"
11
11
  require "openssl"
12
12
  require "time"
13
13
  require_relative "cache_progress"
14
+ require_relative "markdown_reference_validator"
14
15
  require "kettle/ndjson"
15
16
  begin
16
17
  require "addressable/uri"
@@ -24,7 +25,8 @@ module Kettle
24
25
  # Checks:
25
26
  # 1) Ensure GitHub Actions workflow actions are pinned to current SHAs.
26
27
  # 2) Normalize Markdown image URLs using Addressable normalization.
27
- # 3) Validate Markdown image links resolve via cached HTTP(S) HEAD/GET.
28
+ # 3) Validate Markdown references and local heading targets.
29
+ # 4) Validate Markdown image links resolve via cached HTTP(S) HEAD/GET.
28
30
  #
29
31
  # Usage: Kettle::Dev::PreReleaseCLI.new(check_num: 1).run
30
32
  class PreReleaseCLI
@@ -288,6 +290,7 @@ module Kettle
288
290
  checks = []
289
291
  checks << method(:check_github_actions_sha_pins!)
290
292
  checks << method(:check_markdown_uri_normalization!)
293
+ checks << method(:check_markdown_references!)
291
294
  checks << method(:check_markdown_images_http!)
292
295
 
293
296
  start = @check_num
@@ -376,10 +379,30 @@ module Kettle
376
379
  normalized.to_s
377
380
  end
378
381
 
379
- # Check 3: Validate Markdown image links by cached HTTP HEAD/GET.
382
+ # Check 3: Validate Markdown references and local heading targets.
383
+ # @return [void]
384
+ def check_markdown_references!
385
+ puts "[kettle-pre-release] Check 3: Validate Markdown references and local heading targets"
386
+ report = MarkdownReferenceValidator.new(
387
+ root: Dir.pwd,
388
+ files: Markdown.project_markdown_files
389
+ ).validate!
390
+ puts "[kettle-pre-release] Markdown references: #{report.reference_count} references, #{report.local_target_count} local targets, #{report.file_count} files."
391
+ emit_pre_release_event(
392
+ action: "markdown_references",
393
+ status: "ok",
394
+ files: report.file_count,
395
+ references: report.reference_count,
396
+ local_targets: report.local_target_count,
397
+ failures: 0
398
+ )
399
+ nil
400
+ end
401
+
402
+ # Check 4: Validate Markdown image links by cached HTTP HEAD/GET.
380
403
  # @return [void]
381
404
  def check_markdown_images_http!
382
- puts "[kettle-pre-release] Check 3: Validate Markdown image links (cached HTTP HEAD, with GET fallback)"
405
+ puts "[kettle-pre-release] Check 4: Validate Markdown image links (cached HTTP HEAD, with GET fallback)"
383
406
  urls = Markdown.extract_image_urls_from_files
384
407
  puts "[kettle-pre-release] Found #{urls.size} unique image URL(s)."
385
408
  skipped = []
@@ -219,5 +219,7 @@ begin
219
219
  end
220
220
  end
221
221
  rescue LoadError
222
+ # simplecov:disable -- Appraisal is an optional development dependency.
222
223
  warn("[kettle-dev][appraisal.rake] failed to load appraisal/tasks") if Kettle::Dev::DEBUGGING
224
+ # simplecov:enable
223
225
  end
@@ -27,10 +27,12 @@ else
27
27
  Kettle::Dev.register_default("bundle:audit:update")
28
28
  Kettle::Dev.register_default("bundle:audit")
29
29
  rescue LoadError
30
+ # simplecov:disable -- bundler-audit is an optional development dependency.
30
31
  warn("[kettle-dev][bundle_audit.rake] failed to load bundle/audit/task") if Kettle::Dev::DEBUGGING
31
32
  define_bundle_audit_stub_tasks.call(
32
33
  "(stub)",
33
34
  ->(_task_name) { "NOTE: bundler-audit isn't installed, or is disabled for #{RUBY_VERSION} in the current environment" }
34
35
  )
36
+ # simplecov:enable
35
37
  end
36
38
  end
@@ -31,9 +31,11 @@ begin
31
31
  end
32
32
  Kettle::Dev.register_default("reek:update") unless Kettle::Dev::IS_CI
33
33
  rescue LoadError
34
+ # simplecov:disable -- Reek is an optional development dependency.
34
35
  warn("[kettle-dev][reek.rake] failed to load reek/rake/task") if Kettle::Dev::DEBUGGING
35
36
  desc("(stub) reek is unavailable")
36
37
  task(:reek) do
37
38
  warn("NOTE: reek isn't installed, or is disabled for #{RUBY_VERSION} in the current environment")
38
39
  end
40
+ # simplecov:enable
39
41
  end
@@ -3,5 +3,7 @@
3
3
  begin
4
4
  require "require_bench/tasks" if Kettle::Dev::REQUIRE_BENCH
5
5
  rescue LoadError
6
+ # simplecov:disable -- require_bench is an opt-in development dependency.
6
7
  warn("[kettle-dev][require_bench.rake] failed to load require_bench/tasks") if Kettle::Dev::DEBUGGING
8
+ # simplecov:enable
7
9
  end
@@ -7,5 +7,7 @@ begin
7
7
  t.options = ["--debug"]
8
8
  end
9
9
  rescue LoadError
10
+ # simplecov:disable -- RuboCop Gradual is an optional development dependency.
10
11
  warn("[kettle-dev][rubocop_gradual.rake] failed to load rubocop/gradual/rake_task") if Kettle::Dev::DEBUGGING
12
+ # simplecov:enable
11
13
  end
@@ -24,6 +24,7 @@ begin
24
24
  Kettle::Dev.register_default("test") unless Kettle::Dev.default_registered?("coverage")
25
25
  end
26
26
  rescue LoadError
27
+ # simplecov:disable -- Rake's optional test task integration varies by runtime.
27
28
  warn("[kettle-dev][spec_test.rake] failed to load rake/testtask") if Kettle::Dev::DEBUGGING
28
29
  unless Rake::Task.task_defined?(:test)
29
30
  desc("test task stub")
@@ -31,6 +32,7 @@ rescue LoadError
31
32
  warn("NOTE: minitest isn't installed, or is disabled for #{RUBY_VERSION} in the current environment")
32
33
  end
33
34
  end
35
+ # simplecov:enable
34
36
  end
35
37
 
36
38
  setup_spec_task = ->(default:) {
@@ -14,20 +14,26 @@ begin
14
14
  begin
15
15
  require "yard/fence"
16
16
  Yard::Fence.install_rake_tasks!(:yard)
17
+ # simplecov:disable -- optional plugin availability is environment-specific.
17
18
  rescue LoadError
18
19
  # yard-fence not available - that's fine
20
+ # simplecov:enable
19
21
  end
20
22
 
21
23
  begin
22
24
  require "yard/timekeeper"
23
25
  Yard::Timekeeper.install_rake_tasks!(:yard)
26
+ # simplecov:disable -- optional plugin availability is environment-specific.
24
27
  rescue LoadError
25
28
  # yard-timekeeper not available - that's fine
29
+ # simplecov:enable
26
30
  end
27
31
 
28
32
  namespace :yard do
29
33
  desc "Lint YARD Documentation"
30
34
  task :lint do
35
+ next unless File.file?(".yard-lint.yml")
36
+
31
37
  # Keep warning-only lint runs compact in default/release flows, but rerun
32
38
  # with full output when lint fails so the blocking diagnostics are visible.
33
39
  sh("bundle", "exec", "yard-lint", "lib") unless system("bundle", "exec", "yard-lint", "--quiet", "lib")
@@ -39,10 +45,12 @@ begin
39
45
  Kettle::Dev.register_default("yard:lint")
40
46
  Kettle::Dev.register_default("yard")
41
47
  rescue LoadError
48
+ # simplecov:disable -- YARD is an optional development dependency.
42
49
  warn("[kettle-dev][yard.rake] failed to load yard") if Kettle::Dev::DEBUGGING
43
50
  desc("(stub) yard is unavailable")
44
51
  task(:yard) do # rubocop:disable Rake/DuplicateTask -- fallback when the real YARD task cannot load
45
52
  warn("NOTE: yard isn't installed, or is disabled for #{RUBY_VERSION} in the current environment")
46
53
  end
47
54
  Kettle::Dev.register_default("yard")
55
+ # simplecov:enable
48
56
  end
@@ -5,7 +5,7 @@ module Kettle
5
5
  # Version namespace for this gem.
6
6
  module Version
7
7
  # Current gem version.
8
- VERSION = "2.5.18"
8
+ VERSION = "2.5.20"
9
9
  end
10
10
  # Current gem version exposed at the traditional constant location.
11
11
  VERSION = Version::VERSION # Traditional Constant Location
data/lib/kettle/dev.rb CHANGED
@@ -44,6 +44,7 @@ module Kettle
44
44
  autoload :InputAdapter, "kettle/dev/input_adapter"
45
45
  autoload :InteractiveReleaseCommand, "kettle/dev/interactive_release_command"
46
46
  autoload :LockfileReset, "kettle/dev/lockfile_reset"
47
+ autoload :MarkdownReferenceValidator, "kettle/dev/markdown_reference_validator"
47
48
  autoload :ReadmeBackers, "kettle/dev/readme_backers"
48
49
  autoload :OpenCollectiveConfig, "kettle/dev/open_collective_config"
49
50
  autoload :ReleaseNotifier, "kettle/dev/release_notifier"
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.5.18
4
+ version: 2.5.20
5
5
  platform: ruby
6
6
  authors:
7
7
  - Peter H. Boling
@@ -370,6 +370,7 @@ files:
370
370
  - lib/kettle/dev/input_adapter.rb
371
371
  - lib/kettle/dev/interactive_release_command.rb
372
372
  - lib/kettle/dev/lockfile_reset.rb
373
+ - lib/kettle/dev/markdown_reference_validator.rb
373
374
  - lib/kettle/dev/open_collective_config.rb
374
375
  - lib/kettle/dev/pre_release_cli.rb
375
376
  - lib/kettle/dev/rakelib/appraisal.rake
@@ -401,10 +402,10 @@ licenses:
401
402
  - AGPL-3.0-only
402
403
  metadata:
403
404
  homepage_uri: https://kettle-dev.galtzo.com
404
- source_code_uri: https://github.com/kettle-dev/kettle-dev/tree/v2.5.18
405
- changelog_uri: https://github.com/kettle-dev/kettle-dev/blob/v2.5.18/CHANGELOG.md
405
+ source_code_uri: https://github.com/kettle-dev/kettle-dev/tree/v2.5.20
406
+ changelog_uri: https://github.com/kettle-dev/kettle-dev/blob/v2.5.20/CHANGELOG.md
406
407
  bug_tracker_uri: https://github.com/kettle-dev/kettle-dev/issues
407
- documentation_uri: https://www.rubydoc.info/gems/kettle-dev/2.5.18
408
+ documentation_uri: https://www.rubydoc.info/gems/kettle-dev/2.5.20
408
409
  funding_uri: https://github.com/sponsors/pboling
409
410
  wiki_uri: https://github.com/kettle-dev/kettle-dev/wiki
410
411
  news_uri: https://www.railsbling.com/tags/kettle-dev
metadata.gz.sig CHANGED
@@ -1 +1,2 @@
1
- ^��9����?˶r��mN�[��(�N����a?��-
1
+ �~5
2
+ ac��Nz�k�V��)���S����@���5��ȥ:���ZW�@��W���BnF�oc}�7"ẃ7RY��V�Qu:*T{\֎�κX��ܲ�wƺR�Yt�͜rr"�0�r����|��'��� �Q�{���u��-�