kettle-dev 1.2.3 → 2.0.0
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/CHANGELOG.md +176 -3
- data/CITATION.cff +2 -2
- data/CONTRIBUTING.md +11 -17
- data/README.md +390 -319
- data/exe/kettle-dev-setup +12 -63
- data/exe/kettle-gh-release +82 -0
- data/lib/kettle/dev/gem_spec_reader.rb +2 -2
- data/lib/kettle/dev/open_collective_config.rb +12 -0
- data/lib/kettle/dev/rakelib/yard.rake +15 -0
- data/lib/kettle/dev/tasks/ci_task.rb +4 -4
- data/lib/kettle/dev/version.rb +1 -1
- data/lib/kettle/dev.rb +4 -12
- data/sig/kettle/dev/source_merger.rbs +40 -56
- data.tar.gz.sig +0 -0
- metadata +15 -144
- metadata.gz.sig +0 -0
- data/.aiignore.example +0 -19
- data/.devcontainer/apt-install/devcontainer-feature.json +0 -9
- data/.devcontainer/apt-install/install.sh +0 -11
- data/.devcontainer/devcontainer.json +0 -28
- data/.env.local.example +0 -31
- data/.envrc +0 -47
- data/.envrc.example +0 -51
- data/.envrc.no-osc.example +0 -51
- data/.git-hooks/commit-msg +0 -54
- data/.git-hooks/commit-subjects-goalie.txt +0 -8
- data/.git-hooks/footer-template.erb.txt +0 -16
- data/.git-hooks/prepare-commit-msg +0 -8
- data/.github/.codecov.yml.example +0 -14
- data/.github/FUNDING.yml +0 -13
- data/.github/FUNDING.yml.no-osc.example +0 -13
- data/.github/dependabot.yml +0 -13
- data/.github/workflows/ancient.yml +0 -83
- data/.github/workflows/ancient.yml.example +0 -81
- data/.github/workflows/auto-assign.yml +0 -21
- data/.github/workflows/codeql-analysis.yml +0 -70
- data/.github/workflows/coverage.yml +0 -127
- data/.github/workflows/coverage.yml.example +0 -127
- data/.github/workflows/current.yml +0 -116
- data/.github/workflows/current.yml.example +0 -115
- data/.github/workflows/dep-heads.yml +0 -117
- data/.github/workflows/dependency-review.yml +0 -20
- data/.github/workflows/discord-notifier.yml.example +0 -39
- data/.github/workflows/heads.yml +0 -117
- data/.github/workflows/heads.yml.example +0 -116
- data/.github/workflows/jruby.yml +0 -82
- data/.github/workflows/jruby.yml.example +0 -72
- data/.github/workflows/legacy.yml +0 -76
- data/.github/workflows/license-eye.yml +0 -40
- data/.github/workflows/locked_deps.yml +0 -85
- data/.github/workflows/opencollective.yml +0 -40
- data/.github/workflows/style.yml +0 -67
- data/.github/workflows/supported.yml +0 -75
- data/.github/workflows/truffle.yml +0 -99
- data/.github/workflows/unlocked_deps.yml +0 -84
- data/.github/workflows/unsupported.yml +0 -76
- data/.gitignore +0 -50
- data/.gitlab-ci.yml.example +0 -134
- data/.idea/.gitignore +0 -45
- data/.junie/guidelines-rbs.md +0 -49
- data/.junie/guidelines.md +0 -141
- data/.junie/guidelines.md.example +0 -140
- data/.licenserc.yaml +0 -7
- data/.opencollective.yml +0 -3
- data/.opencollective.yml.example +0 -3
- data/.qlty/qlty.toml +0 -79
- data/.rspec +0 -9
- data/.rubocop.yml +0 -13
- data/.rubocop_rspec.yml +0 -33
- data/.simplecov +0 -16
- data/.simplecov.example +0 -11
- data/.tool-versions +0 -1
- data/.yardignore +0 -13
- data/.yardopts +0 -14
- data/Appraisal.root.gemfile +0 -10
- data/Appraisals +0 -151
- data/Appraisals.example +0 -102
- data/CHANGELOG.md.example +0 -47
- data/CONTRIBUTING.md.example +0 -227
- data/FUNDING.md.no-osc.example +0 -63
- data/Gemfile +0 -40
- data/Gemfile.example +0 -34
- data/README.md.example +0 -570
- data/README.md.no-osc.example +0 -536
- data/Rakefile.example +0 -68
- data/gemfiles/modular/coverage.gemfile +0 -6
- data/gemfiles/modular/debug.gemfile +0 -13
- data/gemfiles/modular/documentation.gemfile +0 -14
- data/gemfiles/modular/erb/r2/v3.0.gemfile +0 -1
- data/gemfiles/modular/erb/r2.3/default.gemfile +0 -6
- data/gemfiles/modular/erb/r2.6/v2.2.gemfile +0 -3
- data/gemfiles/modular/erb/r3/v5.0.gemfile +0 -1
- data/gemfiles/modular/erb/r3.1/v4.0.gemfile +0 -2
- data/gemfiles/modular/erb/vHEAD.gemfile +0 -2
- data/gemfiles/modular/injected.gemfile +0 -60
- data/gemfiles/modular/mutex_m/r2/v0.3.gemfile +0 -2
- data/gemfiles/modular/mutex_m/r2.4/v0.1.gemfile +0 -3
- data/gemfiles/modular/mutex_m/r3/v0.3.gemfile +0 -2
- data/gemfiles/modular/mutex_m/vHEAD.gemfile +0 -2
- data/gemfiles/modular/optional.gemfile +0 -8
- data/gemfiles/modular/optional.gemfile.example +0 -5
- data/gemfiles/modular/runtime_heads.gemfile +0 -10
- data/gemfiles/modular/runtime_heads.gemfile.example +0 -8
- data/gemfiles/modular/stringio/r2/v3.0.gemfile +0 -5
- data/gemfiles/modular/stringio/r2.4/v0.0.2.gemfile +0 -4
- data/gemfiles/modular/stringio/r3/v3.0.gemfile +0 -5
- data/gemfiles/modular/stringio/vHEAD.gemfile +0 -2
- data/gemfiles/modular/style.gemfile +0 -25
- data/gemfiles/modular/style.gemfile.example +0 -25
- data/gemfiles/modular/templating.gemfile +0 -3
- data/gemfiles/modular/x_std_libs/r2/libs.gemfile +0 -3
- data/gemfiles/modular/x_std_libs/r2.3/libs.gemfile +0 -3
- data/gemfiles/modular/x_std_libs/r2.4/libs.gemfile +0 -3
- data/gemfiles/modular/x_std_libs/r2.6/libs.gemfile +0 -3
- data/gemfiles/modular/x_std_libs/r3/libs.gemfile +0 -3
- data/gemfiles/modular/x_std_libs/r3.1/libs.gemfile +0 -3
- data/gemfiles/modular/x_std_libs/vHEAD.gemfile +0 -3
- data/gemfiles/modular/x_std_libs.gemfile +0 -2
- data/kettle-dev.gemspec.example +0 -154
- data/lib/kettle/dev/modular_gemfiles.rb +0 -119
- data/lib/kettle/dev/prism_appraisals.rb +0 -351
- data/lib/kettle/dev/prism_gemfile.rb +0 -177
- data/lib/kettle/dev/prism_gemspec.rb +0 -284
- data/lib/kettle/dev/prism_utils.rb +0 -201
- data/lib/kettle/dev/rakelib/install.rake +0 -10
- data/lib/kettle/dev/rakelib/template.rake +0 -10
- data/lib/kettle/dev/setup_cli.rb +0 -403
- data/lib/kettle/dev/source_merger.rb +0 -622
- data/lib/kettle/dev/tasks/install_task.rb +0 -553
- data/lib/kettle/dev/tasks/template_task.rb +0 -975
- data/lib/kettle/dev/template_helpers.rb +0 -685
|
@@ -1,553 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Kettle
|
|
4
|
-
module Dev
|
|
5
|
-
module Tasks
|
|
6
|
-
module InstallTask
|
|
7
|
-
module_function
|
|
8
|
-
|
|
9
|
-
# Abort wrapper that avoids terminating the current rake task process.
|
|
10
|
-
# Always raise Kettle::Dev::Error so callers can decide whether to handle
|
|
11
|
-
# it without terminating the process (e.g., in tests or non-interactive runs).
|
|
12
|
-
def task_abort(msg)
|
|
13
|
-
raise Kettle::Dev::Error, msg
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
def run
|
|
17
|
-
helpers = Kettle::Dev::TemplateHelpers
|
|
18
|
-
project_root = helpers.project_root
|
|
19
|
-
|
|
20
|
-
# Run file templating via dedicated task first
|
|
21
|
-
Rake::Task["kettle:dev:template"].invoke
|
|
22
|
-
|
|
23
|
-
# .tool-versions cleanup offers
|
|
24
|
-
tool_versions_path = File.join(project_root, ".tool-versions")
|
|
25
|
-
if File.file?(tool_versions_path)
|
|
26
|
-
rv = File.join(project_root, ".ruby-version")
|
|
27
|
-
rg = File.join(project_root, ".ruby-gemset")
|
|
28
|
-
to_remove = [rv, rg].select { |p| File.exist?(p) }
|
|
29
|
-
unless to_remove.empty?
|
|
30
|
-
if helpers.ask("Remove #{to_remove.map { |p| File.basename(p) }.join(" and ")} (managed by .tool-versions)?", true)
|
|
31
|
-
to_remove.each { |p| FileUtils.rm_f(p) }
|
|
32
|
-
puts "Removed #{to_remove.map { |p| File.basename(p) }.join(" and ")}"
|
|
33
|
-
end
|
|
34
|
-
end
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
# Trim MRI Ruby version badges in README.md to >= required_ruby_version from gemspec
|
|
38
|
-
begin
|
|
39
|
-
readme_path = File.join(project_root, "README.md")
|
|
40
|
-
if File.file?(readme_path)
|
|
41
|
-
md = helpers.gemspec_metadata(project_root)
|
|
42
|
-
min_ruby = md[:min_ruby] # an instance of Gem::Version
|
|
43
|
-
if min_ruby
|
|
44
|
-
content = File.read(readme_path)
|
|
45
|
-
|
|
46
|
-
# Detect all MRI ruby badge labels present
|
|
47
|
-
removed_labels = []
|
|
48
|
-
|
|
49
|
-
content.scan(/\[(?<label>💎ruby-(?<ver>\d+\.\d+)i)\]/) do |arr|
|
|
50
|
-
label, ver_s = arr
|
|
51
|
-
begin
|
|
52
|
-
ver = Gem::Version.new(ver_s)
|
|
53
|
-
if ver < min_ruby
|
|
54
|
-
# Remove occurrences of badges using this label
|
|
55
|
-
label_re = Regexp.escape(label)
|
|
56
|
-
# Linked form: [![...][label]][...]
|
|
57
|
-
content = content.gsub(/\[!\[[^\]]*?\]\s*\[#{label_re}\]\s*\]\s*\[[^\]]+\]/, "")
|
|
58
|
-
# Unlinked form: ![...][label]
|
|
59
|
-
content = content.gsub(/!\[[^\]]*?\]\s*\[#{label_re}\]/, "")
|
|
60
|
-
removed_labels << label
|
|
61
|
-
end
|
|
62
|
-
rescue StandardError => e
|
|
63
|
-
Kettle::Dev.debug_error(e, __method__)
|
|
64
|
-
# ignore
|
|
65
|
-
end
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
# Fix leading <br/> in MRI rows and remove rows that end up empty. Also normalize leading whitespace in badge cell to a single space.
|
|
69
|
-
content = content.lines.map { |ln|
|
|
70
|
-
if ln.start_with?("| Works with MRI Ruby")
|
|
71
|
-
cells = ln.split("|", -1)
|
|
72
|
-
# cells[0] is empty (leading |), cells[1] = label cell, cells[2] = badges cell
|
|
73
|
-
badge_cell = cells[2] || ""
|
|
74
|
-
# If badge cell is only a <br/> (possibly with whitespace), treat as empty (row will be removed later)
|
|
75
|
-
if badge_cell.strip == "<br/>"
|
|
76
|
-
cells[2] = " "
|
|
77
|
-
cells.join("|")
|
|
78
|
-
elsif badge_cell =~ /\A\s*<br\/>/i
|
|
79
|
-
# If badge cell starts with <br/> and there are no badges before it, strip the leading <br/>
|
|
80
|
-
# We consider "no badges before" as any leading whitespace followed immediately by <br/>
|
|
81
|
-
cleaned = badge_cell.sub(/\A\s*<br\/>\s*/i, "")
|
|
82
|
-
cells[2] = " #{cleaned}" # prefix with a single space
|
|
83
|
-
cells.join("|")
|
|
84
|
-
elsif badge_cell =~ /\A[ \t]{2,}\S/
|
|
85
|
-
# Collapse multiple leading spaces/tabs to exactly one
|
|
86
|
-
cells[2] = " " + badge_cell.lstrip
|
|
87
|
-
cells.join("|")
|
|
88
|
-
elsif badge_cell =~ /\A[ \t]+\S/
|
|
89
|
-
# If there is any leading whitespace at all, normalize it to exactly one space
|
|
90
|
-
cells[2] = " " + badge_cell.lstrip
|
|
91
|
-
cells.join("|")
|
|
92
|
-
else
|
|
93
|
-
ln
|
|
94
|
-
end
|
|
95
|
-
else
|
|
96
|
-
ln
|
|
97
|
-
end
|
|
98
|
-
}.reject { |ln|
|
|
99
|
-
if ln.start_with?("| Works with MRI Ruby")
|
|
100
|
-
cells = ln.split("|", -1)
|
|
101
|
-
badge_cell = cells[2] || ""
|
|
102
|
-
badge_cell.strip.empty?
|
|
103
|
-
else
|
|
104
|
-
false
|
|
105
|
-
end
|
|
106
|
-
}.join
|
|
107
|
-
|
|
108
|
-
# Clean up extra repeated whitespace only when it appears between word characters, and only for non-table lines.
|
|
109
|
-
# This preserves Markdown table alignment and spacing around punctuation/symbols.
|
|
110
|
-
content = content.lines.map do |ln|
|
|
111
|
-
if ln.start_with?("|")
|
|
112
|
-
ln
|
|
113
|
-
else
|
|
114
|
-
# Squish only runs of spaces/tabs between word characters
|
|
115
|
-
ln.gsub(/(\w)[ \t]{2,}(\w)/u, "\\1 \\2")
|
|
116
|
-
end
|
|
117
|
-
end.join
|
|
118
|
-
|
|
119
|
-
# Remove reference definitions for removed labels that are no longer used
|
|
120
|
-
unless removed_labels.empty?
|
|
121
|
-
# Unique
|
|
122
|
-
removed_labels.uniq!
|
|
123
|
-
# Determine which labels are still referenced after edits
|
|
124
|
-
still_referenced = {}
|
|
125
|
-
removed_labels.each do |lbl|
|
|
126
|
-
lbl_re = Regexp.escape(lbl)
|
|
127
|
-
# Consider a label referenced only when it appears not as a definition (i.e., not followed by colon)
|
|
128
|
-
still_referenced[lbl] = !!(content =~ /\[#{lbl_re}\](?!:)/)
|
|
129
|
-
end
|
|
130
|
-
|
|
131
|
-
new_lines = content.lines.map do |line|
|
|
132
|
-
if line =~ /^\[(?<lab>[^\]]+)\]:/ && removed_labels.include?(Regexp.last_match(:lab))
|
|
133
|
-
# Only drop if not referenced anymore
|
|
134
|
-
still_referenced[Regexp.last_match(:lab)] ? line : nil
|
|
135
|
-
else
|
|
136
|
-
line
|
|
137
|
-
end
|
|
138
|
-
end.compact
|
|
139
|
-
content = new_lines.join
|
|
140
|
-
end
|
|
141
|
-
|
|
142
|
-
File.open(readme_path, "w") { |f| f.write(content) }
|
|
143
|
-
end
|
|
144
|
-
end
|
|
145
|
-
rescue StandardError => e
|
|
146
|
-
Kettle::Dev.debug_error(e, __method__)
|
|
147
|
-
puts "WARNING: Skipped trimming MRI Ruby badges in README.md due to #{e.class}: #{e.message}"
|
|
148
|
-
end
|
|
149
|
-
|
|
150
|
-
# Synchronize leading grapheme (emoji) between README H1 and gemspec summary/description
|
|
151
|
-
begin
|
|
152
|
-
readme_path = File.join(project_root, "README.md")
|
|
153
|
-
gemspecs = Dir.glob(File.join(project_root, "*.gemspec"))
|
|
154
|
-
if File.file?(readme_path) && !gemspecs.empty?
|
|
155
|
-
gemspec_path = gemspecs.first
|
|
156
|
-
readme = File.read(readme_path)
|
|
157
|
-
first_h1_idx = readme.lines.index { |ln| ln =~ /^#\s+/ }
|
|
158
|
-
chosen_grapheme = nil
|
|
159
|
-
if first_h1_idx
|
|
160
|
-
lines = readme.split("\n", -1)
|
|
161
|
-
h1 = lines[first_h1_idx]
|
|
162
|
-
tail = h1.sub(/^#\s+/, "")
|
|
163
|
-
begin
|
|
164
|
-
emoji_re = Kettle::EmojiRegex::REGEX
|
|
165
|
-
# Extract first emoji grapheme cluster if present
|
|
166
|
-
if tail =~ /\A#{emoji_re.source}/u
|
|
167
|
-
cluster = tail[/\A\X/u]
|
|
168
|
-
chosen_grapheme = cluster unless cluster.to_s.empty?
|
|
169
|
-
end
|
|
170
|
-
rescue StandardError => e
|
|
171
|
-
Kettle::Dev.debug_error(e, __method__)
|
|
172
|
-
# Fallback: take first Unicode grapheme if any non-space char
|
|
173
|
-
chosen_grapheme ||= tail[/\A\X/u]
|
|
174
|
-
end
|
|
175
|
-
end
|
|
176
|
-
|
|
177
|
-
# If no grapheme found in README H1, either use a default in force mode, or ask the user.
|
|
178
|
-
if chosen_grapheme.nil? || chosen_grapheme.empty?
|
|
179
|
-
if ENV.fetch("force", "").to_s =~ ENV_TRUE_RE
|
|
180
|
-
# Non-interactive install: default to pizza slice to match template style.
|
|
181
|
-
chosen_grapheme = "🍕"
|
|
182
|
-
else
|
|
183
|
-
puts "No grapheme found after README H1. Enter a grapheme (emoji/symbol) to use for README, summary, and description:"
|
|
184
|
-
print("Grapheme: ")
|
|
185
|
-
ans = Kettle::Dev::InputAdapter.gets&.strip.to_s
|
|
186
|
-
chosen_grapheme = ans[/\A\X/u].to_s
|
|
187
|
-
# If still empty, skip synchronization silently
|
|
188
|
-
chosen_grapheme = nil if chosen_grapheme.empty?
|
|
189
|
-
end
|
|
190
|
-
end
|
|
191
|
-
|
|
192
|
-
if chosen_grapheme
|
|
193
|
-
# 1) Normalize README H1 to exactly one grapheme + single space after '#'
|
|
194
|
-
begin
|
|
195
|
-
lines = readme.split("\n", -1)
|
|
196
|
-
idx = lines.index { |ln| ln =~ /^#\s+/ }
|
|
197
|
-
if idx
|
|
198
|
-
rest = lines[idx].sub(/^#\s+/, "")
|
|
199
|
-
begin
|
|
200
|
-
emoji_re = Kettle::EmojiRegex::REGEX
|
|
201
|
-
# Remove any leading emojis from the H1 by peeling full grapheme clusters
|
|
202
|
-
tmp = rest.dup
|
|
203
|
-
while tmp =~ /\A#{emoji_re.source}/u
|
|
204
|
-
cluster = tmp[/\A\X/u]
|
|
205
|
-
tmp = tmp[cluster.length..-1].to_s
|
|
206
|
-
end
|
|
207
|
-
rest_wo_emoji = tmp.sub(/\A\s+/, "")
|
|
208
|
-
rescue StandardError => e
|
|
209
|
-
Kettle::Dev.debug_error(e, __method__)
|
|
210
|
-
rest_wo_emoji = rest.sub(/\A\s+/, "")
|
|
211
|
-
end
|
|
212
|
-
# Build H1 with single spaces only around separators; preserve inner spacing in rest_wo_emoji
|
|
213
|
-
new_line = ["#", chosen_grapheme, rest_wo_emoji].join(" ").sub(/^#\s+/, "# ")
|
|
214
|
-
lines[idx] = new_line
|
|
215
|
-
new_readme = lines.join("\n")
|
|
216
|
-
File.open(readme_path, "w") { |f| f.write(new_readme) }
|
|
217
|
-
end
|
|
218
|
-
rescue StandardError => e
|
|
219
|
-
Kettle::Dev.debug_error(e, __method__)
|
|
220
|
-
# ignore README normalization errors
|
|
221
|
-
end
|
|
222
|
-
|
|
223
|
-
# 2) Update gemspec summary and description to start with grapheme + single space
|
|
224
|
-
begin
|
|
225
|
-
gspec = File.read(gemspec_path)
|
|
226
|
-
|
|
227
|
-
normalize_field = lambda do |text, field|
|
|
228
|
-
# Match the assignment line and the first quoted string
|
|
229
|
-
text.gsub(/(\b#{Regexp.escape(field)}\s*=\s*)(["'])([^\"']*)(\2)/) do
|
|
230
|
-
pre = Regexp.last_match(1)
|
|
231
|
-
q = Regexp.last_match(2)
|
|
232
|
-
body = Regexp.last_match(3)
|
|
233
|
-
# Strip existing leading emojis and spaces
|
|
234
|
-
begin
|
|
235
|
-
emoji_re = Kettle::EmojiRegex::REGEX
|
|
236
|
-
tmp = body.dup
|
|
237
|
-
tmp = tmp.sub(/\A\s+/, "")
|
|
238
|
-
while tmp =~ /\A#{emoji_re.source}/u
|
|
239
|
-
cluster = tmp[/\A\X/u]
|
|
240
|
-
tmp = tmp[cluster.length..-1].to_s
|
|
241
|
-
end
|
|
242
|
-
tmp = tmp.sub(/\A\s+/, "")
|
|
243
|
-
body_wo = tmp
|
|
244
|
-
rescue StandardError => e
|
|
245
|
-
Kettle::Dev.debug_error(e, __method__)
|
|
246
|
-
body_wo = body.sub(/\A\s+/, "")
|
|
247
|
-
end
|
|
248
|
-
pre + q + ("#{chosen_grapheme} " + body_wo) + q
|
|
249
|
-
end
|
|
250
|
-
end
|
|
251
|
-
|
|
252
|
-
gspec2 = normalize_field.call(gspec, "spec.summary")
|
|
253
|
-
gspec3 = normalize_field.call(gspec2, "spec.description")
|
|
254
|
-
if gspec3 != gspec
|
|
255
|
-
File.open(gemspec_path, "w") { |f| f.write(gspec3) }
|
|
256
|
-
end
|
|
257
|
-
rescue StandardError => e
|
|
258
|
-
Kettle::Dev.debug_error(e, __method__)
|
|
259
|
-
# ignore gemspec edits on error
|
|
260
|
-
end
|
|
261
|
-
end
|
|
262
|
-
end
|
|
263
|
-
rescue StandardError => e
|
|
264
|
-
puts "WARNING: Skipped grapheme synchronization due to #{e.class}: #{e.message}"
|
|
265
|
-
end
|
|
266
|
-
|
|
267
|
-
# Perform final whitespace normalization for README: only squish whitespace between word characters (non-table lines)
|
|
268
|
-
begin
|
|
269
|
-
readme_path = File.join(project_root, "README.md")
|
|
270
|
-
if File.file?(readme_path)
|
|
271
|
-
content = File.read(readme_path)
|
|
272
|
-
content = content.lines.map do |ln|
|
|
273
|
-
if ln.start_with?("|")
|
|
274
|
-
ln
|
|
275
|
-
else
|
|
276
|
-
ln.gsub(/(\w)[ \t]{2,}(\w)/u, "\\1 \\2")
|
|
277
|
-
end
|
|
278
|
-
end.join
|
|
279
|
-
File.open(readme_path, "w") { |f| f.write(content) }
|
|
280
|
-
end
|
|
281
|
-
rescue StandardError => e
|
|
282
|
-
Kettle::Dev.debug_error(e, __method__)
|
|
283
|
-
# ignore whitespace normalization errors
|
|
284
|
-
end
|
|
285
|
-
|
|
286
|
-
# Validate gemspec homepage points to GitHub and is a non-interpolated string
|
|
287
|
-
begin
|
|
288
|
-
gemspecs = Dir.glob(File.join(project_root, "*.gemspec"))
|
|
289
|
-
if gemspecs.empty?
|
|
290
|
-
puts
|
|
291
|
-
puts "No .gemspec found in #{project_root}; skipping homepage check."
|
|
292
|
-
else
|
|
293
|
-
gemspec_path = gemspecs.first
|
|
294
|
-
if gemspecs.size > 1
|
|
295
|
-
puts
|
|
296
|
-
puts "Multiple gemspecs found; defaulting to #{File.basename(gemspec_path)} for homepage check."
|
|
297
|
-
end
|
|
298
|
-
|
|
299
|
-
content = File.read(gemspec_path)
|
|
300
|
-
homepage_line = content.lines.find { |l| l =~ /\bspec\.homepage\s*=\s*/ }
|
|
301
|
-
if homepage_line.nil?
|
|
302
|
-
puts
|
|
303
|
-
puts "WARNING: spec.homepage not found in #{File.basename(gemspec_path)}."
|
|
304
|
-
puts "This gem should declare a GitHub homepage: https://github.com/<org>/<repo>"
|
|
305
|
-
else
|
|
306
|
-
assigned = homepage_line.split("=", 2).last.to_s.strip
|
|
307
|
-
interpolated = assigned.include?('#{')
|
|
308
|
-
|
|
309
|
-
if assigned.start_with?("\"", "'")
|
|
310
|
-
begin
|
|
311
|
-
assigned = assigned[1..-2]
|
|
312
|
-
rescue
|
|
313
|
-
# leave as-is
|
|
314
|
-
end
|
|
315
|
-
end
|
|
316
|
-
|
|
317
|
-
github_repo_from_url = lambda do |url|
|
|
318
|
-
return unless url
|
|
319
|
-
|
|
320
|
-
url = url.strip
|
|
321
|
-
m = url.match(%r{github\.com[/:]([^/\s:]+)/([^/\s]+?)(?:\.git)?/?\z}i)
|
|
322
|
-
return unless m
|
|
323
|
-
|
|
324
|
-
[m[1], m[2]]
|
|
325
|
-
end
|
|
326
|
-
|
|
327
|
-
github_homepage_literal = lambda do |val|
|
|
328
|
-
return false unless val
|
|
329
|
-
return false if val.include?('#{')
|
|
330
|
-
|
|
331
|
-
v = val.to_s.strip
|
|
332
|
-
if (v.start_with?("\"") && v.end_with?("\"")) || (v.start_with?("'") && v.end_with?("'"))
|
|
333
|
-
v = begin
|
|
334
|
-
v[1..-2]
|
|
335
|
-
rescue
|
|
336
|
-
v
|
|
337
|
-
end
|
|
338
|
-
end
|
|
339
|
-
return false unless v =~ %r{\Ahttps?://github\.com/}i
|
|
340
|
-
|
|
341
|
-
!!github_repo_from_url.call(v)
|
|
342
|
-
end
|
|
343
|
-
|
|
344
|
-
valid_literal = github_homepage_literal.call(assigned)
|
|
345
|
-
|
|
346
|
-
if interpolated || !valid_literal
|
|
347
|
-
puts
|
|
348
|
-
puts "Checking git remote 'origin' to derive GitHub homepage..."
|
|
349
|
-
origin_url = ""
|
|
350
|
-
# Use GitAdapter to avoid hanging and to simplify testing.
|
|
351
|
-
begin
|
|
352
|
-
ga = Kettle::Dev::GitAdapter.new
|
|
353
|
-
origin_url = ga.remote_url("origin") || ga.remotes_with_urls["origin"]
|
|
354
|
-
origin_url = origin_url.to_s.strip
|
|
355
|
-
rescue StandardError => e
|
|
356
|
-
Kettle::Dev.debug_error(e, __method__)
|
|
357
|
-
end
|
|
358
|
-
|
|
359
|
-
org_repo = github_repo_from_url.call(origin_url)
|
|
360
|
-
unless org_repo
|
|
361
|
-
puts "ERROR: git remote 'origin' is not a GitHub URL (or not found): #{origin_url.empty? ? "(none)" : origin_url}"
|
|
362
|
-
puts "To complete installation: set your GitHub repository as the 'origin' remote, and move any other forge to an alternate name."
|
|
363
|
-
puts "Example:"
|
|
364
|
-
puts " git remote rename origin something_else"
|
|
365
|
-
puts " git remote add origin https://github.com/<org>/<repo>.git"
|
|
366
|
-
puts "After fixing, re-run: rake kettle:dev:install"
|
|
367
|
-
task_abort("Aborting: homepage cannot be corrected without a GitHub origin remote.")
|
|
368
|
-
end
|
|
369
|
-
|
|
370
|
-
org, repo = org_repo
|
|
371
|
-
suggested = "https://github.com/#{org}/#{repo}"
|
|
372
|
-
|
|
373
|
-
puts "Current spec.homepage appears #{interpolated ? "interpolated" : "invalid"}: #{assigned}"
|
|
374
|
-
puts "Suggested literal homepage: \"#{suggested}\""
|
|
375
|
-
print("Update #{File.basename(gemspec_path)} to use this homepage? [Y/n]: ")
|
|
376
|
-
do_update =
|
|
377
|
-
if ENV.fetch("force", "").to_s =~ ENV_TRUE_RE
|
|
378
|
-
true
|
|
379
|
-
else
|
|
380
|
-
ans = Kettle::Dev::InputAdapter.gets&.strip
|
|
381
|
-
ans.nil? || ans.empty? || ans =~ /\Ay(es)?\z/i
|
|
382
|
-
end
|
|
383
|
-
|
|
384
|
-
if do_update
|
|
385
|
-
new_line = homepage_line.sub(/=.*/, "= \"#{suggested}\"\n")
|
|
386
|
-
new_content = content.sub(homepage_line, new_line)
|
|
387
|
-
File.open(gemspec_path, "w") { |f| f.write(new_content) }
|
|
388
|
-
puts "Updated spec.homepage in #{File.basename(gemspec_path)} to #{suggested}"
|
|
389
|
-
else
|
|
390
|
-
puts "Skipping update of spec.homepage. You should set it to: #{suggested}"
|
|
391
|
-
end
|
|
392
|
-
end
|
|
393
|
-
end
|
|
394
|
-
end
|
|
395
|
-
rescue StandardError => e
|
|
396
|
-
# Do not swallow intentional task aborts signaled via Kettle::Dev::Error
|
|
397
|
-
raise if e.is_a?(Kettle::Dev::Error)
|
|
398
|
-
|
|
399
|
-
puts "WARNING: An error occurred while checking gemspec homepage: #{e.class}: #{e.message}"
|
|
400
|
-
end
|
|
401
|
-
|
|
402
|
-
# Summary of templating changes
|
|
403
|
-
begin
|
|
404
|
-
results = helpers.template_results
|
|
405
|
-
meaningful = results.select { |_, rec| [:create, :replace, :dir_create, :dir_replace].include?(rec[:action]) }
|
|
406
|
-
puts
|
|
407
|
-
puts "Summary of templating changes:"
|
|
408
|
-
if meaningful.empty?
|
|
409
|
-
puts " (no files were created or replaced by kettle:dev:template)"
|
|
410
|
-
else
|
|
411
|
-
action_labels = {
|
|
412
|
-
create: "Created",
|
|
413
|
-
replace: "Replaced",
|
|
414
|
-
dir_create: "Directory created",
|
|
415
|
-
dir_replace: "Directory replaced",
|
|
416
|
-
}
|
|
417
|
-
[:create, :replace, :dir_create, :dir_replace].each do |sym|
|
|
418
|
-
items = meaningful.select { |_, rec| rec[:action] == sym }.map { |path, _| path }
|
|
419
|
-
next if items.empty?
|
|
420
|
-
|
|
421
|
-
puts " #{action_labels[sym]}:"
|
|
422
|
-
items.sort.each do |abs|
|
|
423
|
-
rel = begin
|
|
424
|
-
abs.start_with?(project_root.to_s) ? abs.sub(/^#{Regexp.escape(project_root.to_s)}\/?/, "") : abs
|
|
425
|
-
rescue
|
|
426
|
-
abs
|
|
427
|
-
end
|
|
428
|
-
puts " - #{rel}"
|
|
429
|
-
end
|
|
430
|
-
end
|
|
431
|
-
end
|
|
432
|
-
rescue StandardError => e
|
|
433
|
-
puts
|
|
434
|
-
puts "Summary of templating changes: (unavailable: #{e.class}: #{e.message})"
|
|
435
|
-
end
|
|
436
|
-
|
|
437
|
-
puts
|
|
438
|
-
puts "Next steps:"
|
|
439
|
-
puts "1) Configure a shared git hooks path (optional, recommended):"
|
|
440
|
-
puts " git config --global core.hooksPath .git-hooks"
|
|
441
|
-
puts
|
|
442
|
-
puts "2) Install binstubs for this gem so the commit-msg tool is available in ./bin:"
|
|
443
|
-
puts " bundle binstubs kettle-dev --path bin"
|
|
444
|
-
puts " # After running, you should have bin/kettle-commit-msg (wrapper)."
|
|
445
|
-
puts
|
|
446
|
-
# Step 3: direnv and .envrc
|
|
447
|
-
envrc_path = File.join(project_root, ".envrc")
|
|
448
|
-
puts "3) Install direnv (if not already):"
|
|
449
|
-
puts " brew install direnv"
|
|
450
|
-
if helpers.modified_by_template?(envrc_path)
|
|
451
|
-
puts " Your .envrc was created/updated by kettle:dev:template."
|
|
452
|
-
puts " It includes PATH_add bin so that executables in ./bin are on PATH when direnv is active."
|
|
453
|
-
puts " This allows running tools without the bin/ prefix inside the project directory."
|
|
454
|
-
else
|
|
455
|
-
begin
|
|
456
|
-
current = File.file?(envrc_path) ? File.read(envrc_path) : ""
|
|
457
|
-
rescue StandardError => e
|
|
458
|
-
Kettle::Dev.debug_error(e, __method__)
|
|
459
|
-
current = ""
|
|
460
|
-
end
|
|
461
|
-
has_path_add = current.lines.any? { |l| l.strip =~ /^PATH_add\s+bin\b/ }
|
|
462
|
-
if has_path_add
|
|
463
|
-
puts " Your .envrc already contains PATH_add bin."
|
|
464
|
-
else
|
|
465
|
-
puts " Adding PATH_add bin to your project's .envrc is recommended to expose ./bin on PATH."
|
|
466
|
-
if helpers.ask("Add PATH_add bin to #{envrc_path}?", false)
|
|
467
|
-
content = current.dup
|
|
468
|
-
insertion = "# Run any command in this project's bin/ without the bin/ prefix\nPATH_add bin\n"
|
|
469
|
-
if content.empty?
|
|
470
|
-
content = insertion
|
|
471
|
-
else
|
|
472
|
-
content = insertion + "\n" + content unless content.start_with?(insertion)
|
|
473
|
-
end
|
|
474
|
-
# Ensure a stale directory at .envrc is removed so the file can be written
|
|
475
|
-
FileUtils.rm_rf(envrc_path) if File.directory?(envrc_path)
|
|
476
|
-
File.open(envrc_path, "w") { |f| f.write(content) }
|
|
477
|
-
puts " Updated #{envrc_path} with PATH_add bin"
|
|
478
|
-
updated_envrc_by_install = true
|
|
479
|
-
else
|
|
480
|
-
puts " Skipping modification of .envrc. You may add 'PATH_add bin' manually at the top."
|
|
481
|
-
end
|
|
482
|
-
end
|
|
483
|
-
end
|
|
484
|
-
|
|
485
|
-
if defined?(updated_envrc_by_install) && updated_envrc_by_install
|
|
486
|
-
allowed_truthy = ENV.fetch("allowed", "").to_s =~ ENV_TRUE_RE
|
|
487
|
-
if allowed_truthy
|
|
488
|
-
puts "Proceeding after .envrc update because allowed=true."
|
|
489
|
-
else
|
|
490
|
-
puts
|
|
491
|
-
puts "IMPORTANT: .envrc was updated during kettle:dev:install."
|
|
492
|
-
puts "Please review it and then run:"
|
|
493
|
-
puts " direnv allow"
|
|
494
|
-
puts
|
|
495
|
-
puts "After that, re-run to resume:"
|
|
496
|
-
puts " bundle exec rake kettle:dev:install allowed=true"
|
|
497
|
-
task_abort("Aborting: direnv allow required after .envrc changes.")
|
|
498
|
-
end
|
|
499
|
-
end
|
|
500
|
-
|
|
501
|
-
# Warn about .env.local and offer to add it to .gitignore
|
|
502
|
-
puts
|
|
503
|
-
puts "WARNING: Do not commit .env.local; it often contains machine-local secrets."
|
|
504
|
-
puts "Ensure your .gitignore includes:"
|
|
505
|
-
puts " # direnv - brew install direnv"
|
|
506
|
-
puts " .env.local"
|
|
507
|
-
|
|
508
|
-
gitignore_path = File.join(project_root, ".gitignore")
|
|
509
|
-
unless helpers.modified_by_template?(gitignore_path)
|
|
510
|
-
begin
|
|
511
|
-
gitignore_current = File.exist?(gitignore_path) ? File.read(gitignore_path) : ""
|
|
512
|
-
rescue StandardError => e
|
|
513
|
-
Kettle::Dev.debug_error(e, __method__)
|
|
514
|
-
gitignore_current = ""
|
|
515
|
-
end
|
|
516
|
-
has_env_local = gitignore_current.lines.any? { |l| l.strip == ".env.local" }
|
|
517
|
-
unless has_env_local
|
|
518
|
-
puts
|
|
519
|
-
puts "Would you like to add '.env.local' to #{gitignore_path}?"
|
|
520
|
-
print("Add to .gitignore now [Y/n]: ")
|
|
521
|
-
answer = Kettle::Dev::InputAdapter.gets&.strip
|
|
522
|
-
# Respect an explicit negative answer even when force=true
|
|
523
|
-
add_it = if answer && answer =~ /\An(o)?\z/i
|
|
524
|
-
false
|
|
525
|
-
elsif ENV.fetch("force", "").to_s =~ ENV_TRUE_RE
|
|
526
|
-
true
|
|
527
|
-
else
|
|
528
|
-
answer.nil? || answer.empty? || answer =~ /\Ay(es)?\z/i
|
|
529
|
-
end
|
|
530
|
-
if add_it
|
|
531
|
-
FileUtils.mkdir_p(File.dirname(gitignore_path))
|
|
532
|
-
mode = File.exist?(gitignore_path) ? "a" : "w"
|
|
533
|
-
File.open(gitignore_path, mode) do |f|
|
|
534
|
-
f.write("\n") unless gitignore_current.empty? || gitignore_current.end_with?("\n")
|
|
535
|
-
unless gitignore_current.lines.any? { |l| l.strip == "# direnv - brew install direnv" }
|
|
536
|
-
f.write("# direnv - brew install direnv\n")
|
|
537
|
-
end
|
|
538
|
-
f.write(".env.local\n")
|
|
539
|
-
end
|
|
540
|
-
puts "Added .env.local to #{gitignore_path}"
|
|
541
|
-
else
|
|
542
|
-
puts "Skipping modification of .gitignore. Remember to add .env.local to avoid committing it."
|
|
543
|
-
end
|
|
544
|
-
end
|
|
545
|
-
end
|
|
546
|
-
|
|
547
|
-
puts
|
|
548
|
-
puts "kettle:dev:install complete."
|
|
549
|
-
end
|
|
550
|
-
end
|
|
551
|
-
end
|
|
552
|
-
end
|
|
553
|
-
end
|