kettle-dev 1.0.1 → 1.0.3

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 (67) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/.devcontainer/devcontainer.json +26 -0
  4. data/.envrc +42 -0
  5. data/.git-hooks/commit-msg +41 -0
  6. data/.git-hooks/commit-subjects-goalie.txt +8 -0
  7. data/.git-hooks/footer-template.erb.txt +16 -0
  8. data/.git-hooks/prepare-commit-msg +20 -0
  9. data/.github/FUNDING.yml +13 -0
  10. data/.github/dependabot.yml +11 -0
  11. data/.github/workflows/ancient.yml +80 -0
  12. data/.github/workflows/auto-assign.yml +21 -0
  13. data/.github/workflows/codeql-analysis.yml +70 -0
  14. data/.github/workflows/coverage.yml +130 -0
  15. data/.github/workflows/current.yml +88 -0
  16. data/.github/workflows/dependency-review.yml +20 -0
  17. data/.github/workflows/discord-notifier.yml +38 -0
  18. data/.github/workflows/heads.yml +87 -0
  19. data/.github/workflows/jruby.yml +79 -0
  20. data/.github/workflows/legacy.yml +70 -0
  21. data/.github/workflows/locked_deps.yml +88 -0
  22. data/.github/workflows/opencollective.yml +40 -0
  23. data/.github/workflows/style.yml +67 -0
  24. data/.github/workflows/supported.yml +85 -0
  25. data/.github/workflows/truffle.yml +78 -0
  26. data/.github/workflows/unlocked_deps.yml +87 -0
  27. data/.github/workflows/unsupported.yml +78 -0
  28. data/.gitignore +48 -0
  29. data/.gitlab-ci.yml +45 -0
  30. data/.junie/guidelines-rbs.md +49 -0
  31. data/.junie/guidelines.md +132 -0
  32. data/.opencollective.yml +3 -0
  33. data/.qlty/qlty.toml +79 -0
  34. data/.rspec +8 -0
  35. data/.rubocop.yml +13 -0
  36. data/.simplecov +7 -0
  37. data/.tool-versions +1 -0
  38. data/.yard_gfm_support.rb +22 -0
  39. data/.yardopts +11 -0
  40. data/Appraisal.root.gemfile +12 -0
  41. data/Appraisals +120 -0
  42. data/CHANGELOG.md +26 -1
  43. data/Gemfile +32 -0
  44. data/Rakefile +99 -0
  45. data/checksums/kettle-dev-1.0.2.gem.sha256 +1 -0
  46. data/checksums/kettle-dev-1.0.2.gem.sha512 +1 -0
  47. data/checksums/kettle-dev-1.0.3.gem.sha256 +1 -0
  48. data/checksums/kettle-dev-1.0.3.gem.sha512 +1 -0
  49. data/exe/kettle-release +2 -3
  50. data/gemfiles/modular/coverage.gemfile +6 -0
  51. data/gemfiles/modular/documentation.gemfile +11 -0
  52. data/gemfiles/modular/style.gemfile +16 -0
  53. data/lib/kettle/dev/rakelib/appraisal.rake +40 -0
  54. data/lib/kettle/dev/rakelib/bench.rake +58 -0
  55. data/lib/kettle/dev/rakelib/bundle_audit.rake +18 -0
  56. data/lib/kettle/dev/rakelib/ci.rake +348 -0
  57. data/lib/kettle/dev/rakelib/install.rake +304 -0
  58. data/lib/kettle/dev/rakelib/reek.rake +34 -0
  59. data/lib/kettle/dev/rakelib/require_bench.rake +7 -0
  60. data/lib/kettle/dev/rakelib/rubocop_gradual.rake +9 -0
  61. data/lib/kettle/dev/rakelib/spec_test.rake +42 -0
  62. data/lib/kettle/dev/rakelib/template.rake +461 -0
  63. data/lib/kettle/dev/rakelib/yard.rake +33 -0
  64. data/lib/kettle/dev/version.rb +1 -1
  65. data.tar.gz.sig +0 -0
  66. metadata +67 -4
  67. metadata.gz.sig +0 -0
@@ -0,0 +1,9 @@
1
+ begin
2
+ require "rubocop/gradual/rake_task"
3
+
4
+ RuboCop::Gradual::RakeTask.new(:rubocop_gradual_debug) do |t|
5
+ t.options = ["--debug"]
6
+ end
7
+ rescue LoadError
8
+ warn("[kettle-dev][rubocop_gradual.rake] failed to load rubocop/gradual/rake_task") if Kettle::Dev::DEBUGGING
9
+ end
@@ -0,0 +1,42 @@
1
+ # Setup RSpec
2
+ begin
3
+ require "rspec/core/rake_task"
4
+
5
+ RSpec::Core::RakeTask.new(:spec)
6
+ # This takes the place of `coverage` task when running as CI=true
7
+ Kettle::Dev.register_default("spec") if Kettle::Dev::IS_CI
8
+ rescue LoadError
9
+ warn("[kettle-dev][spec_test.rake] failed to load rspec/core/rake_task") if Kettle::Dev::DEBUGGING
10
+ desc("spec task stub")
11
+ task(:spec) do
12
+ warn("NOTE: rspec isn't installed, or is disabled for #{RUBY_VERSION} in the current environment")
13
+ end
14
+ end
15
+
16
+ # Setup MiniTest
17
+ begin
18
+ require "rake/testtask"
19
+
20
+ Rake::TestTask.new(:test) do |t|
21
+ t.test_files = FileList["tests/**/test_*.rb"]
22
+ end
23
+ rescue LoadError
24
+ warn("[kettle-dev][spec_test.rake] failed to load rake/testtask") if Kettle::Dev::DEBUGGING
25
+ desc("test task stub")
26
+ task(:test) do
27
+ warn("NOTE: minitest isn't installed, or is disabled for #{RUBY_VERSION} in the current environment")
28
+ end
29
+ end
30
+
31
+ # rubocop:disable Rake/DuplicateTask
32
+ if Rake::Task.task_defined?("spec") && !Rake::Task.task_defined?("test")
33
+ desc "run spec task with test task"
34
+ task test: :spec
35
+ elsif !Rake::Task.task_defined?("spec") && Rake::Task.task_defined?("test")
36
+ desc "run test task with spec task"
37
+ task spec: :test
38
+ else
39
+ # Add spec as pre-requisite to 'test'
40
+ Rake::Task[:test].enhance(["spec"])
41
+ end
42
+ # rubocop:enable Rake/DuplicateTask
@@ -0,0 +1,461 @@
1
+ namespace :kettle do
2
+ namespace :dev do
3
+ # New template task split from kettle:dev:install
4
+ # Copies and replaces files pulled from this gem checkout into the host project.
5
+ # This task can be run independently of `kettle:dev:install`.
6
+ desc "Template kettle-dev files into the current project"
7
+ task :template do
8
+ require "fileutils"
9
+ require "kettle/dev/template_helpers"
10
+
11
+ helpers = Kettle::Dev::TemplateHelpers
12
+
13
+ project_root = helpers.project_root
14
+ gem_checkout_root = helpers.gem_checkout_root
15
+
16
+ # Ensure git working tree is clean before making changes (when run standalone)
17
+ helpers.ensure_clean_git!(root: project_root, task_label: "kettle:dev:template")
18
+
19
+ meta = helpers.gemspec_metadata(project_root)
20
+ gem_name = meta[:gem_name]
21
+ min_ruby = meta[:min_ruby]
22
+ gh_org = meta[:gh_org]
23
+ entrypoint_require = meta[:entrypoint_require]
24
+ namespace = meta[:namespace]
25
+ namespace_shield = meta[:namespace_shield]
26
+ gem_shield = meta[:gem_shield]
27
+
28
+ # 1) .devcontainer directory
29
+ helpers.copy_dir_with_prompt(File.join(gem_checkout_root, ".devcontainer"), File.join(project_root, ".devcontainer"))
30
+
31
+ # 2) .github/**/*.yml with FUNDING.yml customizations
32
+ source_github_dir = File.join(gem_checkout_root, ".github")
33
+ if Dir.exist?(source_github_dir)
34
+ Dir.glob(File.join(source_github_dir, "**", "*.yml")).each do |src|
35
+ src = helpers.prefer_example(src)
36
+ rel = src.sub(/^#{Regexp.escape(gem_checkout_root)}\/?/, "")
37
+ dest = File.join(project_root, rel)
38
+ if File.basename(src).sub(/\.example\z/, "") == "FUNDING.yml"
39
+ helpers.copy_file_with_prompt(src, dest, allow_create: true, allow_replace: true) do |content|
40
+ c = content.dup
41
+ c = c.gsub(/^open_collective:\s+.*$/i) { |line| gh_org ? "open_collective: #{gh_org}" : line }
42
+ if gem_name && !gem_name.empty?
43
+ c = c.gsub(/^tidelift:\s+.*$/i, "tidelift: rubygems/#{gem_name}")
44
+ end
45
+ # Also apply common replacements for org/gem/namespace/shields
46
+ helpers.apply_common_replacements(
47
+ c,
48
+ gh_org: gh_org,
49
+ gem_name: gem_name,
50
+ namespace: namespace,
51
+ namespace_shield: namespace_shield,
52
+ gem_shield: gem_shield,
53
+ )
54
+ end
55
+ else
56
+ helpers.copy_file_with_prompt(src, dest, allow_create: true, allow_replace: true) do |content|
57
+ helpers.apply_common_replacements(
58
+ content,
59
+ gh_org: gh_org,
60
+ gem_name: gem_name,
61
+ namespace: namespace,
62
+ namespace_shield: namespace_shield,
63
+ gem_shield: gem_shield,
64
+ )
65
+ end
66
+ end
67
+ end
68
+ end
69
+
70
+ # 3) .qlty/qlty.toml
71
+ helpers.copy_file_with_prompt(
72
+ helpers.prefer_example(File.join(gem_checkout_root, ".qlty/qlty.toml")),
73
+ File.join(project_root, ".qlty/qlty.toml"),
74
+ allow_create: true,
75
+ allow_replace: true,
76
+ )
77
+
78
+ # 4) gemfiles/modular/*.gemfile (from gem's gemfiles/modular)
79
+ [%w[coverage.gemfile], %w[documentation.gemfile], %w[style.gemfile]].each do |base|
80
+ src = helpers.prefer_example(File.join(gem_checkout_root, "gemfiles/modular", base[0]))
81
+ dest = File.join(project_root, "gemfiles/modular", base[0])
82
+ if File.basename(src).sub(/\.example\z/, "") == "style.gemfile"
83
+ helpers.copy_file_with_prompt(src, dest, allow_create: true, allow_replace: true) do |content|
84
+ # Adjust rubocop-lts constraint based on min_ruby
85
+ version_map = [
86
+ [Gem::Version.new("1.8"), "~> 0.0"],
87
+ [Gem::Version.new("1.9"), "~> 2.0"],
88
+ [Gem::Version.new("2.0"), "~> 4.0"],
89
+ [Gem::Version.new("2.1"), "~> 6.0"],
90
+ [Gem::Version.new("2.2"), "~> 8.0"],
91
+ [Gem::Version.new("2.3"), "~> 10.0"],
92
+ [Gem::Version.new("2.4"), "~> 12.0"],
93
+ [Gem::Version.new("2.5"), "~> 14.0"],
94
+ [Gem::Version.new("2.6"), "~> 16.0"],
95
+ [Gem::Version.new("2.7"), "~> 18.0"],
96
+ [Gem::Version.new("3.0"), "~> 20.0"],
97
+ [Gem::Version.new("3.1"), "~> 22.0"],
98
+ [Gem::Version.new("3.2"), "~> 24.0"],
99
+ [Gem::Version.new("3.3"), "~> 26.0"],
100
+ [Gem::Version.new("3.4"), "~> 28.0"],
101
+ ]
102
+ new_constraint = nil
103
+ begin
104
+ if min_ruby && !min_ruby.empty?
105
+ v = Gem::Version.new(min_ruby.split(".")[0, 2].join("."))
106
+ version_map.reverse_each do |min, req|
107
+ if v >= min
108
+ new_constraint = req
109
+ break
110
+ end
111
+ end
112
+ end
113
+ rescue StandardError
114
+ # leave nil
115
+ end
116
+ if new_constraint
117
+ content.gsub(/^gem\s+"rubocop-lts",\s*"[^"]+".*$/) do |line|
118
+ # Do not preserve whatever tail was there before
119
+ %(gem "rubocop-lts", "#{new_constraint}")
120
+ end
121
+ else
122
+ content
123
+ end
124
+ end
125
+ else
126
+ helpers.copy_file_with_prompt(src, dest, allow_create: true, allow_replace: true)
127
+ end
128
+ end
129
+
130
+ # 5) spec/spec_helper.rb (no create)
131
+ dest_spec_helper = File.join(project_root, "spec/spec_helper.rb")
132
+ if File.file?(dest_spec_helper)
133
+ old = File.read(dest_spec_helper)
134
+ if old.include?('require "kettle/dev"') || old.include?("require 'kettle/dev'")
135
+ replacement = %(require "#{entrypoint_require}")
136
+ new_content = old.gsub(/require\s+["']kettle\/dev["']/, replacement)
137
+ if new_content != old
138
+ if helpers.ask("Replace require \"kettle/dev\" in spec/spec_helper.rb with #{replacement}?", true)
139
+ helpers.write_file(dest_spec_helper, new_content)
140
+ puts "Updated require in spec/spec_helper.rb"
141
+ else
142
+ puts "Skipped modifying spec/spec_helper.rb"
143
+ end
144
+ end
145
+ end
146
+ end
147
+
148
+ # 6) .env.local special case: never overwrite project .env.local; copy template as .env.local.example
149
+ begin
150
+ envlocal_src = helpers.prefer_example(File.join(gem_checkout_root, ".env.local"))
151
+ envlocal_dest = File.join(project_root, ".env.local.example")
152
+ if File.exist?(envlocal_src)
153
+ helpers.copy_file_with_prompt(envlocal_src, envlocal_dest, allow_create: true, allow_replace: true)
154
+ end
155
+ rescue StandardError => e
156
+ puts "WARNING: Skipped .env.local example copy due to #{e.class}: #{e.message}"
157
+ end
158
+
159
+ # 7) Root and other files
160
+ files_to_copy = %w[
161
+ .envrc
162
+ .gitignore
163
+ .gitlab-ci.yml
164
+ .rspec
165
+ .rubocop.yml
166
+ .simplecov
167
+ .tool-versions
168
+ .yard_gfm_support.rb
169
+ .yardopts
170
+ .opencollective.yml
171
+ Appraisal.root.gemfile
172
+ Appraisals
173
+ CHANGELOG.md
174
+ CITATION.cff
175
+ CODE_OF_CONDUCT.md
176
+ CONTRIBUTING.md
177
+ Gemfile
178
+ Rakefile
179
+ README.md
180
+ RUBOCOP.md
181
+ SECURITY.md
182
+ .junie/guidelines.md
183
+ .junie/guidelines-rbs.md
184
+ ]
185
+
186
+ files_to_copy.each do |rel|
187
+ src = helpers.prefer_example(File.join(gem_checkout_root, rel))
188
+ dest = File.join(project_root, rel)
189
+ next unless File.exist?(src)
190
+ if File.basename(rel) == "README.md"
191
+ helpers.copy_file_with_prompt(src, dest, allow_create: true, allow_replace: true) do |content|
192
+ # 1) Do token replacements on the template content (org/gem/namespace/shields)
193
+ c = helpers.apply_common_replacements(
194
+ content,
195
+ gh_org: gh_org,
196
+ gem_name: gem_name,
197
+ namespace: namespace,
198
+ namespace_shield: namespace_shield,
199
+ gem_shield: gem_shield,
200
+ )
201
+
202
+ # 2) Merge specific sections from destination README, if present
203
+ begin
204
+ dest_existing = File.exist?(dest) ? File.read(dest) : nil
205
+
206
+ # Helper to parse markdown sections at any heading level (#, ##, ###, ...)
207
+ parse_sections = lambda do |md|
208
+ sections = []
209
+ return sections unless md
210
+ lines = md.split("\n", -1) # keep trailing empty lines
211
+ indices = []
212
+ lines.each_with_index do |ln, i|
213
+ indices << i if ln =~ /^#+\s+.+/
214
+ end
215
+ indices << lines.length
216
+ indices.each_cons(2) do |start_i, nxt|
217
+ heading = lines[start_i]
218
+ body_lines = lines[(start_i + 1)...nxt] || []
219
+ title = heading.sub(/^#+\s+/, "")
220
+ # Normalize by removing leading emoji/non-alnum and extra spaces
221
+ base = title.sub(/\A[^\p{Alnum}]+/u, "").strip.downcase
222
+ sections << {start: start_i, stop: nxt - 1, heading: heading, body: body_lines.join("\n"), base: base}
223
+ end
224
+ {lines: lines, sections: sections}
225
+ end
226
+
227
+ # Parse src (c) and dest
228
+ src_parsed = parse_sections.call(c)
229
+ dest_parsed = parse_sections.call(dest_existing)
230
+
231
+ # Build lookup for destination sections by base title
232
+ dest_lookup = {}
233
+ if dest_parsed && dest_parsed[:sections]
234
+ dest_parsed[:sections].each do |s|
235
+ dest_lookup[s[:base]] = s[:body]
236
+ end
237
+ end
238
+
239
+ # Build targets to merge: existing curated list plus any NOTE sections at any level
240
+ note_bases = []
241
+ if src_parsed && src_parsed[:sections]
242
+ note_bases = src_parsed[:sections]
243
+ .select { |s| s[:heading] =~ /^#+\s+note:.*/i }
244
+ .map { |s| s[:base] }
245
+ end
246
+ targets = ["synopsis", "configuration", "basic usage"] + note_bases
247
+
248
+ # Replace matching sections in src
249
+ if src_parsed && src_parsed[:sections] && !src_parsed[:sections].empty?
250
+ lines = src_parsed[:lines].dup
251
+ # Iterate over src sections; when base is in targets, rewrite its body
252
+ src_parsed[:sections].reverse_each do |sec|
253
+ next unless targets.include?(sec[:base])
254
+ new_body = dest_lookup.fetch(sec[:base], "\n\n")
255
+ new_block = [sec[:heading], new_body].join("\n")
256
+ # Replace the range from start+0 to stop with new_block lines
257
+ range_start = sec[:start]
258
+ range_end = sec[:stop]
259
+ # Remove old range
260
+ lines.slice!(range_start..range_end)
261
+ # Insert new block (split preserves potential empty tail)
262
+ insert_lines = new_block.split("\n", -1)
263
+ lines.insert(range_start, *insert_lines)
264
+ end
265
+ c = lines.join("\n")
266
+ end
267
+
268
+ # 3) Preserve first H1 emojis from destination README, if any
269
+ begin
270
+ require "kettle/emoji_regex"
271
+ emoji_re = Kettle::EmojiRegex::REGEX
272
+
273
+ dest_emojis = nil
274
+ if dest_existing
275
+ first_h1_dest = dest_existing.lines.find { |ln| ln =~ /^#\s+/ }
276
+ if first_h1_dest
277
+ after = first_h1_dest.sub(/^#\s+/, "")
278
+ emojis = +""
279
+ while after =~ /\A#{emoji_re.source}/u
280
+ # Capture the entire grapheme cluster for the emoji (handles VS16/ZWJ sequences)
281
+ cluster = after[/\A\X/u]
282
+ emojis << cluster
283
+ after = after[cluster.length..-1].to_s
284
+ end
285
+ dest_emojis = emojis unless emojis.empty?
286
+ end
287
+ end
288
+
289
+ if dest_emojis && !dest_emojis.empty?
290
+ lines_new = c.split("\n", -1)
291
+ idx = lines_new.index { |ln| ln =~ /^#\s+/ }
292
+ if idx
293
+ rest = lines_new[idx].sub(/^#\s+/, "")
294
+ # Remove any leading emojis from the H1 by peeling full grapheme clusters
295
+ rest_wo_emoji = begin
296
+ tmp = rest.dup
297
+ while tmp =~ /\A#{emoji_re.source}/u
298
+ cluster = tmp[/\A\X/u]
299
+ tmp = tmp[cluster.length..-1].to_s
300
+ end
301
+ tmp.sub(/\A\s+/, "")
302
+ end
303
+ lines_new[idx] = ["#", dest_emojis, rest_wo_emoji].join(" ").gsub(/\s+/, " ").sub(/^#\s+/, "# ")
304
+ c = lines_new.join("\n")
305
+ end
306
+ end
307
+ rescue StandardError
308
+ # ignore emoji preservation errors
309
+ end
310
+ rescue StandardError
311
+ # Best effort; if anything fails, keep c as-is
312
+ end
313
+
314
+ c
315
+ end
316
+ elsif ["CHANGELOG.md", "CITATION.cff", "CONTRIBUTING.md", ".opencollective.yml", ".junie/guidelines.md"].include?(rel)
317
+ helpers.copy_file_with_prompt(src, dest, allow_create: true, allow_replace: true) do |content|
318
+ helpers.apply_common_replacements(
319
+ content,
320
+ gh_org: gh_org,
321
+ gem_name: gem_name,
322
+ namespace: namespace,
323
+ namespace_shield: namespace_shield,
324
+ gem_shield: gem_shield,
325
+ )
326
+ end
327
+ else
328
+ helpers.copy_file_with_prompt(src, dest, allow_create: true, allow_replace: true)
329
+ end
330
+ end
331
+
332
+ # After creating or replacing .envrc or .env.local.example, require review and exit unless allowed
333
+ begin
334
+ envrc_path = File.join(project_root, ".envrc")
335
+ envlocal_example_path = File.join(project_root, ".env.local.example")
336
+ changed_env_files = []
337
+ changed_env_files << envrc_path if helpers.modified_by_template?(envrc_path)
338
+ changed_env_files << envlocal_example_path if helpers.modified_by_template?(envlocal_example_path)
339
+ if !changed_env_files.empty?
340
+ if ENV.fetch("allowed", "").to_s =~ /\A(1|true|y|yes)\z/i
341
+ puts "Detected updates to #{changed_env_files.map { |p| File.basename(p) }.join(" and ")}. Proceeding because allowed=true."
342
+ else
343
+ puts
344
+ puts "IMPORTANT: The following environment-related files were created/updated:"
345
+ changed_env_files.each { |p| puts " - #{p}" }
346
+ puts
347
+ puts "Please review these files. If .envrc changed, run:"
348
+ puts " direnv allow"
349
+ puts
350
+ puts "After that, re-run to resume:"
351
+ puts " bundle exec rake kettle:dev:template allowed=true"
352
+ puts " # or to run the full install afterwards:"
353
+ puts " bundle exec rake kettle:dev:install allowed=true"
354
+ abort("Aborting: review of environment files required before continuing.")
355
+ end
356
+ end
357
+ rescue StandardError => e
358
+ puts "WARNING: Could not determine env file changes: #{e.class}: #{e.message}"
359
+ end
360
+
361
+ # Handle .git-hooks files
362
+ # - Two template files (.txt) are offered with a location prompt (local/global/skip).
363
+ # - Two hook scripts are always copied to both local and global locations by default;
364
+ # if they already exist, ask before overwriting.
365
+ source_hooks_dir = File.join(gem_checkout_root, ".git-hooks")
366
+ if Dir.exist?(source_hooks_dir)
367
+ goalie_src = File.join(source_hooks_dir, "commit-subjects-goalie.txt")
368
+ footer_src = File.join(source_hooks_dir, "footer-template.erb.txt")
369
+ hook_ruby_src = File.join(source_hooks_dir, "commit-msg")
370
+ hook_sh_src = File.join(source_hooks_dir, "prepare-commit-msg")
371
+
372
+ # First: templates (.txt) — keep the existing single question which applies only to these
373
+ if File.file?(goalie_src) && File.file?(footer_src)
374
+ puts
375
+ puts "Git hooks templates found:"
376
+ puts " - #{goalie_src}"
377
+ puts " - #{footer_src}"
378
+ puts
379
+ puts "About these files:"
380
+ puts "- commit-subjects-goalie.txt:"
381
+ puts " Lists commit subject prefixes to look for; if a commit subject starts with any listed prefix,"
382
+ puts " kettle-commit-msg will append a footer to the commit message (when GIT_HOOK_FOOTER_APPEND=true)."
383
+ puts " Defaults include release prep (🔖 Prepare release v) and checksum commits (🔒️ Checksums for v)."
384
+ puts "- footer-template.erb.txt:"
385
+ puts " ERB template rendered to produce the footer. You can customize its contents and variables."
386
+ puts
387
+ puts "Where would you like to install these two templates?"
388
+ puts " [l] Local to this project (#{File.join(project_root, ".git-hooks")})"
389
+ puts " [g] Global for this user (#{File.join(ENV["HOME"], ".git-hooks")})"
390
+ puts " [s] Skip copying"
391
+ print "Choose (l/g/s) [l]: "
392
+ choice = $stdin.gets&.strip
393
+ choice = "l" if choice.nil? || choice.empty?
394
+ dest_dir = case choice.downcase
395
+ when "g", "global" then File.join(ENV["HOME"], ".git-hooks")
396
+ when "s", "skip" then nil
397
+ else File.join(project_root, ".git-hooks")
398
+ end
399
+
400
+ if dest_dir
401
+ FileUtils.mkdir_p(dest_dir)
402
+ [[goalie_src, "commit-subjects-goalie.txt"], [footer_src, "footer-template.erb.txt"]].each do |src, base|
403
+ dest = File.join(dest_dir, base)
404
+ # Use helpers to allow create/replace prompts for these files (question applies to them)
405
+ helpers.copy_file_with_prompt(src, dest, allow_create: true, allow_replace: true)
406
+ # Ensure readable (0644). These are data/templates, not executables.
407
+ begin
408
+ File.chmod(0o644, dest) if File.exist?(dest)
409
+ rescue StandardError
410
+ # ignore permission issues
411
+ end
412
+ end
413
+ else
414
+ puts "Skipping copy of .git-hooks templates."
415
+ end
416
+ end
417
+
418
+ # Second: hook scripts — copy only to the local project; prompt only on overwrite
419
+ # Per requirements: do not install hook scripts to global ~/.git-hooks
420
+ hook_dests = [File.join(project_root, ".git-hooks")]
421
+ hook_pairs = [[hook_ruby_src, "commit-msg", 0o755], [hook_sh_src, "prepare-commit-msg", 0o755]]
422
+ hook_pairs.each do |src, base, mode|
423
+ next unless File.file?(src)
424
+ hook_dests.each do |dstdir|
425
+ begin
426
+ FileUtils.mkdir_p(dstdir)
427
+ dest = File.join(dstdir, base)
428
+ # Create without prompt if missing; if exists, ask to replace
429
+ if File.exist?(dest)
430
+ # ask to overwrite
431
+ if helpers.ask("Overwrite existing #{dest}?", true)
432
+ content = File.read(src)
433
+ helpers.write_file(dest, content)
434
+ begin
435
+ File.chmod(mode, dest)
436
+ rescue StandardError
437
+ # ignore permission issues
438
+ end
439
+ puts "Replaced #{dest}"
440
+ else
441
+ puts "Kept existing #{dest}"
442
+ end
443
+ else
444
+ content = File.read(src)
445
+ helpers.write_file(dest, content)
446
+ begin
447
+ File.chmod(mode, dest)
448
+ rescue StandardError
449
+ # ignore permission issues
450
+ end
451
+ puts "Installed #{dest}"
452
+ end
453
+ rescue StandardError => e
454
+ puts "WARNING: Could not install hook #{base} to #{dstdir}: #{e.class}: #{e.message}"
455
+ end
456
+ end
457
+ end
458
+ end
459
+ end
460
+ end
461
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Setup Yard
4
+ begin
5
+ require "yard"
6
+
7
+ YARD::Rake::YardocTask.new(:yard) do |t|
8
+ t.files = [
9
+ # Source Splats (alphabetical)
10
+ "lib/**/*.rb",
11
+ "-", # source and extra docs are separated by "-"
12
+ # Extra Files (alphabetical)
13
+ "*.cff",
14
+ "*.md",
15
+ "*.txt",
16
+ "checksums/**/*.sha256",
17
+ "checksums/**/*.sha512",
18
+ "REEK",
19
+ "sig/**/*.rbs",
20
+ ]
21
+
22
+ # No need for this, due to plugin load in .yardopts
23
+ # require "yard-junk/rake"
24
+ # YardJunk::Rake.define_task
25
+ end
26
+ Kettle::Dev.register_default("yard")
27
+ rescue LoadError
28
+ warn("[kettle-dev][yard.rake] failed to load yard") if Kettle::Dev::DEBUGGING
29
+ desc("(stub) yard is unavailable")
30
+ task(:yard) do
31
+ warn("NOTE: yard isn't installed, or is disabled for #{RUBY_VERSION} in the current environment")
32
+ end
33
+ end
@@ -6,7 +6,7 @@ module Kettle
6
6
  module Version
7
7
  # The gem version.
8
8
  # @return [String]
9
- VERSION = "1.0.1"
9
+ VERSION = "1.0.3"
10
10
  end
11
11
  end
12
12
  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.1
4
+ version: 1.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Peter H. Boling
@@ -207,26 +207,89 @@ extra_rdoc_files:
207
207
  - checksums/kettle-dev-1.0.0.gem.sha512
208
208
  - checksums/kettle-dev-1.0.1.gem.sha256
209
209
  - checksums/kettle-dev-1.0.1.gem.sha512
210
+ - checksums/kettle-dev-1.0.2.gem.sha256
211
+ - checksums/kettle-dev-1.0.2.gem.sha512
212
+ - checksums/kettle-dev-1.0.3.gem.sha256
213
+ - checksums/kettle-dev-1.0.3.gem.sha512
210
214
  files:
215
+ - ".devcontainer/devcontainer.json"
216
+ - ".envrc"
217
+ - ".git-hooks/commit-msg"
218
+ - ".git-hooks/commit-subjects-goalie.txt"
219
+ - ".git-hooks/footer-template.erb.txt"
220
+ - ".git-hooks/prepare-commit-msg"
221
+ - ".github/FUNDING.yml"
222
+ - ".github/dependabot.yml"
223
+ - ".github/workflows/ancient.yml"
224
+ - ".github/workflows/auto-assign.yml"
225
+ - ".github/workflows/codeql-analysis.yml"
226
+ - ".github/workflows/coverage.yml"
227
+ - ".github/workflows/current.yml"
228
+ - ".github/workflows/dependency-review.yml"
229
+ - ".github/workflows/discord-notifier.yml"
230
+ - ".github/workflows/heads.yml"
231
+ - ".github/workflows/jruby.yml"
232
+ - ".github/workflows/legacy.yml"
233
+ - ".github/workflows/locked_deps.yml"
234
+ - ".github/workflows/opencollective.yml"
235
+ - ".github/workflows/style.yml"
236
+ - ".github/workflows/supported.yml"
237
+ - ".github/workflows/truffle.yml"
238
+ - ".github/workflows/unlocked_deps.yml"
239
+ - ".github/workflows/unsupported.yml"
240
+ - ".gitignore"
241
+ - ".gitlab-ci.yml"
242
+ - ".junie/guidelines-rbs.md"
243
+ - ".junie/guidelines.md"
244
+ - ".opencollective.yml"
245
+ - ".qlty/qlty.toml"
246
+ - ".rspec"
247
+ - ".rubocop.yml"
248
+ - ".simplecov"
249
+ - ".tool-versions"
250
+ - ".yard_gfm_support.rb"
251
+ - ".yardopts"
252
+ - Appraisal.root.gemfile
253
+ - Appraisals
211
254
  - CHANGELOG.md
212
255
  - CITATION.cff
213
256
  - CODE_OF_CONDUCT.md
214
257
  - CONTRIBUTING.md
258
+ - Gemfile
215
259
  - LICENSE.txt
216
260
  - README.md
217
261
  - REEK
218
262
  - RUBOCOP.md
263
+ - Rakefile
219
264
  - SECURITY.md
220
265
  - checksums/kettle-dev-1.0.0.gem.sha256
221
266
  - checksums/kettle-dev-1.0.0.gem.sha512
222
267
  - checksums/kettle-dev-1.0.1.gem.sha256
223
268
  - checksums/kettle-dev-1.0.1.gem.sha512
269
+ - checksums/kettle-dev-1.0.2.gem.sha256
270
+ - checksums/kettle-dev-1.0.2.gem.sha512
271
+ - checksums/kettle-dev-1.0.3.gem.sha256
272
+ - checksums/kettle-dev-1.0.3.gem.sha512
224
273
  - exe/kettle-commit-msg
225
274
  - exe/kettle-readme-backers
226
275
  - exe/kettle-release
276
+ - gemfiles/modular/coverage.gemfile
277
+ - gemfiles/modular/documentation.gemfile
278
+ - gemfiles/modular/style.gemfile
227
279
  - lib/kettle-dev.rb
228
280
  - lib/kettle/dev.rb
229
281
  - lib/kettle/dev/ci_helpers.rb
282
+ - lib/kettle/dev/rakelib/appraisal.rake
283
+ - lib/kettle/dev/rakelib/bench.rake
284
+ - lib/kettle/dev/rakelib/bundle_audit.rake
285
+ - lib/kettle/dev/rakelib/ci.rake
286
+ - lib/kettle/dev/rakelib/install.rake
287
+ - lib/kettle/dev/rakelib/reek.rake
288
+ - lib/kettle/dev/rakelib/require_bench.rake
289
+ - lib/kettle/dev/rakelib/rubocop_gradual.rake
290
+ - lib/kettle/dev/rakelib/spec_test.rake
291
+ - lib/kettle/dev/rakelib/template.rake
292
+ - lib/kettle/dev/rakelib/yard.rake
230
293
  - lib/kettle/dev/tasks.rb
231
294
  - lib/kettle/dev/template_helpers.rb
232
295
  - lib/kettle/dev/version.rb
@@ -239,10 +302,10 @@ licenses:
239
302
  - MIT
240
303
  metadata:
241
304
  homepage_uri: https://kettle-dev.galtzo.com/
242
- source_code_uri: https://github.com/galtzo-floss/kettle-dev/tree/v1.0.1
243
- changelog_uri: https://github.com/galtzo-floss/kettle-dev/blob/v1.0.1/CHANGELOG.md
305
+ source_code_uri: https://github.com/galtzo-floss/kettle-dev/tree/v1.0.3
306
+ changelog_uri: https://github.com/galtzo-floss/kettle-dev/blob/v1.0.3/CHANGELOG.md
244
307
  bug_tracker_uri: https://github.com/galtzo-floss/kettle-dev/issues
245
- documentation_uri: https://www.rubydoc.info/gems/kettle-dev/1.0.1
308
+ documentation_uri: https://www.rubydoc.info/gems/kettle-dev/1.0.3
246
309
  funding_uri: https://github.com/sponsors/pboling
247
310
  wiki_uri: https://github.com/galtzo-floss/kettle-dev/wiki
248
311
  news_uri: https://www.railsbling.com/tags/kettle-dev
metadata.gz.sig CHANGED
Binary file