kettle-dev 1.1.10 → 1.1.12
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/.envrc +1 -1
- data/CHANGELOG.md +24 -1
- data/README.md +1 -1
- data/README.md.example +1 -1
- data/Rakefile.example +1 -1
- data/exe/kettle-dev-setup +29 -265
- data/gemfiles/modular/injected.gemfile +1 -1
- data/lib/kettle/dev/modular_gemfiles.rb +110 -0
- data/lib/kettle/dev/setup_cli.rb +405 -0
- data/lib/kettle/dev/tasks/template_task.rb +7 -85
- data/lib/kettle/dev/version.rb +1 -1
- data/lib/kettle/dev.rb +2 -0
- data/sig/kettle/dev/modular_gemfiles.rbs +12 -0
- data/sig/kettle/dev/setup_cli.rbs +8 -0
- data.tar.gz.sig +0 -0
- metadata +8 -4
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 28d4d222b4d85b17a9392755d884f8f0bf0e2193140bc4a5264f54fbf55448e0
|
4
|
+
data.tar.gz: 1495e5a4b252b2b6537727725165dfdb67211c70f078d5b293a3ce912038983c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5861e1d242db51b88b9625e4dfcd09bb598b57ae25b505f3e3674d0a250b325133b2cae2644e111d9db649f690d5f94a31cebfc098a19bfd0d4aeba5f9efebbe
|
7
|
+
data.tar.gz: 78c1003a70069e48e768dbec76298b50a8a2207419a27d398b0ab8fef89e31cfb64d9564ed9fa3c52ca4840c982298885ce1ead3426db4e50e82e251865010c8
|
checksums.yaml.gz.sig
CHANGED
Binary file
|
data/.envrc
CHANGED
@@ -21,7 +21,7 @@ export K_SOUP_COV_DO=true # Means you want code coverage
|
|
21
21
|
export K_SOUP_COV_COMMAND_NAME="Test Coverage"
|
22
22
|
# Available formats are html, xml, rcov, lcov, json, tty
|
23
23
|
export K_SOUP_COV_FORMATTERS="html,xml,rcov,lcov,json,tty"
|
24
|
-
export K_SOUP_COV_MIN_BRANCH=
|
24
|
+
export K_SOUP_COV_MIN_BRANCH=80 # Means you want to enforce X% branch coverage
|
25
25
|
export K_SOUP_COV_MIN_LINE=96 # Means you want to enforce X% line coverage
|
26
26
|
export K_SOUP_COV_MIN_HARD=true # Means you want the build to fail if the coverage thresholds are not met
|
27
27
|
export K_SOUP_COV_MULTI_FORMATTERS=true
|
data/CHANGELOG.md
CHANGED
@@ -24,6 +24,25 @@ Please file a bug if you notice a violation of semantic versioning.
|
|
24
24
|
### Fixed
|
25
25
|
### Security
|
26
26
|
|
27
|
+
## [1.1.12] - 2025-09-09
|
28
|
+
- TAG: [v1.1.12][1.1.12t]
|
29
|
+
- COVERAGE: 94.84% -- 3422/3608 lines in 25 files
|
30
|
+
- BRANCH COVERAGE: 78.97% -- 1386/1755 branches in 25 files
|
31
|
+
- 76.88% documented
|
32
|
+
### Changed
|
33
|
+
- improve Gemfile updates during kettle-dev-setup
|
34
|
+
- git origin-based funding_org derivation during setup
|
35
|
+
|
36
|
+
## [1.1.11] - 2025-09-08
|
37
|
+
- TAG: [v1.1.11][1.1.11t]
|
38
|
+
- COVERAGE: 96.56% -- 3396/3517 lines in 24 files
|
39
|
+
- BRANCH COVERAGE: 81.33% -- 1385/1703 branches in 24 files
|
40
|
+
- 77.06% documented
|
41
|
+
### Changed
|
42
|
+
- move kettle-dev-setup logic into Kettle::Dev::SetupCLI
|
43
|
+
### Fixed
|
44
|
+
- gem dependency detection in kettle-dev-setup to prevent duplication
|
45
|
+
|
27
46
|
## [1.1.10] - 2025-09-08
|
28
47
|
- TAG: [v1.1.10][1.1.10t]
|
29
48
|
- COVERAGE: 97.14% -- 3256/3352 lines in 23 files
|
@@ -571,7 +590,11 @@ Please file a bug if you notice a violation of semantic versioning.
|
|
571
590
|
- Selecting will run the selected workflow via `act`
|
572
591
|
- This may move to its own gem in the future.
|
573
592
|
|
574
|
-
[Unreleased]: https://github.com/kettle-rb/kettle-dev/compare/v1.1.
|
593
|
+
[Unreleased]: https://github.com/kettle-rb/kettle-dev/compare/v1.1.12...HEAD
|
594
|
+
[1.1.12]: https://github.com/kettle-rb/kettle-dev/compare/v1.1.11...v1.1.12
|
595
|
+
[1.1.12t]: https://github.com/kettle-rb/kettle-dev/releases/tag/v1.1.12
|
596
|
+
[1.1.11]: https://github.com/kettle-rb/kettle-dev/compare/v1.1.10...v1.1.11
|
597
|
+
[1.1.11t]: https://github.com/kettle-rb/kettle-dev/releases/tag/v1.1.11
|
575
598
|
[1.1.10]: https://github.com/kettle-rb/kettle-dev/compare/v1.1.9...v1.1.10
|
576
599
|
[1.1.10t]: https://github.com/kettle-rb/kettle-dev/releases/tag/v1.1.10
|
577
600
|
[1.1.9]: https://github.com/kettle-rb/kettle-dev/compare/v1.1.8...v1.1.9
|
data/README.md
CHANGED
@@ -907,7 +907,7 @@ Thanks for RTFM. ☺️
|
|
907
907
|
[📌gitmoji]:https://gitmoji.dev
|
908
908
|
[📌gitmoji-img]:https://img.shields.io/badge/gitmoji_commits-%20%F0%9F%98%9C%20%F0%9F%98%8D-34495e.svg?style=flat-square
|
909
909
|
[🧮kloc]: https://www.youtube.com/watch?v=dQw4w9WgXcQ
|
910
|
-
[🧮kloc-img]: https://img.shields.io/badge/KLOC-3.
|
910
|
+
[🧮kloc-img]: https://img.shields.io/badge/KLOC-3.608-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
|
911
911
|
[🔐security]: SECURITY.md
|
912
912
|
[🔐security-img]: https://img.shields.io/badge/security-policy-259D6C.svg?style=flat
|
913
913
|
[📄copyright-notice-explainer]: https://opensource.stackexchange.com/questions/5778/why-do-licenses-such-as-the-mit-license-specify-a-single-year
|
data/README.md.example
CHANGED
@@ -507,7 +507,7 @@ Thanks for RTFM. ☺️
|
|
507
507
|
[📌gitmoji]:https://gitmoji.dev
|
508
508
|
[📌gitmoji-img]:https://img.shields.io/badge/gitmoji_commits-%20%F0%9F%98%9C%20%F0%9F%98%8D-34495e.svg?style=flat-square
|
509
509
|
[🧮kloc]: https://www.youtube.com/watch?v=dQw4w9WgXcQ
|
510
|
-
[🧮kloc-img]: https://img.shields.io/badge/KLOC-3.
|
510
|
+
[🧮kloc-img]: https://img.shields.io/badge/KLOC-3.608-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
|
511
511
|
[🔐security]: SECURITY.md
|
512
512
|
[🔐security-img]: https://img.shields.io/badge/security-policy-259D6C.svg?style=flat
|
513
513
|
[📄copyright-notice-explainer]: https://opensource.stackexchange.com/questions/5778/why-do-licenses-such-as-the-mit-license-specify-a-single-year
|
data/Rakefile.example
CHANGED
data/exe/kettle-dev-setup
CHANGED
@@ -19,7 +19,7 @@ $stdout.sync = true
|
|
19
19
|
$stderr.sync = true
|
20
20
|
|
21
21
|
script_basename = File.basename(__FILE__)
|
22
|
-
|
22
|
+
File.basename($PROGRAM_NAME)
|
23
23
|
|
24
24
|
begin
|
25
25
|
require "kettle/dev"
|
@@ -31,273 +31,37 @@ rescue StandardError => e
|
|
31
31
|
warn("[kettle-dev-setup] Warning: could not fully load kettle/dev (#{e.class}: #{e.message}). Proceeding with limited features.")
|
32
32
|
end
|
33
33
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
@argv = argv
|
40
|
-
@passthrough = []
|
41
|
-
@options = {}
|
42
|
-
parse!
|
43
|
-
end
|
34
|
+
# Always execute when this file is loaded (e.g., via a Bundler binstub).
|
35
|
+
# Do not guard with __FILE__ == $PROGRAM_NAME because binstubs use Kernel.load.
|
36
|
+
if ARGV.include?("-h") || ARGV.include?("--help")
|
37
|
+
puts <<~USAGE
|
38
|
+
Usage: kettle-dev-setup [options]
|
44
39
|
|
45
|
-
|
46
|
-
debug("run!")
|
47
|
-
say("Starting kettle-dev setup…")
|
48
|
-
prechecks!
|
49
|
-
ensure_dev_deps!
|
50
|
-
ensure_bin_setup!
|
51
|
-
ensure_rakefile!
|
52
|
-
run_bin_setup!
|
53
|
-
run_bundle_binstubs!
|
54
|
-
commit_bootstrap_changes!
|
55
|
-
run_kettle_install!
|
56
|
-
say("kettle-dev setup complete.")
|
57
|
-
end
|
40
|
+
Bootstraps a host gem repository to use kettle-dev tooling.
|
58
41
|
|
59
|
-
|
42
|
+
Options (passed through to rake kettle:dev:install):
|
43
|
+
--allowed=VAL
|
44
|
+
--force
|
45
|
+
--hook_templates=VAL
|
46
|
+
--only=VAL
|
60
47
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
def parse!
|
67
|
-
debug("parse! argv_before=#{@argv.inspect}")
|
68
|
-
parser = OptionParser.new do |opts|
|
69
|
-
opts.banner = "Usage: kettle-dev-setup [options]"
|
70
|
-
# Map CLI flags to environment-style assignments for Rake (key=value)
|
71
|
-
opts.on("--allowed=VAL", "Pass through to kettle:dev:install") { |v| @passthrough << "allowed=#{v}" }
|
72
|
-
opts.on("--force", "Pass through to kettle:dev:install") { @passthrough << "force=true" }
|
73
|
-
opts.on("--hook_templates=VAL", "Pass through to kettle:dev:install") { |v| @passthrough << "hook_templates=#{v}" }
|
74
|
-
opts.on("--only=VAL", "Pass through to kettle:dev:install") { |v| @passthrough << "only=#{v}" }
|
75
|
-
opts.on("-h", "--help", "Show help") do
|
76
|
-
puts opts
|
77
|
-
exit(0)
|
78
|
-
end
|
79
|
-
end
|
80
|
-
begin
|
81
|
-
parser.parse!(@argv)
|
82
|
-
rescue OptionParser::ParseError => e
|
83
|
-
warn("[kettle-dev-setup] #{e.class}: #{e.message}")
|
84
|
-
puts parser
|
85
|
-
exit(2)
|
86
|
-
end
|
87
|
-
# Any remaining args pass through as-is
|
88
|
-
@passthrough.concat(@argv)
|
89
|
-
debug("parse! argv_after=#{@argv.inspect} passthrough=#{@passthrough.inspect}")
|
90
|
-
end
|
91
|
-
|
92
|
-
def say(msg)
|
93
|
-
puts "[kettle-dev-setup] #{msg}"
|
94
|
-
end
|
95
|
-
|
96
|
-
def abort!(msg)
|
97
|
-
debug("abort!(msg=#{msg.inspect})")
|
98
|
-
raise SystemExit, "[kettle-dev-setup] ERROR: #{msg}"
|
99
|
-
end
|
100
|
-
|
101
|
-
def sh!(cmd, env: {})
|
102
|
-
debug("sh!(cmd=#{cmd.inspect}, env_keys=#{env.keys.inspect})")
|
103
|
-
say("exec: #{cmd}")
|
104
|
-
stdout_str, stderr_str, status = Open3.capture3(env, cmd)
|
105
|
-
$stdout.print(stdout_str) unless stdout_str.empty?
|
106
|
-
$stderr.print(stderr_str) unless stderr_str.empty?
|
107
|
-
abort!("Command failed: #{cmd}") unless status.success?
|
108
|
-
end
|
109
|
-
|
110
|
-
# 1. Prechecks
|
111
|
-
def prechecks!
|
112
|
-
debug("prechecks!")
|
113
|
-
abort!("Not inside a git repository (missing .git).") unless Dir.exist?(".git")
|
114
|
-
|
115
|
-
# 1a. git clean
|
116
|
-
begin
|
117
|
-
# Use GitAdapter if available; otherwise shell out
|
118
|
-
if defined?(Kettle::Dev::GitAdapter)
|
119
|
-
ga = Kettle::Dev::GitAdapter.new
|
120
|
-
dirty = !ga.clean?
|
121
|
-
else
|
122
|
-
stdout, _stderr, _status = Open3.capture3("git status --porcelain")
|
123
|
-
dirty = !stdout.strip.empty?
|
124
|
-
end
|
125
|
-
abort!("Git working tree is not clean. Please commit/stash changes and try again.") if dirty
|
126
|
-
rescue StandardError
|
127
|
-
# Fallback if anything goes wrong: be conservative and require clean tree via shell
|
128
|
-
stdout, _stderr, _status = Open3.capture3("git status --porcelain")
|
129
|
-
abort!("Git working tree is not clean. Please commit/stash changes and try again.") unless stdout.strip.empty?
|
130
|
-
end
|
131
|
-
|
132
|
-
# 1b. target is a gem (has *.gemspec)
|
133
|
-
gemspecs = Dir["*.gemspec"]
|
134
|
-
abort!("No gemspec found in current directory.") if gemspecs.empty?
|
135
|
-
@gemspec_path = gemspecs.first
|
136
|
-
|
137
|
-
# 1c. target uses bundler (Gemfile)
|
138
|
-
abort!("No Gemfile found; bundler is required.") unless File.exist?("Gemfile")
|
139
|
-
end
|
140
|
-
|
141
|
-
# 3. Sync dev dependencies from this gem's example gemspec into target gemspec
|
142
|
-
def ensure_dev_deps!
|
143
|
-
debug("ensure_dev_deps!")
|
144
|
-
source_example = installed_path("kettle-dev.gemspec.example")
|
145
|
-
abort!("Internal error: kettle-dev.gemspec.example not found within the installed gem.") unless source_example && File.exist?(source_example)
|
146
|
-
|
147
|
-
example = File.read(source_example)
|
148
|
-
example = example.gsub("{KETTLE|DEV|GEM}", "kettle-dev")
|
149
|
-
|
150
|
-
wanted_lines = example.each_line.map(&:rstrip).select do |line|
|
151
|
-
line =~ /add_development_dependency\s*\(/
|
152
|
-
end
|
153
|
-
|
154
|
-
return if wanted_lines.empty?
|
155
|
-
|
156
|
-
target = File.read(@gemspec_path)
|
157
|
-
|
158
|
-
# Build a map from gem name to the desired line
|
159
|
-
wanted = {}
|
160
|
-
wanted_lines.each do |line|
|
161
|
-
if (m = line.match(/add_development_dependency\s*\(\s*["']([^"']+)["']/))
|
162
|
-
wanted[m[1]] = line
|
163
|
-
end
|
164
|
-
end
|
165
|
-
|
166
|
-
modified = target.dup
|
167
|
-
wanted.each do |gem_name, desired_line|
|
168
|
-
# Replace existing add_development_dependency for this gem, else append near the end
|
169
|
-
if /add_development_dependency\s*\(\s*["']#{Regexp.escape(gem_name)}["']/ =~ modified
|
170
|
-
modified = modified.gsub(/^[^\n]*add_development_dependency\s*\(\s*["']#{Regexp.escape(gem_name)}["'][^\n]*\)$/) do |_|
|
171
|
-
desired_line
|
172
|
-
end
|
173
|
-
elsif (idx = modified.rindex(/\nend\s*\z/))
|
174
|
-
# Insert before the last end, if present, otherwise append
|
175
|
-
before = modified[0...idx]
|
176
|
-
after = modified[idx..-1]
|
177
|
-
insertion = "\n #{desired_line.strip}\n"
|
178
|
-
modified = before + insertion + after
|
179
|
-
else
|
180
|
-
modified << "\n#{desired_line}\n"
|
181
|
-
end
|
182
|
-
end
|
183
|
-
|
184
|
-
if modified != target
|
185
|
-
File.write(@gemspec_path, modified)
|
186
|
-
say("Updated development dependencies in #{@gemspec_path}.")
|
187
|
-
else
|
188
|
-
say("Development dependencies already up to date.")
|
189
|
-
end
|
190
|
-
end
|
191
|
-
|
192
|
-
# 4. Ensure bin/setup present (copy from gem if missing)
|
193
|
-
def ensure_bin_setup!
|
194
|
-
debug("ensure_bin_setup!")
|
195
|
-
target = File.join("bin", "setup")
|
196
|
-
return say("bin/setup present.") if File.exist?(target)
|
197
|
-
|
198
|
-
source = installed_path(File.join("bin", "setup"))
|
199
|
-
abort!("Internal error: source bin/setup not found within installed gem.") unless source && File.exist?(source)
|
200
|
-
FileUtils.mkdir_p("bin")
|
201
|
-
FileUtils.cp(source, target)
|
202
|
-
FileUtils.chmod("+x", target)
|
203
|
-
say("Copied bin/setup.")
|
204
|
-
end
|
205
|
-
|
206
|
-
# 5. Ensure Rakefile matches example (replace or create)
|
207
|
-
def ensure_rakefile!
|
208
|
-
debug("ensure_rakefile!")
|
209
|
-
source = installed_path("Rakefile.example")
|
210
|
-
abort!("Internal error: Rakefile.example not found within installed gem.") unless source && File.exist?(source)
|
211
|
-
|
212
|
-
content = File.read(source)
|
213
|
-
if File.exist?("Rakefile")
|
214
|
-
say("Replacing existing Rakefile with kettle-dev Rakefile.example.")
|
215
|
-
else
|
216
|
-
say("Creating Rakefile from kettle-dev Rakefile.example.")
|
217
|
-
end
|
218
|
-
File.write("Rakefile", content)
|
219
|
-
end
|
220
|
-
|
221
|
-
# 6. Run bin/setup
|
222
|
-
def run_bin_setup!
|
223
|
-
debug("run_bin_setup!")
|
224
|
-
sh!(Shellwords.join([File.join("bin", "setup")]))
|
225
|
-
end
|
226
|
-
|
227
|
-
# 7. Run bundle binstubs --all
|
228
|
-
def run_bundle_binstubs!
|
229
|
-
debug("run_bundle_binstubs!")
|
230
|
-
sh!("bundle exec bundle binstubs --all")
|
231
|
-
end
|
232
|
-
|
233
|
-
# 8. Stage and commit any changes made by the setup before install
|
234
|
-
def commit_bootstrap_changes!
|
235
|
-
debug("commit_bootstrap_changes!")
|
236
|
-
# Only operate if we are inside a git repo (prechecked earlier), and if dirty
|
237
|
-
dirty = begin
|
238
|
-
if defined?(Kettle::Dev::GitAdapter)
|
239
|
-
!Kettle::Dev::GitAdapter.new.clean?
|
240
|
-
else
|
241
|
-
out, _st = Open3.capture2("git", "status", "--porcelain")
|
242
|
-
!out.strip.empty?
|
243
|
-
end
|
244
|
-
rescue StandardError
|
245
|
-
out, _st = Open3.capture2("git", "status", "--porcelain")
|
246
|
-
!out.strip.empty?
|
247
|
-
end
|
248
|
-
unless dirty
|
249
|
-
say("No changes to commit from template bootstrap.")
|
250
|
-
return
|
251
|
-
end
|
252
|
-
# Stage all changes
|
253
|
-
sh!(Shellwords.join(["git", "add", "-A"]))
|
254
|
-
# Compose commit message exactly as required
|
255
|
-
script_name = File.basename(__FILE__)
|
256
|
-
msg = "🎨 Template bootstrap by #{script_name} v#{Kettle::Dev::Version::VERSION}"
|
257
|
-
# Commit; allow empty to be false (we already detected dirty)
|
258
|
-
sh!(Shellwords.join(["git", "commit", "-m", msg]))
|
259
|
-
say("Committed template bootstrap changes.")
|
260
|
-
end
|
261
|
-
|
262
|
-
# 9. Run rake kettle:dev:install with passthrough args
|
263
|
-
def run_kettle_install!
|
264
|
-
debug("run_kettle_install! passthrough=#{@passthrough.inspect}")
|
265
|
-
cmd = ["bin/rake", "kettle:dev:install"] + @passthrough
|
266
|
-
sh!(Shellwords.join(cmd))
|
267
|
-
end
|
268
|
-
|
269
|
-
# Locate files shipped with this gem installation. Handles execution under a checkout as well.
|
270
|
-
def installed_path(rel)
|
271
|
-
debug("installed_path(rel=#{rel.inspect})")
|
272
|
-
# Prefer real gem installation location
|
273
|
-
if defined?(Gem) && (spec = Gem.loaded_specs["kettle-dev"])
|
274
|
-
path = File.join(spec.full_gem_path, rel)
|
275
|
-
return path if File.exist?(path)
|
276
|
-
end
|
277
|
-
# Fallback: relative to this file (useful when running from repo)
|
278
|
-
here = File.expand_path(File.join(__dir__, "..")) # move from exe/ to project root
|
279
|
-
path = File.join(here, rel)
|
280
|
-
return path if File.exist?(path)
|
281
|
-
nil
|
282
|
-
end
|
283
|
-
end
|
284
|
-
end
|
48
|
+
Environment:
|
49
|
+
DEBUG=true # print backtraces on errors
|
50
|
+
USAGE
|
51
|
+
exit 0
|
285
52
|
end
|
286
53
|
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
end
|
301
|
-
else
|
302
|
-
puts "== #{script_basename} v#{Kettle::Dev::Version::VERSION} fin (#{running_as_script}) == (#{script_basename}) =="
|
54
|
+
begin
|
55
|
+
Kettle::Dev::SetupCLI.new(ARGV).run!
|
56
|
+
rescue LoadError => e
|
57
|
+
warn("#{script_basename}: could not load dependency: #{e.class}: #{e.message}")
|
58
|
+
warn(e.backtrace.join("\n")) if ENV["DEBUG"]
|
59
|
+
exit(1)
|
60
|
+
rescue SystemExit => e
|
61
|
+
warn("#{script_basename}: exited (status=#{e.status}, msg=#{e.message})") if e.status != 0
|
62
|
+
raise
|
63
|
+
rescue StandardError => e
|
64
|
+
warn("#{script_basename}: unexpected error: #{e.class}: #{e.message}")
|
65
|
+
warn(e.backtrace.join("\n")) if ENV["DEBUG"]
|
66
|
+
exit(1)
|
303
67
|
end
|
@@ -2,7 +2,7 @@
|
|
2
2
|
# visibility and discoverability on RubyGems.org.
|
3
3
|
# However, this gem sits underneath all my other gems, and also "depends on" many of them.
|
4
4
|
# So instead of depending on them directly it injects them into the other gem's gemspec on install.
|
5
|
-
# This gem its injected dev dependencies, will install on Ruby down to 2.3.x.
|
5
|
+
# This gem, and its injected dev dependencies, will install on Ruby down to 2.3.x.
|
6
6
|
# This gem does not inject runtime dependencies.
|
7
7
|
# Thus, dev dependencies injected into gemspecs must have
|
8
8
|
#
|
@@ -0,0 +1,110 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kettle
|
4
|
+
module Dev
|
5
|
+
# Utilities for copying modular Gemfiles and related directories
|
6
|
+
# in a DRY fashion. Used by both the template rake task and the
|
7
|
+
# setup CLI to ensure gemfiles/modular/* are present before use.
|
8
|
+
module ModularGemfiles
|
9
|
+
MODULAR_GEMFILE_DIR = "gemfiles/modular"
|
10
|
+
|
11
|
+
module_function
|
12
|
+
|
13
|
+
# Copy the modular gemfiles and nested directories from the gem
|
14
|
+
# checkout into the target project, prompting where appropriate
|
15
|
+
# via the provided helpers.
|
16
|
+
#
|
17
|
+
# @param helpers [Kettle::Dev::TemplateHelpers] helper API
|
18
|
+
# @param project_root [String] destination project root
|
19
|
+
# @param gem_checkout_root [String] kettle-dev checkout root (source)
|
20
|
+
# @param min_ruby [Gem::Version, nil] minimum Ruby version (for style.gemfile tuning)
|
21
|
+
# @return [void]
|
22
|
+
def sync!(helpers:, project_root:, gem_checkout_root:, min_ruby: nil)
|
23
|
+
# 4a) gemfiles/modular/*.gemfile except style.gemfile (handled below)
|
24
|
+
modular_gemfiles = %w[
|
25
|
+
coverage
|
26
|
+
debug
|
27
|
+
documentation
|
28
|
+
injected
|
29
|
+
optional
|
30
|
+
runtime_heads
|
31
|
+
x_std_libs
|
32
|
+
]
|
33
|
+
modular_gemfiles.each do |base|
|
34
|
+
modular_gemfile = "#{base}.gemfile"
|
35
|
+
src = helpers.prefer_example(File.join(gem_checkout_root, MODULAR_GEMFILE_DIR, modular_gemfile))
|
36
|
+
dest = File.join(project_root, MODULAR_GEMFILE_DIR, modular_gemfile)
|
37
|
+
helpers.copy_file_with_prompt(src, dest, allow_create: true, allow_replace: true)
|
38
|
+
end
|
39
|
+
|
40
|
+
# 4b) gemfiles/modular/style.gemfile with dynamic rubocop constraints
|
41
|
+
modular_gemfile = "style.gemfile"
|
42
|
+
src = helpers.prefer_example(File.join(gem_checkout_root, MODULAR_GEMFILE_DIR, modular_gemfile))
|
43
|
+
dest = File.join(project_root, MODULAR_GEMFILE_DIR, modular_gemfile)
|
44
|
+
if File.basename(src).sub(/\.example\z/, "") == "style.gemfile"
|
45
|
+
helpers.copy_file_with_prompt(src, dest, allow_create: true, allow_replace: true) do |content|
|
46
|
+
# Adjust rubocop-lts constraint based on min_ruby
|
47
|
+
version_map = [
|
48
|
+
[Gem::Version.new("1.8"), "~> 0.1"],
|
49
|
+
[Gem::Version.new("1.9"), "~> 2.0"],
|
50
|
+
[Gem::Version.new("2.0"), "~> 4.0"],
|
51
|
+
[Gem::Version.new("2.1"), "~> 6.0"],
|
52
|
+
[Gem::Version.new("2.2"), "~> 8.0"],
|
53
|
+
[Gem::Version.new("2.3"), "~> 10.0"],
|
54
|
+
[Gem::Version.new("2.4"), "~> 12.0"],
|
55
|
+
[Gem::Version.new("2.5"), "~> 14.0"],
|
56
|
+
[Gem::Version.new("2.6"), "~> 16.0"],
|
57
|
+
[Gem::Version.new("2.7"), "~> 18.0"],
|
58
|
+
[Gem::Version.new("3.0"), "~> 20.0"],
|
59
|
+
[Gem::Version.new("3.1"), "~> 22.0"],
|
60
|
+
[Gem::Version.new("3.2"), "~> 24.0"],
|
61
|
+
[Gem::Version.new("3.3"), "~> 26.0"],
|
62
|
+
[Gem::Version.new("3.4"), "~> 28.0"],
|
63
|
+
]
|
64
|
+
new_constraint = nil
|
65
|
+
rubocop_ruby_gem_version = nil
|
66
|
+
ruby1_8 = version_map.first
|
67
|
+
begin
|
68
|
+
if min_ruby
|
69
|
+
version_map.reverse_each do |min, req|
|
70
|
+
if min_ruby >= min
|
71
|
+
new_constraint = req
|
72
|
+
rubocop_ruby_gem_version = min.segments.join("_")
|
73
|
+
break
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
if !new_constraint || !rubocop_ruby_gem_version
|
78
|
+
# A gem with no declared minimum ruby is effectively >= 1.8.7
|
79
|
+
new_constraint = ruby1_8[1]
|
80
|
+
rubocop_ruby_gem_version = ruby1_8[0].segments.join("_")
|
81
|
+
end
|
82
|
+
rescue StandardError => e
|
83
|
+
Kettle::Dev.debug_error(e, __method__) if defined?(Kettle::Dev.debug_error)
|
84
|
+
# ignore, use default
|
85
|
+
ensure
|
86
|
+
new_constraint ||= ruby1_8[1]
|
87
|
+
rubocop_ruby_gem_version ||= ruby1_8[0].segments.join("_")
|
88
|
+
end
|
89
|
+
if new_constraint && rubocop_ruby_gem_version
|
90
|
+
token = "{RUBOCOP|LTS|CONSTRAINT}"
|
91
|
+
content.gsub!(token, new_constraint) if content.include?(token)
|
92
|
+
token = "{RUBOCOP|RUBY|GEM}"
|
93
|
+
content.gsub!(token, "rubocop-ruby#{rubocop_ruby_gem_version}") if content.include?(token)
|
94
|
+
end
|
95
|
+
content
|
96
|
+
end
|
97
|
+
else
|
98
|
+
helpers.copy_file_with_prompt(src, dest, allow_create: true, allow_replace: true)
|
99
|
+
end
|
100
|
+
|
101
|
+
# 4c) Copy modular directories with nested/versioned files
|
102
|
+
%w[erb mutex_m stringio x_std_libs].each do |dir|
|
103
|
+
src_dir = File.join(gem_checkout_root, MODULAR_GEMFILE_DIR, dir)
|
104
|
+
dest_dir = File.join(project_root, MODULAR_GEMFILE_DIR, dir)
|
105
|
+
helpers.copy_dir_with_prompt(src_dir, dest_dir)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,405 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "fileutils"
|
4
|
+
require "shellwords"
|
5
|
+
require "open3"
|
6
|
+
require "optparse"
|
7
|
+
|
8
|
+
module Kettle
|
9
|
+
module Dev
|
10
|
+
# SetupCLI bootstraps a host gem repository to use kettle-dev tooling.
|
11
|
+
# It performs prechecks, syncs development dependencies, ensures bin/setup and
|
12
|
+
# Rakefile templates, runs setup tasks, and invokes kettle:dev:install.
|
13
|
+
#
|
14
|
+
# Usage:
|
15
|
+
# Kettle::Dev::SetupCLI.new(ARGV).run!
|
16
|
+
#
|
17
|
+
# Options are parsed from argv and passed through to the rake task as
|
18
|
+
# key=value pairs (e.g., --force => force=true).
|
19
|
+
class SetupCLI
|
20
|
+
# @param argv [Array<String>] CLI arguments
|
21
|
+
def initialize(argv)
|
22
|
+
@argv = argv
|
23
|
+
@passthrough = []
|
24
|
+
@options = {}
|
25
|
+
parse!
|
26
|
+
end
|
27
|
+
|
28
|
+
# Execute the full setup workflow.
|
29
|
+
# @return [void]
|
30
|
+
def run!
|
31
|
+
say("Starting kettle-dev setup…")
|
32
|
+
prechecks!
|
33
|
+
ensure_dev_deps!
|
34
|
+
ensure_gemfile_from_example!
|
35
|
+
ensure_modular_gemfiles!
|
36
|
+
ensure_bin_setup!
|
37
|
+
ensure_rakefile!
|
38
|
+
run_bin_setup!
|
39
|
+
run_bundle_binstubs!
|
40
|
+
commit_bootstrap_changes!
|
41
|
+
run_kettle_install!
|
42
|
+
say("kettle-dev setup complete.")
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def debug(msg)
|
48
|
+
return if ENV.fetch("DEBUG", "false").casecmp("true").nonzero?
|
49
|
+
$stderr.puts("[kettle-dev-setup] DEBUG: #{msg}")
|
50
|
+
end
|
51
|
+
|
52
|
+
# Attempt to derive a funding organization from the git remote 'origin' when
|
53
|
+
# not explicitly provided via env or .opencollective.yml.
|
54
|
+
# This is a soft helper that only sets ENV["FUNDING_ORG"] if a plausible
|
55
|
+
# GitHub org can be parsed from the origin URL.
|
56
|
+
# @return [void]
|
57
|
+
def derive_funding_org_from_git_if_missing!
|
58
|
+
# Respect explicit bypass
|
59
|
+
env_val = ENV["FUNDING_ORG"]
|
60
|
+
return if env_val && env_val.to_s.strip.casecmp("false").zero?
|
61
|
+
|
62
|
+
# If already provided via env, do nothing
|
63
|
+
return if ENV["FUNDING_ORG"].to_s.strip != ""
|
64
|
+
return if ENV["OPENCOLLECTIVE_HANDLE"].to_s.strip != ""
|
65
|
+
|
66
|
+
# If project provides an .opencollective.yml with org, do nothing
|
67
|
+
begin
|
68
|
+
oc_path = File.join(Dir.pwd, ".opencollective.yml")
|
69
|
+
if File.file?(oc_path)
|
70
|
+
txt = File.read(oc_path)
|
71
|
+
return if txt =~ /\borg:\s*([\w\-]+)/i
|
72
|
+
end
|
73
|
+
rescue StandardError => e
|
74
|
+
debug("Reading .opencollective.yml failed: #{e.class}: #{e.message}")
|
75
|
+
end
|
76
|
+
|
77
|
+
# Attempt to get origin URL and parse GitHub org
|
78
|
+
begin
|
79
|
+
ga = Kettle::Dev::GitAdapter.new
|
80
|
+
origin_url = nil
|
81
|
+
origin_url = ga.remote_url("origin") if ga.respond_to?(:remote_url)
|
82
|
+
if origin_url.nil? && ga.respond_to?(:remotes_with_urls)
|
83
|
+
begin
|
84
|
+
urls = ga.remotes_with_urls
|
85
|
+
origin_url = urls["origin"] if urls
|
86
|
+
rescue StandardError => e
|
87
|
+
# graceful fallback if adapter backend errs; keep silent behavior
|
88
|
+
debug("remotes_with_urls failed: #{e.class}: #{e.message}")
|
89
|
+
end
|
90
|
+
end
|
91
|
+
origin_url = origin_url.to_s.strip
|
92
|
+
if (m = origin_url.match(%r{github\.com[/:]([^/]+)/}i))
|
93
|
+
org = m[1].to_s
|
94
|
+
if !org.empty?
|
95
|
+
ENV["FUNDING_ORG"] = org
|
96
|
+
debug("Derived FUNDING_ORG from git origin: #{org}")
|
97
|
+
end
|
98
|
+
end
|
99
|
+
rescue StandardError => e
|
100
|
+
# Be silent; this is a best-effort and shouldn't fail setup
|
101
|
+
debug("Could not derive funding org from git: #{e.class}: #{e.message}")
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def parse!
|
106
|
+
parser = OptionParser.new do |opts|
|
107
|
+
opts.banner = "Usage: kettle-dev-setup [options]"
|
108
|
+
opts.on("--allowed=VAL", "Pass through to kettle:dev:install") { |v| @passthrough << "allowed=#{v}" }
|
109
|
+
opts.on("--force", "Pass through to kettle:dev:install") { @passthrough << "force=true" }
|
110
|
+
opts.on("--hook_templates=VAL", "Pass through to kettle:dev:install") { |v| @passthrough << "hook_templates=#{v}" }
|
111
|
+
opts.on("--only=VAL", "Pass through to kettle:dev:install") { |v| @passthrough << "only=#{v}" }
|
112
|
+
opts.on("-h", "--help", "Show help") do
|
113
|
+
puts opts
|
114
|
+
Kettle::Dev::ExitAdapter.exit(0)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
begin
|
118
|
+
parser.parse!(@argv)
|
119
|
+
rescue OptionParser::ParseError => e
|
120
|
+
warn("[kettle-dev-setup] #{e.class}: #{e.message}")
|
121
|
+
puts parser
|
122
|
+
Kettle::Dev::ExitAdapter.exit(2)
|
123
|
+
end
|
124
|
+
@passthrough.concat(@argv)
|
125
|
+
end
|
126
|
+
|
127
|
+
def say(msg)
|
128
|
+
puts "[kettle-dev-setup] #{msg}"
|
129
|
+
end
|
130
|
+
|
131
|
+
def abort!(msg)
|
132
|
+
Kettle::Dev::ExitAdapter.abort("[kettle-dev-setup] ERROR: #{msg}")
|
133
|
+
end
|
134
|
+
|
135
|
+
def sh!(cmd, env: {})
|
136
|
+
say("exec: #{cmd}")
|
137
|
+
stdout_str, stderr_str, status = Open3.capture3(env, cmd)
|
138
|
+
$stdout.print(stdout_str) unless stdout_str.empty?
|
139
|
+
$stderr.print(stderr_str) unless stderr_str.empty?
|
140
|
+
abort!("Command failed: #{cmd}") unless status.success?
|
141
|
+
end
|
142
|
+
|
143
|
+
# 1. Prechecks
|
144
|
+
def prechecks!
|
145
|
+
abort!("Not inside a git repository (missing .git).") unless Dir.exist?(".git")
|
146
|
+
|
147
|
+
# Ensure clean working tree
|
148
|
+
begin
|
149
|
+
if defined?(Kettle::Dev::GitAdapter)
|
150
|
+
dirty = !Kettle::Dev::GitAdapter.new.clean?
|
151
|
+
else
|
152
|
+
stdout, _stderr, _status = Open3.capture3("git status --porcelain")
|
153
|
+
dirty = !stdout.strip.empty?
|
154
|
+
end
|
155
|
+
abort!("Git working tree is not clean. Please commit/stash changes and try again.") if dirty
|
156
|
+
rescue StandardError
|
157
|
+
stdout, _stderr, _status = Open3.capture3("git status --porcelain")
|
158
|
+
abort!("Git working tree is not clean. Please commit/stash changes and try again.") unless stdout.strip.empty?
|
159
|
+
end
|
160
|
+
|
161
|
+
# gemspec
|
162
|
+
gemspecs = Dir["*.gemspec"]
|
163
|
+
abort!("No gemspec found in current directory.") if gemspecs.empty?
|
164
|
+
@gemspec_path = gemspecs.first
|
165
|
+
|
166
|
+
# Gemfile
|
167
|
+
abort!("No Gemfile found; bundler is required.") unless File.exist?("Gemfile")
|
168
|
+
|
169
|
+
# Seed FUNDING_ORG from git remote origin org when not provided elsewhere
|
170
|
+
derive_funding_org_from_git_if_missing!
|
171
|
+
end
|
172
|
+
|
173
|
+
# 3. Sync dev dependencies from this gem's example gemspec into target gemspec
|
174
|
+
def ensure_dev_deps!
|
175
|
+
source_example = installed_path("kettle-dev.gemspec.example")
|
176
|
+
abort!("Internal error: kettle-dev.gemspec.example not found within the installed gem.") unless source_example && File.exist?(source_example)
|
177
|
+
|
178
|
+
example = File.read(source_example)
|
179
|
+
example = example.gsub("{KETTLE|DEV|GEM}", "kettle-dev")
|
180
|
+
|
181
|
+
wanted_lines = example.each_line.map(&:rstrip).select { |line| line =~ /add_development_dependency\s*\(?/ }
|
182
|
+
return if wanted_lines.empty?
|
183
|
+
|
184
|
+
target = File.read(@gemspec_path)
|
185
|
+
|
186
|
+
# Build gem=>desired line map
|
187
|
+
wanted = {}
|
188
|
+
wanted_lines.each do |line|
|
189
|
+
if (m = line.match(/add_development_dependency\s*\(?\s*["']([^"']+)["']/))
|
190
|
+
wanted[m[1]] = line
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
modified = target.dup
|
195
|
+
wanted.each do |gem_name, desired_line|
|
196
|
+
lines = modified.lines
|
197
|
+
found = false
|
198
|
+
lines.map! do |ln|
|
199
|
+
if ln =~ /add_development_dependency\s*\(?\s*["']#{Regexp.escape(gem_name)}["']/
|
200
|
+
found = true
|
201
|
+
indent = ln[/^\s*/] || ""
|
202
|
+
"#{indent}#{desired_line.strip}\n"
|
203
|
+
else
|
204
|
+
ln
|
205
|
+
end
|
206
|
+
end
|
207
|
+
modified = lines.join
|
208
|
+
|
209
|
+
next if found
|
210
|
+
|
211
|
+
if (idx = modified.rindex(/\nend\s*\z/))
|
212
|
+
before = modified[0...idx]
|
213
|
+
after = modified[idx..-1]
|
214
|
+
insertion = "\n #{desired_line.strip}\n"
|
215
|
+
modified = before + insertion + after
|
216
|
+
else
|
217
|
+
modified << "\n#{desired_line}\n"
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
if modified != target
|
222
|
+
File.write(@gemspec_path, modified)
|
223
|
+
say("Updated development dependencies in #{@gemspec_path}.")
|
224
|
+
else
|
225
|
+
say("Development dependencies already up to date.")
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
# 4. Ensure bin/setup present (copy from gem if missing)
|
230
|
+
def ensure_bin_setup!
|
231
|
+
target = File.join("bin", "setup")
|
232
|
+
return say("bin/setup present.") if File.exist?(target)
|
233
|
+
|
234
|
+
source = installed_path(File.join("bin", "setup"))
|
235
|
+
abort!("Internal error: source bin/setup not found within installed gem.") unless source && File.exist?(source)
|
236
|
+
FileUtils.mkdir_p("bin")
|
237
|
+
FileUtils.cp(source, target)
|
238
|
+
FileUtils.chmod("+x", target)
|
239
|
+
say("Copied bin/setup.")
|
240
|
+
end
|
241
|
+
|
242
|
+
# 3b. Ensure Gemfile contains required lines from example without duplicating directives
|
243
|
+
# - Copies source, git_source, gemspec, and eval_gemfile lines that are missing
|
244
|
+
# - Idempotent (running multiple times does not duplicate entries)
|
245
|
+
def ensure_gemfile_from_example!
|
246
|
+
source_path = installed_path("Gemfile.example")
|
247
|
+
abort!("Internal error: Gemfile.example not found within installed gem.") unless source_path && File.exist?(source_path)
|
248
|
+
|
249
|
+
example = File.read(source_path)
|
250
|
+
target_path = "Gemfile"
|
251
|
+
target = File.exist?(target_path) ? File.read(target_path) : ""
|
252
|
+
|
253
|
+
# Extract interesting lines from example
|
254
|
+
ex_sources = []
|
255
|
+
ex_git_sources = [] # names (e.g., :github)
|
256
|
+
ex_git_source_lines = {}
|
257
|
+
ex_has_gemspec = false
|
258
|
+
ex_eval_paths = []
|
259
|
+
|
260
|
+
example.each_line do |ln|
|
261
|
+
s = ln.strip
|
262
|
+
next if s.empty?
|
263
|
+
if s.start_with?("source ")
|
264
|
+
ex_sources << ln.rstrip
|
265
|
+
elsif (m = s.match(/^git_source\(\s*:(\w+)\s*\)/))
|
266
|
+
name = m[1]
|
267
|
+
ex_git_sources << name
|
268
|
+
ex_git_source_lines[name] = ln.rstrip
|
269
|
+
elsif s.start_with?("gemspec")
|
270
|
+
ex_has_gemspec = true
|
271
|
+
elsif (m = s.match(/^eval_gemfile\s+["']([^"']+)["']/))
|
272
|
+
ex_eval_paths << m[1]
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
# Scan target for presence
|
277
|
+
tg_sources = target.each_line.map(&:rstrip).select { |l| l.strip.start_with?("source ") }
|
278
|
+
tg_git_sources = {}
|
279
|
+
target.each_line do |ln|
|
280
|
+
if (m = ln.strip.match(/^git_source\(\s*:(\w+)\s*\)/))
|
281
|
+
tg_git_sources[m[1]] = true
|
282
|
+
end
|
283
|
+
end
|
284
|
+
tg_has_gemspec = !!target.each_line.find { |l| l.strip.start_with?("gemspec") }
|
285
|
+
tg_eval_paths = target.each_line.map do |ln|
|
286
|
+
if (m = ln.strip.match(/^eval_gemfile\s+["']([^"']+)["']/))
|
287
|
+
m[1]
|
288
|
+
end
|
289
|
+
end.compact
|
290
|
+
|
291
|
+
additions = []
|
292
|
+
# Add missing sources (exact line match)
|
293
|
+
ex_sources.each do |src_line|
|
294
|
+
additions << src_line unless tg_sources.include?(src_line)
|
295
|
+
end
|
296
|
+
# Add missing git_source by name
|
297
|
+
ex_git_sources.each do |name|
|
298
|
+
additions << ex_git_source_lines[name] unless tg_git_sources[name]
|
299
|
+
end
|
300
|
+
# Add gemspec if example has it and target lacks it
|
301
|
+
additions << "gemspec" if ex_has_gemspec && !tg_has_gemspec
|
302
|
+
# Add missing eval_gemfile paths (recreate the exact example line when possible)
|
303
|
+
ex_eval_paths.each do |path|
|
304
|
+
next if tg_eval_paths.include?(path)
|
305
|
+
additions << "eval_gemfile \"#{path}\""
|
306
|
+
end
|
307
|
+
|
308
|
+
return say("Gemfile already contains required entries from example.") if additions.empty?
|
309
|
+
|
310
|
+
# Ensure file ends with a newline
|
311
|
+
target << "\n" unless target.end_with?("\n") || target.empty?
|
312
|
+
new_content = target + additions.join("\n") + "\n"
|
313
|
+
File.write(target_path, new_content)
|
314
|
+
say("Updated Gemfile with entries from Gemfile.example (added #{additions.size}).")
|
315
|
+
end
|
316
|
+
|
317
|
+
# 3c. Ensure gemfiles/modular/* are present (copied like template task)
|
318
|
+
def ensure_modular_gemfiles!
|
319
|
+
helpers = Kettle::Dev::TemplateHelpers
|
320
|
+
project_root = helpers.project_root
|
321
|
+
gem_checkout_root = helpers.gem_checkout_root
|
322
|
+
# Gather min_ruby for style.gemfile adjustments
|
323
|
+
min_ruby = begin
|
324
|
+
md = helpers.gemspec_metadata(project_root)
|
325
|
+
md[:min_ruby]
|
326
|
+
rescue StandardError
|
327
|
+
nil
|
328
|
+
end
|
329
|
+
Kettle::Dev::ModularGemfiles.sync!(
|
330
|
+
helpers: helpers,
|
331
|
+
project_root: project_root,
|
332
|
+
gem_checkout_root: gem_checkout_root,
|
333
|
+
min_ruby: min_ruby,
|
334
|
+
)
|
335
|
+
end
|
336
|
+
|
337
|
+
# 5. Ensure Rakefile matches example (replace or create)
|
338
|
+
def ensure_rakefile!
|
339
|
+
source = installed_path("Rakefile.example")
|
340
|
+
abort!("Internal error: Rakefile.example not found within installed gem.") unless source && File.exist?(source)
|
341
|
+
|
342
|
+
content = File.read(source)
|
343
|
+
if File.exist?("Rakefile")
|
344
|
+
say("Replacing existing Rakefile with kettle-dev Rakefile.example.")
|
345
|
+
else
|
346
|
+
say("Creating Rakefile from kettle-dev Rakefile.example.")
|
347
|
+
end
|
348
|
+
File.write("Rakefile", content)
|
349
|
+
end
|
350
|
+
|
351
|
+
# 6. Run bin/setup
|
352
|
+
def run_bin_setup!
|
353
|
+
sh!(Shellwords.join([File.join("bin", "setup")]))
|
354
|
+
end
|
355
|
+
|
356
|
+
# 7. Run bundle binstubs --all
|
357
|
+
def run_bundle_binstubs!
|
358
|
+
sh!("bundle exec bundle binstubs --all")
|
359
|
+
end
|
360
|
+
|
361
|
+
# 8. Commit template bootstrap changes if any
|
362
|
+
def commit_bootstrap_changes!
|
363
|
+
dirty = begin
|
364
|
+
if defined?(Kettle::Dev::GitAdapter)
|
365
|
+
!Kettle::Dev::GitAdapter.new.clean?
|
366
|
+
else
|
367
|
+
out, _st = Open3.capture2("git", "status", "--porcelain")
|
368
|
+
!out.strip.empty?
|
369
|
+
end
|
370
|
+
rescue StandardError
|
371
|
+
out, _st = Open3.capture2("git", "status", "--porcelain")
|
372
|
+
!out.strip.empty?
|
373
|
+
end
|
374
|
+
unless dirty
|
375
|
+
say("No changes to commit from template bootstrap.")
|
376
|
+
return
|
377
|
+
end
|
378
|
+
sh!(Shellwords.join(["git", "add", "-A"]))
|
379
|
+
msg = "🎨 Template bootstrap by kettle-dev-setup v#{Kettle::Dev::Version::VERSION}"
|
380
|
+
sh!(Shellwords.join(["git", "commit", "-m", msg]))
|
381
|
+
say("Committed template bootstrap changes.")
|
382
|
+
end
|
383
|
+
|
384
|
+
# 9. Invoke rake install task with passthrough
|
385
|
+
def run_kettle_install!
|
386
|
+
cmd = ["bin/rake", "kettle:dev:install"] + @passthrough
|
387
|
+
sh!(Shellwords.join(cmd))
|
388
|
+
end
|
389
|
+
|
390
|
+
# Resolve a path to files shipped within the gem or repo checkout
|
391
|
+
# @param rel [String]
|
392
|
+
# @return [String, nil]
|
393
|
+
def installed_path(rel)
|
394
|
+
if defined?(Gem) && (spec = Gem.loaded_specs["kettle-dev"])
|
395
|
+
path = File.join(spec.full_gem_path, rel)
|
396
|
+
return path if File.exist?(path)
|
397
|
+
end
|
398
|
+
here = File.expand_path(File.join(__dir__, "..", "..", "..")) # lib/kettle/dev/ -> project root
|
399
|
+
path = File.join(here, rel)
|
400
|
+
return path if File.exist?(path)
|
401
|
+
nil
|
402
|
+
end
|
403
|
+
end
|
404
|
+
end
|
405
|
+
end
|
@@ -104,91 +104,13 @@ module Kettle
|
|
104
104
|
allow_replace: true,
|
105
105
|
)
|
106
106
|
|
107
|
-
#
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
injected
|
115
|
-
optional
|
116
|
-
runtime_heads
|
117
|
-
x_std_libs
|
118
|
-
]
|
119
|
-
modular_gemfiles.each do |base|
|
120
|
-
modular_gemfile = "#{base}.gemfile"
|
121
|
-
src = helpers.prefer_example(File.join(gem_checkout_root, MODULAR_GEMFILE_DIR, modular_gemfile))
|
122
|
-
dest = File.join(project_root, MODULAR_GEMFILE_DIR, modular_gemfile)
|
123
|
-
helpers.copy_file_with_prompt(src, dest, allow_create: true, allow_replace: true)
|
124
|
-
end
|
125
|
-
|
126
|
-
# 4b) gemfiles/modular/style.gemfile
|
127
|
-
modular_gemfile = "style.gemfile"
|
128
|
-
src = helpers.prefer_example(File.join(gem_checkout_root, MODULAR_GEMFILE_DIR, modular_gemfile))
|
129
|
-
dest = File.join(project_root, MODULAR_GEMFILE_DIR, modular_gemfile)
|
130
|
-
if File.basename(src).sub(/\.example\z/, "") == "style.gemfile"
|
131
|
-
helpers.copy_file_with_prompt(src, dest, allow_create: true, allow_replace: true) do |content|
|
132
|
-
# Adjust rubocop-lts constraint based on min_ruby
|
133
|
-
version_map = [
|
134
|
-
[Gem::Version.new("1.8"), "~> 0.1"],
|
135
|
-
[Gem::Version.new("1.9"), "~> 2.0"],
|
136
|
-
[Gem::Version.new("2.0"), "~> 4.0"],
|
137
|
-
[Gem::Version.new("2.1"), "~> 6.0"],
|
138
|
-
[Gem::Version.new("2.2"), "~> 8.0"],
|
139
|
-
[Gem::Version.new("2.3"), "~> 10.0"],
|
140
|
-
[Gem::Version.new("2.4"), "~> 12.0"],
|
141
|
-
[Gem::Version.new("2.5"), "~> 14.0"],
|
142
|
-
[Gem::Version.new("2.6"), "~> 16.0"],
|
143
|
-
[Gem::Version.new("2.7"), "~> 18.0"],
|
144
|
-
[Gem::Version.new("3.0"), "~> 20.0"],
|
145
|
-
[Gem::Version.new("3.1"), "~> 22.0"],
|
146
|
-
[Gem::Version.new("3.2"), "~> 24.0"],
|
147
|
-
[Gem::Version.new("3.3"), "~> 26.0"],
|
148
|
-
[Gem::Version.new("3.4"), "~> 28.0"],
|
149
|
-
]
|
150
|
-
new_constraint = nil
|
151
|
-
rubocop_ruby_gem_version = nil
|
152
|
-
ruby1_8 = version_map.first
|
153
|
-
begin
|
154
|
-
if min_ruby
|
155
|
-
version_map.reverse_each do |min, req|
|
156
|
-
if min_ruby >= min
|
157
|
-
new_constraint = req
|
158
|
-
rubocop_ruby_gem_version = min.segments.join("_")
|
159
|
-
break
|
160
|
-
end
|
161
|
-
end
|
162
|
-
end
|
163
|
-
if !new_constraint || !rubocop_ruby_gem_version
|
164
|
-
# A gem with no declared minimum ruby is effectively >= 1.8.7
|
165
|
-
new_constraint = ruby1_8[1]
|
166
|
-
rubocop_ruby_gem_version = ruby1_8[0].segments.join("_")
|
167
|
-
end
|
168
|
-
rescue StandardError => e
|
169
|
-
Kettle::Dev.debug_error(e, __method__)
|
170
|
-
# ignore, use default
|
171
|
-
ensure
|
172
|
-
new_constraint ||= ruby1_8[1]
|
173
|
-
rubocop_ruby_gem_version ||= ruby1_8[0].segments.join("_")
|
174
|
-
end
|
175
|
-
if new_constraint && rubocop_ruby_gem_version
|
176
|
-
token = "{RUBOCOP|LTS|CONSTRAINT}"
|
177
|
-
content.gsub!(token, new_constraint) if content.include?(token)
|
178
|
-
token = "{RUBOCOP|RUBY|GEM}"
|
179
|
-
content.gsub!(token, "rubocop-ruby#{rubocop_ruby_gem_version}") if content.include?(token)
|
180
|
-
end
|
181
|
-
end
|
182
|
-
else
|
183
|
-
helpers.copy_file_with_prompt(src, dest, allow_create: true, allow_replace: true)
|
184
|
-
end
|
185
|
-
|
186
|
-
# 4c) Copy modular directories with nested/versioned files
|
187
|
-
%w[erb mutex_m stringio x_std_libs].each do |dir|
|
188
|
-
src_dir = File.join(gem_checkout_root, MODULAR_GEMFILE_DIR, dir)
|
189
|
-
dest_dir = File.join(project_root, MODULAR_GEMFILE_DIR, dir)
|
190
|
-
helpers.copy_dir_with_prompt(src_dir, dest_dir)
|
191
|
-
end
|
107
|
+
# 4) gemfiles/modular/* and nested directories (delegated for DRYness)
|
108
|
+
Kettle::Dev::ModularGemfiles.sync!(
|
109
|
+
helpers: helpers,
|
110
|
+
project_root: project_root,
|
111
|
+
gem_checkout_root: gem_checkout_root,
|
112
|
+
min_ruby: min_ruby,
|
113
|
+
)
|
192
114
|
|
193
115
|
# 5) spec/spec_helper.rb (no create)
|
194
116
|
dest_spec_helper = File.join(project_root, "spec/spec_helper.rb")
|
data/lib/kettle/dev/version.rb
CHANGED
data/lib/kettle/dev.rb
CHANGED
@@ -24,7 +24,9 @@ module Kettle
|
|
24
24
|
autoload :ReadmeBackers, "kettle/dev/readme_backers"
|
25
25
|
autoload :ReleaseCLI, "kettle/dev/release_cli"
|
26
26
|
autoload :PreReleaseCLI, "kettle/dev/pre_release_cli"
|
27
|
+
autoload :SetupCLI, "kettle/dev/setup_cli"
|
27
28
|
autoload :TemplateHelpers, "kettle/dev/template_helpers"
|
29
|
+
autoload :ModularGemfiles, "kettle/dev/modular_gemfiles"
|
28
30
|
autoload :Version, "kettle/dev/version"
|
29
31
|
autoload :Versioning, "kettle/dev/versioning"
|
30
32
|
|
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.1.
|
4
|
+
version: 1.1.12
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Peter H. Boling
|
@@ -333,6 +333,7 @@ files:
|
|
333
333
|
- lib/kettle/dev/git_adapter.rb
|
334
334
|
- lib/kettle/dev/git_commit_footer.rb
|
335
335
|
- lib/kettle/dev/input_adapter.rb
|
336
|
+
- lib/kettle/dev/modular_gemfiles.rb
|
336
337
|
- lib/kettle/dev/pre_release_cli.rb
|
337
338
|
- lib/kettle/dev/rakelib/appraisal.rake
|
338
339
|
- lib/kettle/dev/rakelib/bench.rake
|
@@ -347,6 +348,7 @@ files:
|
|
347
348
|
- lib/kettle/dev/rakelib/yard.rake
|
348
349
|
- lib/kettle/dev/readme_backers.rb
|
349
350
|
- lib/kettle/dev/release_cli.rb
|
351
|
+
- lib/kettle/dev/setup_cli.rb
|
350
352
|
- lib/kettle/dev/tasks.rb
|
351
353
|
- lib/kettle/dev/tasks/ci_task.rb
|
352
354
|
- lib/kettle/dev/tasks/install_task.rb
|
@@ -366,9 +368,11 @@ files:
|
|
366
368
|
- sig/kettle/dev/git_adapter.rbs
|
367
369
|
- sig/kettle/dev/git_commit_footer.rbs
|
368
370
|
- sig/kettle/dev/input_adapter.rbs
|
371
|
+
- sig/kettle/dev/modular_gemfiles.rbs
|
369
372
|
- sig/kettle/dev/pre_release_cli.rbs
|
370
373
|
- sig/kettle/dev/readme_backers.rbs
|
371
374
|
- sig/kettle/dev/release_cli.rbs
|
375
|
+
- sig/kettle/dev/setup_cli.rbs
|
372
376
|
- sig/kettle/dev/tasks.rbs
|
373
377
|
- sig/kettle/dev/tasks/ci_task.rbs
|
374
378
|
- sig/kettle/dev/tasks/install_task.rbs
|
@@ -381,10 +385,10 @@ licenses:
|
|
381
385
|
- MIT
|
382
386
|
metadata:
|
383
387
|
homepage_uri: https://kettle-dev.galtzo.com/
|
384
|
-
source_code_uri: https://github.com/kettle-rb/kettle-dev/tree/v1.1.
|
385
|
-
changelog_uri: https://github.com/kettle-rb/kettle-dev/blob/v1.1.
|
388
|
+
source_code_uri: https://github.com/kettle-rb/kettle-dev/tree/v1.1.12
|
389
|
+
changelog_uri: https://github.com/kettle-rb/kettle-dev/blob/v1.1.12/CHANGELOG.md
|
386
390
|
bug_tracker_uri: https://github.com/kettle-rb/kettle-dev/issues
|
387
|
-
documentation_uri: https://www.rubydoc.info/gems/kettle-dev/1.1.
|
391
|
+
documentation_uri: https://www.rubydoc.info/gems/kettle-dev/1.1.12
|
388
392
|
funding_uri: https://github.com/sponsors/pboling
|
389
393
|
wiki_uri: https://github.com/kettle-rb/kettle-dev/wiki
|
390
394
|
news_uri: https://www.railsbling.com/tags/kettle-dev
|
metadata.gz.sig
CHANGED
Binary file
|