kettle-dev 1.1.4 → 1.1.5
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 +4 -4
- checksums.yaml.gz.sig +0 -0
- data/.env.local.example +14 -1
- data/.envrc +2 -2
- data/.git-hooks/commit-msg +22 -16
- data/.git-hooks/prepare-commit-msg.example +19 -0
- data/.github/workflows/coverage.yml +2 -2
- data/.junie/guidelines.md +4 -0
- data/.opencollective.yml.example +3 -0
- data/CHANGELOG.md +31 -1
- data/CONTRIBUTING.md +29 -0
- data/FUNDING.md +2 -2
- data/README.md +148 -38
- data/README.md.example +7 -7
- data/Rakefile.example +1 -1
- data/exe/kettle-changelog +7 -2
- data/exe/kettle-commit-msg +20 -5
- data/exe/kettle-dev-setup +2 -1
- data/exe/kettle-dvcs +7 -2
- data/exe/kettle-pre-release +66 -0
- data/exe/kettle-readme-backers +7 -2
- data/exe/kettle-release +9 -2
- data/lib/kettle/dev/changelog_cli.rb +4 -5
- data/lib/kettle/dev/ci_helpers.rb +9 -5
- data/lib/kettle/dev/ci_monitor.rb +229 -8
- data/lib/kettle/dev/gem_spec_reader.rb +105 -39
- data/lib/kettle/dev/git_adapter.rb +6 -3
- data/lib/kettle/dev/git_commit_footer.rb +5 -2
- data/lib/kettle/dev/pre_release_cli.rb +248 -0
- data/lib/kettle/dev/readme_backers.rb +4 -2
- data/lib/kettle/dev/release_cli.rb +27 -17
- data/lib/kettle/dev/tasks/ci_task.rb +112 -22
- data/lib/kettle/dev/tasks/install_task.rb +23 -17
- data/lib/kettle/dev/tasks/template_task.rb +64 -23
- data/lib/kettle/dev/template_helpers.rb +44 -31
- data/lib/kettle/dev/version.rb +1 -1
- data/lib/kettle/dev.rb +5 -0
- data/sig/kettle/dev/ci_monitor.rbs +6 -0
- data/sig/kettle/dev/gem_spec_reader.rbs +8 -5
- data/sig/kettle/dev/pre_release_cli.rbs +20 -0
- data/sig/kettle/dev/template_helpers.rbs +2 -0
- data/sig/kettle/dev.rbs +1 -0
- data.tar.gz.sig +0 -0
- metadata +30 -4
- metadata.gz.sig +0 -0
@@ -55,12 +55,7 @@ module Kettle
|
|
55
55
|
dynamic_files.each { |f| display_code_for[f] = "" }
|
56
56
|
|
57
57
|
status_emoji = proc do |status, conclusion|
|
58
|
-
|
59
|
-
when "queued" then "⏳️"
|
60
|
-
when "in_progress" then "👟"
|
61
|
-
when "completed" then ((conclusion == "success") ? "✅" : "🍅")
|
62
|
-
else "⏳️"
|
63
|
-
end
|
58
|
+
Kettle::Dev::CIMonitor.status_emoji(status, conclusion)
|
64
59
|
end
|
65
60
|
|
66
61
|
fetch_and_print_status = proc do |workflow_file|
|
@@ -98,6 +93,52 @@ module Kettle
|
|
98
93
|
end
|
99
94
|
end
|
100
95
|
|
96
|
+
# Print GitLab pipeline status (if configured) for the current branch.
|
97
|
+
print_gitlab_status = proc do
|
98
|
+
begin
|
99
|
+
branch = Kettle::Dev::CIHelpers.current_branch
|
100
|
+
# Detect any GitLab remote (not just origin), mirroring CIMonitor behavior
|
101
|
+
gl_remotes = Kettle::Dev::CIMonitor.gitlab_remote_candidates
|
102
|
+
if gl_remotes.nil? || gl_remotes.empty? || branch.nil?
|
103
|
+
puts "Latest GL (#{branch || "n/a"}) pipeline: n/a"
|
104
|
+
next
|
105
|
+
end
|
106
|
+
|
107
|
+
# Parse owner/repo from the first GitLab remote URL
|
108
|
+
gl_url = Kettle::Dev::CIMonitor.remote_url(gl_remotes.first)
|
109
|
+
owner = repo = nil
|
110
|
+
if gl_url =~ %r{git@gitlab.com:(.+?)/(.+?)(\.git)?$}
|
111
|
+
owner = Regexp.last_match(1)
|
112
|
+
repo = Regexp.last_match(2).sub(/\.git\z/, "")
|
113
|
+
elsif gl_url =~ %r{https://gitlab.com/(.+?)/(.+?)(\.git)?$}
|
114
|
+
owner = Regexp.last_match(1)
|
115
|
+
repo = Regexp.last_match(2).sub(/\.git\z/, "")
|
116
|
+
end
|
117
|
+
|
118
|
+
unless owner && repo
|
119
|
+
puts "Latest GL (#{branch}) pipeline: n/a"
|
120
|
+
next
|
121
|
+
end
|
122
|
+
|
123
|
+
pipe = Kettle::Dev::CIHelpers.gitlab_latest_pipeline(owner: owner, repo: repo, branch: branch)
|
124
|
+
if pipe
|
125
|
+
st = pipe["status"].to_s
|
126
|
+
status = if st == "success"
|
127
|
+
"success"
|
128
|
+
else
|
129
|
+
((st == "failed") ? "failure" : nil)
|
130
|
+
end
|
131
|
+
emoji = Kettle::Dev::CIMonitor.status_emoji(st, status)
|
132
|
+
details = [st, pipe["failure_reason"]].compact.join("/")
|
133
|
+
puts "Latest GL (#{branch}) pipeline: #{emoji} (#{details})"
|
134
|
+
else
|
135
|
+
puts "Latest GL (#{branch}) pipeline: none"
|
136
|
+
end
|
137
|
+
rescue StandardError => e
|
138
|
+
puts "GL status: error #{e.class}: #{e.message}"
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
101
142
|
run_act_for = proc do |file_path|
|
102
143
|
ok = system("act", "-W", file_path)
|
103
144
|
task_abort("ci:act failed: 'act' command not found or exited with failure") unless ok
|
@@ -131,6 +172,7 @@ module Kettle
|
|
131
172
|
task_abort("ci:act aborted")
|
132
173
|
end
|
133
174
|
fetch_and_print_status.call(file)
|
175
|
+
print_gitlab_status.call
|
134
176
|
run_act_for.call(file_path)
|
135
177
|
return
|
136
178
|
end
|
@@ -152,13 +194,15 @@ module Kettle
|
|
152
194
|
upstream = begin
|
153
195
|
out, status = Open3.capture2("git", "rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}")
|
154
196
|
status.success? ? out.strip : nil
|
155
|
-
rescue StandardError
|
197
|
+
rescue StandardError => e
|
198
|
+
Kettle::Dev.debug_error(e, __method__)
|
156
199
|
nil
|
157
200
|
end
|
158
201
|
sha = begin
|
159
202
|
out, status = Open3.capture2("git", "rev-parse", "--short", "HEAD")
|
160
203
|
status.success? ? out.strip : nil
|
161
|
-
rescue StandardError
|
204
|
+
rescue StandardError => e
|
205
|
+
Kettle::Dev.debug_error(e, __method__)
|
162
206
|
nil
|
163
207
|
end
|
164
208
|
if org && branch
|
@@ -170,6 +214,36 @@ module Kettle
|
|
170
214
|
end
|
171
215
|
puts "Upstream: #{upstream || "n/a"}"
|
172
216
|
puts "HEAD: #{sha || "n/a"}"
|
217
|
+
|
218
|
+
# Compare remote HEAD SHAs between GitHub and GitLab for current branch and highlight mismatch
|
219
|
+
begin
|
220
|
+
branch_name = branch
|
221
|
+
if branch_name
|
222
|
+
gh_remote = Kettle::Dev::CIMonitor.preferred_github_remote
|
223
|
+
gl_remote = Kettle::Dev::CIMonitor.gitlab_remote_candidates.first
|
224
|
+
gh_sha = nil
|
225
|
+
gl_sha = nil
|
226
|
+
if gh_remote
|
227
|
+
out, status = Open3.capture2("git", "ls-remote", gh_remote.to_s, "refs/heads/#{branch_name}")
|
228
|
+
gh_sha = out.split(/\s+/).first if status.success? && out && !out.empty?
|
229
|
+
end
|
230
|
+
if gl_remote
|
231
|
+
out, status = Open3.capture2("git", "ls-remote", gl_remote.to_s, "refs/heads/#{branch_name}")
|
232
|
+
gl_sha = out.split(/\s+/).first if status.success? && out && !out.empty?
|
233
|
+
end
|
234
|
+
if gh_sha && gl_sha
|
235
|
+
gh_short = gh_sha[0, 7]
|
236
|
+
gl_short = gl_sha[0, 7]
|
237
|
+
if gh_short != gl_short
|
238
|
+
puts "⚠️ HEAD mismatch on #{branch_name}: GitHub #{gh_short} vs GitLab #{gl_short}"
|
239
|
+
end
|
240
|
+
end
|
241
|
+
end
|
242
|
+
rescue StandardError => e
|
243
|
+
Kettle::Dev.debug_error(e, __method__)
|
244
|
+
end
|
245
|
+
|
246
|
+
print_gitlab_status.call
|
173
247
|
puts
|
174
248
|
puts "Select a workflow to run with 'act':"
|
175
249
|
|
@@ -183,14 +257,18 @@ module Kettle
|
|
183
257
|
|
184
258
|
puts "(Fetching latest GHA status for branch #{branch || "n/a"} — you can type your choice and press Enter)"
|
185
259
|
prompt = "Enter number or code (or 'q' to quit): "
|
186
|
-
|
187
|
-
|
260
|
+
if tty
|
261
|
+
print(prompt)
|
262
|
+
$stdout.flush
|
263
|
+
end
|
188
264
|
|
189
265
|
# We need to sleep a bit here to ensure the terminal is ready for both
|
190
266
|
# input and writing status updates to each workflow's line
|
191
267
|
sleep(0.2) unless Kettle::Dev::IS_CI
|
192
268
|
|
193
269
|
selected = nil
|
270
|
+
# Create input thread always so specs that assert its cleanup/exception behavior can exercise it,
|
271
|
+
# but guard against non-interactive stdin by rescuing 'bad tty' and similar errors immediately.
|
194
272
|
input_thread = Thread.new do
|
195
273
|
begin
|
196
274
|
selected = Kettle::Dev::InputAdapter.gets&.strip
|
@@ -198,7 +276,7 @@ module Kettle
|
|
198
276
|
# Catch all exceptions in background thread, including SystemExit
|
199
277
|
# NOTE: look into refactoring to minimize potential SystemExit.
|
200
278
|
puts "Error in background thread: #{error.class}: #{error.message}" if Kettle::Dev::DEBUGGING
|
201
|
-
selected =
|
279
|
+
selected = :input_error
|
202
280
|
end
|
203
281
|
end
|
204
282
|
|
@@ -231,12 +309,7 @@ module Kettle
|
|
231
309
|
if run
|
232
310
|
st = run["status"]
|
233
311
|
con = run["conclusion"]
|
234
|
-
emoji =
|
235
|
-
when "queued" then "⏳️"
|
236
|
-
when "in_progress" then "👟"
|
237
|
-
when "completed" then ((con == "success") ? "✅" : "🍅")
|
238
|
-
else "⏳️"
|
239
|
-
end
|
312
|
+
emoji = Kettle::Dev::CIMonitor.status_emoji(st, con)
|
240
313
|
details = [st, con].compact.join("/")
|
241
314
|
status_q << [c, f, "#{emoji} (#{details})"]
|
242
315
|
break if st == "completed"
|
@@ -247,13 +320,15 @@ module Kettle
|
|
247
320
|
else
|
248
321
|
status_q << [c, f, "fail #{res.code}"]
|
249
322
|
end
|
250
|
-
rescue Exception
|
323
|
+
rescue Exception => e # rubocop:disable Lint/RescueException
|
324
|
+
Kettle::Dev.debug_error(e, __method__)
|
251
325
|
# Catch all exceptions to prevent crashing the process from a worker thread
|
252
326
|
status_q << [c, f, "err"]
|
253
327
|
end
|
254
328
|
sleep(poll_interval)
|
255
329
|
end
|
256
|
-
rescue Exception
|
330
|
+
rescue Exception => e # rubocop:disable Lint/RescueException
|
331
|
+
Kettle::Dev.debug_error(e, __method__)
|
257
332
|
# :nocov:
|
258
333
|
# Catch all exceptions in the worker thread boundary, including SystemExit
|
259
334
|
status_q << [c, f, "err"]
|
@@ -264,11 +339,22 @@ module Kettle
|
|
264
339
|
|
265
340
|
statuses = Hash.new(placeholder)
|
266
341
|
|
342
|
+
# In non-interactive environments (no TTY) and when not DEBUGGING, auto-quit after a short idle
|
343
|
+
auto_quit_deadline = if !tty && !Kettle::Dev::DEBUGGING
|
344
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC) + 1.0
|
345
|
+
end
|
346
|
+
|
267
347
|
loop do
|
268
348
|
if selected
|
269
349
|
break
|
270
350
|
end
|
271
351
|
|
352
|
+
# Auto-quit if deadline passed without input (non-interactive runs)
|
353
|
+
if auto_quit_deadline && Process.clock_gettime(Process::CLOCK_MONOTONIC) >= auto_quit_deadline
|
354
|
+
selected = "q"
|
355
|
+
break
|
356
|
+
end
|
357
|
+
|
272
358
|
begin
|
273
359
|
code, file_name, display = status_q.pop(true)
|
274
360
|
statuses[code] = display
|
@@ -291,20 +377,24 @@ module Kettle
|
|
291
377
|
puts "status #{code}: #{display}"
|
292
378
|
end
|
293
379
|
rescue ThreadError
|
380
|
+
# ThreadError is raised when the queue is empty,
|
381
|
+
# and it needs to be silent to maintain the output row alignment
|
294
382
|
sleep(0.05)
|
295
383
|
end
|
296
384
|
end
|
297
385
|
|
298
386
|
begin
|
299
387
|
workers.each { |t| t.kill if t&.alive? }
|
300
|
-
rescue StandardError
|
388
|
+
rescue StandardError => e
|
389
|
+
Kettle::Dev.debug_error(e, __method__)
|
301
390
|
end
|
302
391
|
begin
|
303
392
|
input_thread.kill if input_thread&.alive?
|
304
|
-
rescue StandardError
|
393
|
+
rescue StandardError => e
|
394
|
+
Kettle::Dev.debug_error(e, __method__)
|
305
395
|
end
|
306
396
|
|
307
|
-
input = selected
|
397
|
+
input = (selected == :input_error) ? nil : selected
|
308
398
|
task_abort("ci:act aborted: no selection") if input.nil? || input.empty?
|
309
399
|
|
310
400
|
chosen_file = nil
|
@@ -61,7 +61,8 @@ module Kettle
|
|
61
61
|
content = content.gsub(/!\[[^\]]*?\]\s*\[#{label_re}\]/, "")
|
62
62
|
removed_labels << label
|
63
63
|
end
|
64
|
-
rescue StandardError
|
64
|
+
rescue StandardError => e
|
65
|
+
Kettle::Dev.debug_error(e, __method__)
|
65
66
|
# ignore
|
66
67
|
end
|
67
68
|
end
|
@@ -201,7 +202,8 @@ module Kettle
|
|
201
202
|
tmp = tmp[cluster.length..-1].to_s
|
202
203
|
end
|
203
204
|
rest_wo_emoji = tmp.sub(/\A\s+/, "")
|
204
|
-
rescue StandardError
|
205
|
+
rescue StandardError => e
|
206
|
+
Kettle::Dev.debug_error(e, __method__)
|
205
207
|
rest_wo_emoji = rest.sub(/\A\s+/, "")
|
206
208
|
end
|
207
209
|
# Build H1 with single spaces only around separators; preserve inner spacing in rest_wo_emoji
|
@@ -236,7 +238,8 @@ module Kettle
|
|
236
238
|
end
|
237
239
|
tmp = tmp.sub(/\A\s+/, "")
|
238
240
|
body_wo = tmp
|
239
|
-
rescue StandardError
|
241
|
+
rescue StandardError => e
|
242
|
+
Kettle::Dev.debug_error(e, __method__)
|
240
243
|
body_wo = body.sub(/\A\s+/, "")
|
241
244
|
end
|
242
245
|
pre + q + ("#{chosen_grapheme} " + body_wo) + q
|
@@ -272,7 +275,8 @@ module Kettle
|
|
272
275
|
end.join
|
273
276
|
File.open(readme_path, "w") { |f| f.write(content) }
|
274
277
|
end
|
275
|
-
rescue StandardError
|
278
|
+
rescue StandardError => e
|
279
|
+
Kettle::Dev.debug_error(e, __method__)
|
276
280
|
# ignore whitespace normalization errors
|
277
281
|
end
|
278
282
|
|
@@ -335,14 +339,14 @@ module Kettle
|
|
335
339
|
if interpolated || !valid_literal
|
336
340
|
puts
|
337
341
|
puts "Checking git remote 'origin' to derive GitHub homepage..."
|
338
|
-
origin_url =
|
342
|
+
origin_url = ""
|
343
|
+
# Use GitAdapter to avoid hanging and to simplify testing.
|
339
344
|
begin
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
origin_url = ""
|
345
|
+
ga = Kettle::Dev::GitAdapter.new
|
346
|
+
origin_url = ga.remote_url("origin") || ga.remotes_with_urls["origin"]
|
347
|
+
origin_url = origin_url.to_s.strip
|
348
|
+
rescue StandardError => e
|
349
|
+
Kettle::Dev.debug_error(e, __method__)
|
346
350
|
end
|
347
351
|
|
348
352
|
org_repo = github_repo_from_url.call(origin_url)
|
@@ -363,7 +367,7 @@ module Kettle
|
|
363
367
|
puts "Suggested literal homepage: \"#{suggested}\""
|
364
368
|
print("Update #{File.basename(gemspec_path)} to use this homepage? [Y/n]: ")
|
365
369
|
ans = Kettle::Dev::InputAdapter.gets&.strip
|
366
|
-
do_update = if ENV.fetch("force", "").to_s =~
|
370
|
+
do_update = if ENV.fetch("force", "").to_s =~ ENV_TRUE_RE
|
367
371
|
true
|
368
372
|
else
|
369
373
|
ans.nil? || ans.empty? || ans =~ /\Ay(es)?\z/i
|
@@ -440,7 +444,8 @@ module Kettle
|
|
440
444
|
else
|
441
445
|
begin
|
442
446
|
current = File.file?(envrc_path) ? File.read(envrc_path) : ""
|
443
|
-
rescue StandardError
|
447
|
+
rescue StandardError => e
|
448
|
+
Kettle::Dev.debug_error(e, __method__)
|
444
449
|
current = ""
|
445
450
|
end
|
446
451
|
has_path_add = current.lines.any? { |l| l.strip =~ /^PATH_add\s+bin\b/ }
|
@@ -468,8 +473,8 @@ module Kettle
|
|
468
473
|
end
|
469
474
|
|
470
475
|
if defined?(updated_envrc_by_install) && updated_envrc_by_install
|
471
|
-
allowed_truthy = ENV.fetch("allowed", "").to_s =~
|
472
|
-
force_truthy = ENV.fetch("force", "").to_s =~
|
476
|
+
allowed_truthy = ENV.fetch("allowed", "").to_s =~ ENV_TRUE_RE
|
477
|
+
force_truthy = ENV.fetch("force", "").to_s =~ ENV_TRUE_RE
|
473
478
|
if allowed_truthy || force_truthy
|
474
479
|
reason = allowed_truthy ? "allowed=true" : "force=true"
|
475
480
|
puts "Proceeding after .envrc update because #{reason}."
|
@@ -496,7 +501,8 @@ module Kettle
|
|
496
501
|
unless helpers.modified_by_template?(gitignore_path)
|
497
502
|
begin
|
498
503
|
gitignore_current = File.exist?(gitignore_path) ? File.read(gitignore_path) : ""
|
499
|
-
rescue StandardError
|
504
|
+
rescue StandardError => e
|
505
|
+
Kettle::Dev.debug_error(e, __method__)
|
500
506
|
gitignore_current = ""
|
501
507
|
end
|
502
508
|
has_env_local = gitignore_current.lines.any? { |l| l.strip == ".env.local" }
|
@@ -505,7 +511,7 @@ module Kettle
|
|
505
511
|
puts "Would you like to add '.env.local' to #{gitignore_path}?"
|
506
512
|
print("Add to .gitignore now [Y/n]: ")
|
507
513
|
answer = Kettle::Dev::InputAdapter.gets&.strip
|
508
|
-
add_it = if ENV.fetch("force", "").to_s =~
|
514
|
+
add_it = if ENV.fetch("force", "").to_s =~ ENV_TRUE_RE
|
509
515
|
true
|
510
516
|
else
|
511
517
|
answer.nil? || answer.empty? || answer =~ /\Ay(es)?\z/i
|
@@ -63,6 +63,7 @@ module Kettle
|
|
63
63
|
helpers.apply_common_replacements(
|
64
64
|
c,
|
65
65
|
org: forge_org,
|
66
|
+
funding_org: funding_org,
|
66
67
|
gem_name: gem_name,
|
67
68
|
namespace: namespace,
|
68
69
|
namespace_shield: namespace_shield,
|
@@ -74,6 +75,7 @@ module Kettle
|
|
74
75
|
helpers.apply_common_replacements(
|
75
76
|
content,
|
76
77
|
org: forge_org,
|
78
|
+
funding_org: funding_org,
|
77
79
|
gem_name: gem_name,
|
78
80
|
namespace: namespace,
|
79
81
|
namespace_shield: namespace_shield,
|
@@ -209,21 +211,29 @@ module Kettle
|
|
209
211
|
if File.exist?(dest_gemspec)
|
210
212
|
begin
|
211
213
|
orig_meta = helpers.gemspec_metadata(File.dirname(dest_gemspec))
|
212
|
-
rescue StandardError
|
214
|
+
rescue StandardError => e
|
215
|
+
Kettle::Dev.debug_error(e, __method__)
|
213
216
|
orig_meta = nil
|
214
217
|
end
|
215
218
|
end
|
216
219
|
|
217
220
|
helpers.copy_file_with_prompt(gemspec_template_src, dest_gemspec, allow_create: true, allow_replace: true) do |content|
|
218
|
-
# First apply standard replacements from the template example
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
221
|
+
# First apply standard replacements from the template example, but only
|
222
|
+
# when we have a usable gem_name. If gem_name is unknown, leave content as-is
|
223
|
+
# to allow filename fallback behavior without raising.
|
224
|
+
c = if gem_name && !gem_name.to_s.empty?
|
225
|
+
helpers.apply_common_replacements(
|
226
|
+
content,
|
227
|
+
org: forge_org,
|
228
|
+
funding_org: funding_org,
|
229
|
+
gem_name: gem_name,
|
230
|
+
namespace: namespace,
|
231
|
+
namespace_shield: namespace_shield,
|
232
|
+
gem_shield: gem_shield,
|
233
|
+
)
|
234
|
+
else
|
235
|
+
content.dup
|
236
|
+
end
|
227
237
|
|
228
238
|
if orig_meta
|
229
239
|
# Replace a scalar string assignment like: spec.field = "..."
|
@@ -311,10 +321,32 @@ module Kettle
|
|
311
321
|
end
|
312
322
|
end
|
313
323
|
|
324
|
+
# Ensure we do not introduce a self-dependency when templating the gemspec.
|
325
|
+
# If the template included a dependency on the template gem (e.g., "kettle-dev"),
|
326
|
+
# the common replacements would have turned it into the destination gem's name.
|
327
|
+
# Strip any dependency lines that name the destination gem.
|
328
|
+
begin
|
329
|
+
if gem_name && !gem_name.to_s.empty?
|
330
|
+
name_escaped = Regexp.escape(gem_name)
|
331
|
+
# Matches both runtime and development dependency lines, with or without parentheses.
|
332
|
+
# Examples matched:
|
333
|
+
# spec.add_dependency("my-gem", "~> 1.0")
|
334
|
+
# spec.add_dependency 'my-gem'
|
335
|
+
# spec.add_development_dependency "my-gem"
|
336
|
+
# spec.add_development_dependency 'my-gem', ">= 0"
|
337
|
+
self_dep_re = /\A\s*spec\.add_(?:development_)?dependency(?:\s*\(|\s+)\s*["']#{name_escaped}["'][^\n]*\)?\s*\z/
|
338
|
+
c = c.lines.reject { |ln| self_dep_re =~ ln }.join
|
339
|
+
end
|
340
|
+
rescue StandardError => e
|
341
|
+
Kettle::Dev.debug_error(e, __method__)
|
342
|
+
# If anything goes wrong, keep the content as-is rather than failing the task
|
343
|
+
end
|
344
|
+
|
314
345
|
c
|
315
346
|
end
|
316
347
|
end
|
317
|
-
rescue StandardError
|
348
|
+
rescue StandardError => e
|
349
|
+
Kettle::Dev.debug_error(e, __method__)
|
318
350
|
# Do not fail the entire template task if gemspec copy has issues
|
319
351
|
end
|
320
352
|
|
@@ -349,7 +381,8 @@ module Kettle
|
|
349
381
|
existing_readme_before = begin
|
350
382
|
path = File.join(project_root, "README.md")
|
351
383
|
File.file?(path) ? File.read(path) : nil
|
352
|
-
rescue StandardError
|
384
|
+
rescue StandardError => e
|
385
|
+
Kettle::Dev.debug_error(e, __method__)
|
353
386
|
nil
|
354
387
|
end
|
355
388
|
|
@@ -372,7 +405,7 @@ module Kettle
|
|
372
405
|
loop do
|
373
406
|
cluster = s[/\A\X/u]
|
374
407
|
break if cluster.nil? || cluster.empty?
|
375
|
-
if emoji_re
|
408
|
+
if emoji_re =~ cluster
|
376
409
|
out << cluster
|
377
410
|
s = s[cluster.length..-1].to_s
|
378
411
|
else
|
@@ -397,6 +430,7 @@ module Kettle
|
|
397
430
|
c = helpers.apply_common_replacements(
|
398
431
|
content,
|
399
432
|
org: forge_org,
|
433
|
+
funding_org: funding_org,
|
400
434
|
gem_name: gem_name,
|
401
435
|
namespace: namespace,
|
402
436
|
namespace_shield: namespace_shield,
|
@@ -539,7 +573,8 @@ module Kettle
|
|
539
573
|
helpers.copy_file_with_prompt(src, dest, allow_create: true, allow_replace: true) do |content|
|
540
574
|
c = helpers.apply_common_replacements(
|
541
575
|
content,
|
542
|
-
org:
|
576
|
+
org: forge_org,
|
577
|
+
funding_org: funding_org,
|
543
578
|
gem_name: gem_name,
|
544
579
|
namespace: namespace,
|
545
580
|
namespace_shield: namespace_shield,
|
@@ -581,7 +616,8 @@ module Kettle
|
|
581
616
|
end
|
582
617
|
end
|
583
618
|
end
|
584
|
-
rescue StandardError
|
619
|
+
rescue StandardError => e
|
620
|
+
Kettle::Dev.debug_error(e, __method__)
|
585
621
|
# ignore post-processing errors
|
586
622
|
end
|
587
623
|
|
@@ -656,13 +692,15 @@ module Kettle
|
|
656
692
|
end
|
657
693
|
end
|
658
694
|
end
|
659
|
-
rescue StandardError
|
695
|
+
rescue StandardError => e
|
696
|
+
Kettle::Dev.debug_error(e, __method__)
|
660
697
|
# If filter parsing fails, proceed as before
|
661
698
|
end
|
662
|
-
|
663
|
-
|
664
|
-
|
665
|
-
|
699
|
+
# Prefer .example variant when present for .git-hooks
|
700
|
+
goalie_src = helpers.prefer_example(File.join(source_hooks_dir, "commit-subjects-goalie.txt"))
|
701
|
+
footer_src = helpers.prefer_example(File.join(source_hooks_dir, "footer-template.erb.txt"))
|
702
|
+
hook_ruby_src = helpers.prefer_example(File.join(source_hooks_dir, "commit-msg"))
|
703
|
+
hook_sh_src = helpers.prefer_example(File.join(source_hooks_dir, "prepare-commit-msg"))
|
666
704
|
|
667
705
|
# First: templates (.txt) — ask local/global/skip
|
668
706
|
if File.file?(goalie_src) && File.file?(footer_src)
|
@@ -708,7 +746,8 @@ module Kettle
|
|
708
746
|
# Ensure readable (0644). These are data/templates, not executables.
|
709
747
|
begin
|
710
748
|
File.chmod(0o644, dest) if File.exist?(dest)
|
711
|
-
rescue StandardError
|
749
|
+
rescue StandardError => e
|
750
|
+
Kettle::Dev.debug_error(e, __method__)
|
712
751
|
# ignore permission issues
|
713
752
|
end
|
714
753
|
end
|
@@ -733,7 +772,8 @@ module Kettle
|
|
733
772
|
helpers.write_file(dest, content)
|
734
773
|
begin
|
735
774
|
File.chmod(mode, dest)
|
736
|
-
rescue StandardError
|
775
|
+
rescue StandardError => e
|
776
|
+
Kettle::Dev.debug_error(e, __method__)
|
737
777
|
# ignore permission issues
|
738
778
|
end
|
739
779
|
puts "Replaced #{dest}"
|
@@ -745,7 +785,8 @@ module Kettle
|
|
745
785
|
helpers.write_file(dest, content)
|
746
786
|
begin
|
747
787
|
File.chmod(mode, dest)
|
748
|
-
rescue StandardError
|
788
|
+
rescue StandardError => e
|
789
|
+
Kettle::Dev.debug_error(e, __method__)
|
749
790
|
# ignore permission issues
|
750
791
|
end
|
751
792
|
puts "Installed #{dest}"
|
@@ -12,6 +12,8 @@ module Kettle
|
|
12
12
|
# Values: Hash with keys: :action (Symbol, one of :create, :replace, :skip, :dir_create, :dir_replace), :timestamp (Time)
|
13
13
|
@@template_results = {}
|
14
14
|
|
15
|
+
EXECUTABLE_GIT_HOOKS_RE = %r{[\\/]\.git-hooks[\\/](commit-msg|prepare-commit-msg)\z}
|
16
|
+
|
15
17
|
module_function
|
16
18
|
|
17
19
|
# Root of the host project where Rake was invoked
|
@@ -119,9 +121,11 @@ module Kettle
|
|
119
121
|
end
|
120
122
|
|
121
123
|
if clean.nil?
|
122
|
-
# Fallback to
|
124
|
+
# Fallback to using the GitAdapter to get both status and preview
|
123
125
|
status_output = begin
|
124
|
-
|
126
|
+
ga = Kettle::Dev::GitAdapter.new
|
127
|
+
out, ok = ga.capture(["-C", root.to_s, "status", "--porcelain"]) # adapter can use CLI safely
|
128
|
+
ok ? out.to_s : ""
|
125
129
|
rescue StandardError => e
|
126
130
|
Kettle::Dev.debug_error(e, __method__)
|
127
131
|
""
|
@@ -130,9 +134,11 @@ module Kettle
|
|
130
134
|
preview = status_output.lines.take(10).map(&:rstrip)
|
131
135
|
else
|
132
136
|
return if clean
|
133
|
-
# For messaging, provide a small preview
|
137
|
+
# For messaging, provide a small preview using GitAdapter even when using the adapter
|
134
138
|
status_output = begin
|
135
|
-
|
139
|
+
ga = Kettle::Dev::GitAdapter.new
|
140
|
+
out, ok = ga.capture(["-C", root.to_s, "status", "--porcelain"]) # read-only query
|
141
|
+
ok ? out.to_s : ""
|
136
142
|
rescue StandardError => e
|
137
143
|
Kettle::Dev.debug_error(e, __method__)
|
138
144
|
""
|
@@ -223,10 +229,11 @@ module Kettle
|
|
223
229
|
write_file(dest_path, content)
|
224
230
|
begin
|
225
231
|
# Ensure executable bit for git hook scripts when writing under .git-hooks
|
226
|
-
if dest_path.to_s
|
232
|
+
if EXECUTABLE_GIT_HOOKS_RE =~ dest_path.to_s
|
227
233
|
File.chmod(0o755, dest_path) if File.exist?(dest_path)
|
228
234
|
end
|
229
|
-
rescue StandardError
|
235
|
+
rescue StandardError => e
|
236
|
+
Kettle::Dev.debug_error(e, __method__)
|
230
237
|
# ignore permission issues
|
231
238
|
end
|
232
239
|
record_template_result(dest_path, dest_exists ? :replace : :create)
|
@@ -327,10 +334,11 @@ module Kettle
|
|
327
334
|
begin
|
328
335
|
# Ensure executable bit for git hook scripts when copying under .git-hooks
|
329
336
|
if target.end_with?("/.git-hooks/commit-msg", "/.git-hooks/prepare-commit-msg") ||
|
330
|
-
target
|
337
|
+
EXECUTABLE_GIT_HOOKS_RE =~ target
|
331
338
|
File.chmod(0o755, target)
|
332
339
|
end
|
333
|
-
rescue StandardError
|
340
|
+
rescue StandardError => e
|
341
|
+
Kettle::Dev.debug_error(e, __method__)
|
334
342
|
# ignore permission issues
|
335
343
|
end
|
336
344
|
end
|
@@ -374,10 +382,11 @@ module Kettle
|
|
374
382
|
begin
|
375
383
|
# Ensure executable bit for git hook scripts when copying under .git-hooks
|
376
384
|
if target.end_with?("/.git-hooks/commit-msg", "/.git-hooks/prepare-commit-msg") ||
|
377
|
-
target
|
385
|
+
EXECUTABLE_GIT_HOOKS_RE =~ target
|
378
386
|
File.chmod(0o755, target)
|
379
387
|
end
|
380
|
-
rescue StandardError
|
388
|
+
rescue StandardError => e
|
389
|
+
Kettle::Dev.debug_error(e, __method__)
|
381
390
|
# ignore permission issues
|
382
391
|
end
|
383
392
|
end
|
@@ -394,30 +403,34 @@ module Kettle
|
|
394
403
|
# @param namespace [String]
|
395
404
|
# @param namespace_shield [String]
|
396
405
|
# @param gem_shield [String]
|
406
|
+
# @param funding_org [String, nil]
|
397
407
|
# @return [String]
|
398
|
-
def apply_common_replacements(content, org:, gem_name:, namespace:, namespace_shield:, gem_shield:)
|
399
|
-
|
400
|
-
|
401
|
-
if gem_name && !gem_name.empty?
|
402
|
-
# Special-case: yard-head link uses the gem name as a subdomain and must be dashes-only.
|
403
|
-
# Apply this BEFORE other generic replacements so it isn't altered incorrectly.
|
404
|
-
begin
|
405
|
-
dashed = gem_name.tr("_", "-")
|
406
|
-
c = c.gsub("[🚎yard-head]: https://kettle-dev.galtzo.com", "[🚎yard-head]: https://#{dashed}.galtzo.com")
|
407
|
-
rescue StandardError
|
408
|
-
# ignore
|
409
|
-
end
|
408
|
+
def apply_common_replacements(content, org:, gem_name:, namespace:, namespace_shield:, gem_shield:, funding_org: nil)
|
409
|
+
raise Error, "Org could not be derived" unless org && !org.empty?
|
410
|
+
raise Error, "Gem name could not be derived" unless gem_name && !gem_name.empty?
|
410
411
|
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
412
|
+
funding_org ||= org
|
413
|
+
c = content.dup
|
414
|
+
c = c.gsub("kettle-rb", org.to_s)
|
415
|
+
c = c.gsub("{OPENCOLLECTIVE|ORG_NAME}", funding_org)
|
416
|
+
# Special-case: yard-head link uses the gem name as a subdomain and must be dashes-only.
|
417
|
+
# Apply this BEFORE other generic replacements so it isn't altered incorrectly.
|
418
|
+
begin
|
419
|
+
dashed = gem_name.tr("_", "-")
|
420
|
+
c = c.gsub("[🚎yard-head]: https://kettle-dev.galtzo.com", "[🚎yard-head]: https://#{dashed}.galtzo.com")
|
421
|
+
rescue StandardError => e
|
422
|
+
Kettle::Dev.debug_error(e, __method__)
|
423
|
+
# ignore
|
419
424
|
end
|
420
|
-
|
425
|
+
|
426
|
+
# Replace occurrences of the template gem name in text, including inside
|
427
|
+
# markdown reference labels like [🖼️kettle-dev] and identifiers like kettle-dev-i
|
428
|
+
c = c.gsub("kettle-dev", gem_name)
|
429
|
+
c = c.gsub(/\bKettle::Dev\b/u, namespace) unless namespace.empty?
|
430
|
+
c = c.gsub("Kettle%3A%3ADev", namespace_shield) unless namespace_shield.empty?
|
431
|
+
c = c.gsub("kettle--dev", gem_shield)
|
432
|
+
# Replace require and path structures with gem_name, modifying - to / if needed
|
433
|
+
c.gsub("kettle/dev", gem_name.tr("-", "/"))
|
421
434
|
end
|
422
435
|
|
423
436
|
# Parse gemspec metadata and derive useful strings
|