ratatui_ruby-devtools 0.1.3 → 0.2.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.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +11 -0
  3. data/LICENSES/BSD-2-Clause.txt +9 -0
  4. data/README.md +1 -1
  5. data/exe/hbs +149 -209
  6. data/exe/scaffold +169 -282
  7. data/lib/ratatui_ruby/devtools/tasks/lint.rake +3 -7
  8. data/lib/ratatui_ruby/devtools/tasks/release/ci_run.rb +44 -0
  9. data/lib/ratatui_ruby/devtools/tasks/release/github_cli.rb +36 -0
  10. data/lib/ratatui_ruby/devtools/tasks/release/native_gem_version.rb +70 -0
  11. data/lib/ratatui_ruby/devtools/tasks/release/platform_gem.rb +63 -0
  12. data/lib/ratatui_ruby/devtools/tasks/release/versioned_binary.rb +36 -0
  13. data/lib/ratatui_ruby/devtools/tasks/release.rake +87 -0
  14. data/lib/ratatui_ruby/devtools/tasks/resources/rubies.yml +1 -1
  15. data/lib/ratatui_ruby/devtools/templates/.github/workflows/build-gems.yml.erb +124 -0
  16. data/lib/ratatui_ruby/devtools/templates/.github/workflows/ci.yml.erb +77 -0
  17. data/lib/ratatui_ruby/devtools/templates/.gitignore.erb +2 -0
  18. data/lib/ratatui_ruby/devtools/templates/.rubocop.yml.erb +6 -4
  19. data/lib/ratatui_ruby/devtools/templates/AGENTS.md.erb +101 -28
  20. data/lib/ratatui_ruby/devtools/templates/CODE_OF_CONDUCT.md.erb +44 -0
  21. data/lib/ratatui_ruby/devtools/templates/CONTRIBUTING.md.erb +80 -0
  22. data/lib/ratatui_ruby/devtools/templates/Gemfile.erb +2 -2
  23. data/lib/ratatui_ruby/devtools/templates/README.md.erb +46 -13
  24. data/lib/ratatui_ruby/devtools/templates/REUSE.toml.erb +2 -2
  25. data/lib/ratatui_ruby/devtools/templates/Rakefile.erb +4 -4
  26. data/lib/ratatui_ruby/devtools/templates/bin/setup.erb +21 -11
  27. data/lib/ratatui_ruby/devtools/templates/bin/setup.ps1.erb +105 -0
  28. data/lib/ratatui_ruby/devtools/templates/doc/custom.css.erb +3 -3
  29. data/lib/ratatui_ruby/devtools/templates/doc/getting_started/quickstart.md.erb +3 -3
  30. data/lib/ratatui_ruby/devtools/templates/doc/index.md.erb +2 -2
  31. data/lib/ratatui_ruby/devtools/templates/ext/.cargo/config.toml.erb +15 -0
  32. data/lib/ratatui_ruby/devtools/templates/ext/.gitignore.erb +6 -0
  33. data/lib/ratatui_ruby/devtools/templates/ext/Cargo.toml.erb +20 -0
  34. data/lib/ratatui_ruby/devtools/templates/ext/clippy.toml.erb +9 -0
  35. data/lib/ratatui_ruby/devtools/templates/ext/extconf.rb.erb +23 -0
  36. data/lib/ratatui_ruby/devtools/templates/ext/src/lib.rs.erb +13 -0
  37. data/lib/ratatui_ruby/devtools/templates/gemspec.erb +21 -14
  38. data/lib/ratatui_ruby/devtools/templates/lib/main.rb.erb +19 -0
  39. data/lib/ratatui_ruby/devtools/templates/lib/test_helper.rb.erb +26 -0
  40. data/lib/ratatui_ruby/devtools/templates/lib/version.rb.erb +10 -0
  41. data/lib/ratatui_ruby/devtools/templates/mise.toml.erb +3 -3
  42. data/lib/ratatui_ruby/devtools/templates/tasks/example_viewer.html.erb +23 -23
  43. data/lib/ratatui_ruby/devtools/templates/tasks/resources/index.html.erb +15 -16
  44. data/lib/ratatui_ruby/devtools/templates/tasks/resources/rubies.yml.erb +5 -6
  45. data/lib/ratatui_ruby/devtools/templates/test/test_helper.rb.erb +15 -0
  46. data/lib/ratatui_ruby/devtools/version.rb +1 -1
  47. data/lib/ratatui_ruby/devtools.rb +2 -2
  48. metadata +24 -5
  49. data/lib/ratatui_ruby/devtools/tasks/resources/build.yml.erb +0 -54
  50. data/lib/ratatui_ruby/devtools/tasks/sourcehut.rake +0 -94
  51. data/lib/ratatui_ruby/devtools/templates/tasks/resources/build.yml.erb +0 -62
  52. /data/lib/ratatui_ruby/devtools/templates/{vendor/goodcop/base.yml → lib/rubocop.yml} +0 -0
data/exe/scaffold CHANGED
@@ -6,15 +6,19 @@
6
6
  # SPDX-License-Identifier: AGPL-3.0-or-later
7
7
  #++
8
8
 
9
- # Scaffolds a new RatatuiRuby ecosystem gem.
9
+ # Scaffolds or updates a RatatuiRuby ecosystem gem.
10
10
  #
11
11
  # Creating a new ecosystem gem requires many files with consistent formatting:
12
- # gemspec, Rakefile, AGENTS.md, CI configs, license headers, and more.
12
+ # gemspec, Rakefile, AGENTS.md, CI workflows, license headers, and more.
13
13
  # Manually copying and editing templates is tedious and error-prone.
14
14
  #
15
- # This script renders all devtools templates into a new gem directory with
16
- # the correct gem name, copyright holder, and license. Run it to bootstrap
17
- # a new ecosystem gem with all quality gates pre-configured.
15
+ # This script renders all devtools templates into a gem directory with the
16
+ # correct gem name, copyright holder, and license. Use it to bootstrap a
17
+ # new gem or to update an existing one when devtools templates change.
18
+ #
19
+ # In update mode (<tt>--update</tt>), new files are written automatically.
20
+ # Existing files that differ from the template show a diff and prompt for
21
+ # overwrite. Unchanged files are skipped silently.
18
22
  #
19
23
  # == Usage
20
24
  #
@@ -26,13 +30,17 @@
26
30
  # --email EMAIL Copyright holder email (default: from git config)
27
31
  # --license ID SPDX license identifier (default: AGPL-3.0-or-later)
28
32
  # --dir PATH Parent directory for new gem (default: current directory)
33
+ # --update Update existing project (diff + prompt for conflicts)
34
+ # --force Remove existing directory if present
35
+ # --rust Include Rust native extension support
29
36
  # --help Show this help
30
37
  #
31
38
  # == Examples
32
39
  #
33
- # scaffold ratatui_ruby-tea
40
+ # scaffold rooibos
34
41
  # scaffold ratatui_ruby-kit --license MIT
35
42
  # scaffold ratatui_ruby-framework --dir ~/Developer
43
+ # scaffold rooibos --update --dir ~/Developer
36
44
 
37
45
  require "erb"
38
46
  require "fileutils"
@@ -94,7 +102,7 @@ class ScaffoldConfig
94
102
 
95
103
  # Ruby versions to test in CI.
96
104
  def ruby_versions
97
- %w[3.2 3.3 3.4 4.0.0]
105
+ %w[3.2 3.3 3.4 4.0.1]
98
106
  end
99
107
  end
100
108
 
@@ -108,10 +116,11 @@ class TemplateRenderer
108
116
  # [config] The ScaffoldConfig with gem parameters.
109
117
  # [templates_dir] Path to the ERB templates.
110
118
  # [output_dir] Path where rendered files are written.
111
- def initialize(config, templates_dir, output_dir)
119
+ def initialize(config, templates_dir, output_dir, update_mode: false)
112
120
  @config = config
113
121
  @templates_dir = templates_dir
114
122
  @output_dir = output_dir
123
+ @update_mode = update_mode
115
124
  end
116
125
 
117
126
  # Renders all templates into the output directory.
@@ -122,7 +131,9 @@ class TemplateRenderer
122
131
  render_template(".pre-commit-config.yaml.erb", ".pre-commit-config.yaml")
123
132
  render_template(".rubocop.yml.erb", ".rubocop.yml")
124
133
  render_template("AGENTS.md.erb", "AGENTS.md")
125
- render_template("CHANGELOG.md.erb", "CHANGELOG.md")
134
+ render_template("CHANGELOG.md.erb", "CHANGELOG.md") unless @update_mode
135
+ render_template("CONTRIBUTING.md.erb", "CONTRIBUTING.md")
136
+ render_template("CODE_OF_CONDUCT.md.erb", "CODE_OF_CONDUCT.md")
126
137
  render_template("Gemfile.erb", "Gemfile")
127
138
  render_template("README.md.erb", "README.md")
128
139
  render_template("REUSE.toml.erb", "REUSE.toml")
@@ -132,13 +143,17 @@ class TemplateRenderer
132
143
 
133
144
  # bin/ scripts
134
145
  render_template("bin/setup.erb", "bin/setup", executable: true)
146
+ render_template("bin/setup.ps1.erb", "bin/setup.ps1")
135
147
  render_template("bin/console.erb", "bin/console", executable: true)
136
148
 
137
- # task resources for CI manifest generation
138
- copy_task_resources
149
+ # GitHub Actions CI workflow
150
+ render_template(".github/workflows/ci.yml.erb", ".github/workflows/ci.yml")
139
151
 
140
- # vendor files (RuboCop base config)
141
- copy_vendor_files
152
+ # GitHub Actions build-gems workflow (Rust only)
153
+ render_template(".github/workflows/build-gems.yml.erb", ".github/workflows/build-gems.yml") if @config.has_rust
154
+
155
+ # task resources
156
+ copy_task_resources
142
157
 
143
158
  # doc files (RDoc styling)
144
159
  copy_doc_files
@@ -158,17 +173,44 @@ class TemplateRenderer
158
173
 
159
174
  FileUtils.mkdir_p(File.dirname(output_path))
160
175
 
161
- if File.exist?(template_path)
162
- if template_name.end_with?(".erb")
163
- content = ERB.new(File.read(template_path), trim_mode: "-").result(binding_for_config)
164
- else
165
- content = File.read(template_path)
166
- end
167
- File.write(output_path, content)
168
- FileUtils.chmod(0o755, output_path) if executable
169
- puts " Created #{output_name}"
170
- else
176
+ unless File.exist?(template_path)
171
177
  warn " WARNING: Template not found: #{template_name}"
178
+ return
179
+ end
180
+
181
+ content = if template_name.end_with?(".erb")
182
+ ERB.new(File.read(template_path), trim_mode: "-").result(binding_for_config)
183
+ else
184
+ File.read(template_path)
185
+ end
186
+
187
+ if @update_mode && File.exist?(output_path)
188
+ existing = File.read(output_path)
189
+ if existing == content
190
+ puts " Unchanged #{output_name}"
191
+ return
192
+ end
193
+ puts " Conflict #{output_name}"
194
+ show_diff(output_path, content)
195
+ print " Overwrite #{output_name}? [y/N] "
196
+ answer = $stdin.gets&.strip
197
+ unless answer&.match?(/^[Yy]/)
198
+ puts " Skipped #{output_name}"
199
+ return
200
+ end
201
+ end
202
+
203
+ File.write(output_path, content)
204
+ FileUtils.chmod(0o755, output_path) if executable
205
+ puts " Created #{output_name}"
206
+ end
207
+
208
+ private def show_diff(existing_path, new_content)
209
+ require "tempfile"
210
+ Tempfile.create(["scaffold", File.extname(existing_path)]) do |tmp|
211
+ tmp.write(new_content)
212
+ tmp.flush
213
+ system("git", "--no-pager", "diff", "--no-index", "--color", existing_path, tmp.path)
172
214
  end
173
215
  end
174
216
 
@@ -177,10 +219,7 @@ class TemplateRenderer
177
219
  tasks_dir = File.join(@output_dir, "tasks")
178
220
  FileUtils.mkdir_p(resources_dir)
179
221
 
180
- # Copy task resource files
181
- template_resources = File.join(@templates_dir, "tasks", "resources")
182
222
  # Render ERB templates to tasks/resources/
183
- render_template("tasks/resources/build.yml.erb", "tasks/resources/build.yml.erb")
184
223
  render_template("tasks/resources/rubies.yml.erb", "tasks/resources/rubies.yml")
185
224
  render_template("tasks/resources/index.html.erb", "tasks/resources/index.html.erb")
186
225
 
@@ -195,20 +234,6 @@ class TemplateRenderer
195
234
  puts " Created exe/.gitkeep"
196
235
  end
197
236
 
198
- private def copy_vendor_files
199
- vendor_dir = File.join(@output_dir, "vendor", "goodcop")
200
- FileUtils.mkdir_p(vendor_dir)
201
-
202
- src = File.join(@templates_dir, "vendor", "goodcop", "base.yml")
203
- dst = File.join(vendor_dir, "base.yml")
204
- if File.exist?(src)
205
- FileUtils.cp(src, dst)
206
- puts " Copied vendor/goodcop/base.yml"
207
- else
208
- warn " WARNING: vendor/goodcop/base.yml not found"
209
- end
210
- end
211
-
212
237
  private def copy_doc_files
213
238
  doc_dir = File.join(@output_dir, "doc")
214
239
  FileUtils.mkdir_p(doc_dir)
@@ -234,193 +259,32 @@ class TemplateRenderer
234
259
  lib_path = File.join(@output_dir, "lib", @config.require_path)
235
260
  FileUtils.mkdir_p(lib_path)
236
261
 
237
- # Split the module name into parts for proper nesting
238
- module_parts = @config.module_name.split("::")
262
+ # RuboCop config shipped inside the gem (resolved via inherit_gem)
263
+ render_template("lib/rubocop.yml", "lib/#{@config.require_path}/rubocop.yml")
239
264
 
240
265
  # Main require file
241
- main_file = File.join(@output_dir, "lib", "#{@config.require_path}.rb")
242
- # REUSE-IgnoreStart
243
- File.write(main_file, <<~RUBY)
244
- # frozen_string_literal: true
245
-
246
- #--
247
- # SPDX-FileCopyrightText: #{@config.year} #{@config.copyright_holder}
248
- # SPDX-License-Identifier: #{@config.license}
249
- #++
250
-
251
- require_relative "#{@config.gem_name.split('-').last}/version"
252
-
253
- # Entry point for the #{@config.gem_name} gem.
254
- #
255
- # Ruby libraries benefit from a clear namespace. Gems need a central module.
256
- #
257
- # This module serves as the namespace root. All classes and utilities live here.
258
- #
259
- # Require this file to load the library.
260
- #{generate_nested_module(module_parts, '# Namespace for library functionality.')}
261
- RUBY
262
- # REUSE-IgnoreEnd
263
- puts " Created lib/#{@config.require_path}.rb"
266
+ render_template("lib/main.rb.erb", "lib/#{@config.require_path}.rb")
264
267
 
265
268
  # Version file
266
- version_file = File.join(lib_path, "version.rb")
267
- # REUSE-IgnoreStart
268
- File.write(version_file, <<~RUBY)
269
- # frozen_string_literal: true
269
+ render_template("lib/version.rb.erb", "lib/#{@config.require_path}/version.rb")
270
270
 
271
- #--
272
- # SPDX-FileCopyrightText: #{@config.year} #{@config.copyright_holder}
273
- # SPDX-License-Identifier: #{@config.license}
274
- #++
275
-
276
- #{generate_nested_module_with_version(module_parts)}
277
- RUBY
278
- # REUSE-IgnoreEnd
279
- puts " Created lib/#{@config.require_path}/version.rb"
271
+ # Test helper shipped inside the gem
272
+ render_template("lib/test_helper.rb.erb", "lib/#{@config.require_path}/test_helper.rb")
280
273
 
281
274
  # Test directory with placeholder
282
- test_path = File.join(@output_dir, "test")
283
- FileUtils.mkdir_p(test_path)
284
- # REUSE-IgnoreStart
285
- File.write(File.join(test_path, "test_helper.rb"), <<~RUBY)
286
- # frozen_string_literal: true
287
-
288
- #--
289
- # SPDX-FileCopyrightText: #{@config.year} #{@config.copyright_holder}
290
- # SPDX-License-Identifier: #{@config.license}
291
- #++
292
-
293
- $LOAD_PATH.unshift File.expand_path("../lib", __dir__)
294
- require "#{@config.require_path}"
295
- require "minitest/autorun"
296
- RUBY
297
- # REUSE-IgnoreEnd
298
- puts " Created test/test_helper.rb"
275
+ FileUtils.mkdir_p(File.join(@output_dir, "test"))
276
+ render_template("test/test_helper.rb.erb", "test/test_helper.rb")
299
277
  end
300
278
 
301
279
  private def create_rust_extension
302
280
  ext_name = @config.gem_name.tr("-", "_")
303
- ext_path = File.join(@output_dir, "ext", ext_name)
304
- cargo_path = File.join(ext_path, ".cargo")
305
- FileUtils.mkdir_p(cargo_path)
306
-
307
- # Cargo.toml
308
- # REUSE-IgnoreStart
309
- File.write(File.join(ext_path, "Cargo.toml"), <<~TOML)
310
- # SPDX-FileCopyrightText: #{@config.year} #{@config.copyright_holder}
311
- # SPDX-License-Identifier: #{@config.license}
312
-
313
- [package]
314
- name = "#{ext_name}"
315
- version = "0.1.0"
316
- edition = "2021"
317
-
318
- [lib]
319
- crate-type = ["cdylib", "staticlib"]
320
-
321
- [dependencies]
322
- magnus = "0.8"
323
- ratatui = { version = "0.30", features = ["widget-calendar", "layout-cache", "unstable-rendered-line-info"] }
324
-
325
- # Optional: Arena allocation to avoid Box::leak for long-lived Ruby objects
326
- # bumpalo = "3.16"
327
- # lazy_static = "1.4"
328
- TOML
329
- # REUSE-IgnoreEnd
330
- puts " Created ext/#{ext_name}/Cargo.toml"
331
-
332
- # extconf.rb
333
- # REUSE-IgnoreStart
334
- File.write(File.join(ext_path, "extconf.rb"), <<~RUBY)
335
- # frozen_string_literal: true
336
-
337
- #--
338
- # SPDX-FileCopyrightText: #{@config.year} #{@config.copyright_holder}
339
- # SPDX-License-Identifier: #{@config.license}
340
- #++
341
-
342
- require "mkmf"
343
- require "rb_sys/mkmf"
344
-
345
- create_rust_makefile("#{ext_name}/#{ext_name}") do |r|
346
- # Optional: Force release profile if needed
347
- # r.profile = ENV.fetch("RB_SYS_CARGO_PROFILE", :release).to_sym
348
-
349
- # Force static linking on musl to avoid "cdylib" issues
350
- if RbConfig::CONFIG["target_os"].include?("linux-musl") || RbConfig::CONFIG["host_os"].include?("linux-musl")
351
- r.extra_rustc_args = ["--crate-type", "staticlib"]
352
- else
353
- r.extra_rustc_args = ["--crate-type", "cdylib"]
354
- end
355
- end
356
- RUBY
357
- # REUSE-IgnoreEnd
358
- puts " Created ext/#{ext_name}/extconf.rb"
359
-
360
- # .cargo/config.toml
361
- # REUSE-IgnoreStart
362
- File.write(File.join(cargo_path, "config.toml"), <<~TOML)
363
- # SPDX-FileCopyrightText: #{@config.year} #{@config.copyright_holder}
364
- # SPDX-License-Identifier: #{@config.license}
365
-
366
- [target.aarch64-apple-darwin]
367
- rustflags = ["-C", "link-arg=-undefined", "-C", "link-arg=dynamic_lookup"]
368
-
369
- [target.x86_64-apple-darwin]
370
- rustflags = ["-C", "link-arg=-undefined", "-C", "link-arg=dynamic_lookup"]
371
-
372
- # Force dynamic linking on Alpine (musl) to fix "Dynamic loading not supported"
373
- # errors when bindgen tries to load libclang.
374
- [target.'cfg(target_env = "musl")']
375
- rustflags = ["-C", "target-feature=-crt-static"]
376
- TOML
377
- # REUSE-IgnoreEnd
378
- puts " Created ext/#{ext_name}/.cargo/config.toml"
379
-
380
- # .gitignore
381
- # REUSE-IgnoreStart
382
- File.write(File.join(ext_path, ".gitignore"), <<~GITIGNORE)
383
- # SPDX-FileCopyrightText: #{@config.year} #{@config.copyright_holder}
384
- # SPDX-License-Identifier: #{@config.license}
385
-
386
- /target/
387
- GITIGNORE
388
- # REUSE-IgnoreEnd
389
- puts " Created ext/#{ext_name}/.gitignore"
390
-
391
- # clippy.toml
392
- # REUSE-IgnoreStart
393
- File.write(File.join(ext_path, "clippy.toml"), <<~TOML)
394
- # SPDX-FileCopyrightText: #{@config.year} #{@config.copyright_holder}
395
- # SPDX-License-Identifier: #{@config.license}
396
-
397
- disallowed-methods = [
398
- { path = "std::boxed::Box::leak", reason = "Use bumpalo arena allocation instead." },
399
- { path = "std::vec::Vec::leak", reason = "Use bumpalo arena allocation instead." },
400
- ]
401
- TOML
402
- # REUSE-IgnoreEnd
403
- puts " Created ext/#{ext_name}/clippy.toml"
404
-
405
- # src/lib.rs stub
406
- src_path = File.join(ext_path, "src")
407
- FileUtils.mkdir_p(src_path)
408
- # REUSE-IgnoreStart
409
- File.write(File.join(src_path, "lib.rs"), <<~RUST)
410
- // SPDX-FileCopyrightText: #{@config.year} #{@config.copyright_holder}
411
- // SPDX-License-Identifier: #{@config.license}
412
-
413
- use magnus::{define_module, prelude::*, Error};
414
-
415
- #[magnus::init]
416
- fn init() -> Result<(), Error> {
417
- let module = define_module("#{@config.module_name.split('::').first}")?;
418
- // Add your native methods here
419
- Ok(())
420
- }
421
- RUST
422
- # REUSE-IgnoreEnd
423
- puts " Created ext/#{ext_name}/src/lib.rs"
281
+
282
+ render_template("ext/Cargo.toml.erb", "ext/#{ext_name}/Cargo.toml")
283
+ render_template("ext/extconf.rb.erb", "ext/#{ext_name}/extconf.rb")
284
+ render_template("ext/.cargo/config.toml.erb", "ext/#{ext_name}/.cargo/config.toml")
285
+ render_template("ext/.gitignore.erb", "ext/#{ext_name}/.gitignore")
286
+ render_template("ext/clippy.toml.erb", "ext/#{ext_name}/clippy.toml")
287
+ render_template("ext/src/lib.rs.erb", "ext/#{ext_name}/src/lib.rs")
424
288
  end
425
289
 
426
290
  private def generate_nested_module(parts, content, indent = 0, is_outermost = true)
@@ -474,7 +338,7 @@ class TemplateRenderer
474
338
  #{prefix}module #{parts.first}
475
339
  #{prefix} # The version of this gem.
476
340
  #{prefix} # See https://semver.org/spec/v2.0.0.html
477
- #{prefix} VERSION = "0.1.0"
341
+ #{prefix} VERSION = "0.0.1.pre.1"
478
342
  #{prefix}end
479
343
  RUBY
480
344
  else
@@ -488,9 +352,13 @@ class TemplateRenderer
488
352
  end
489
353
 
490
354
  private def binding_for_config
355
+ module_parts = @config.module_name.split("::")
356
+ ext_name = @config.gem_name.tr("-", "_")
357
+
491
358
  # Create a binding with all config values available to templates
492
359
  b = binding
493
360
  b.local_variable_set(:gem_name, @config.gem_name)
361
+ b.local_variable_set(:gem_display_name, @config.module_name)
494
362
  b.local_variable_set(:module_name, @config.module_name)
495
363
  b.local_variable_set(:require_path, @config.require_path)
496
364
  b.local_variable_set(:copyright_holder, @config.copyright_holder)
@@ -500,9 +368,16 @@ class TemplateRenderer
500
368
  b.local_variable_set(:year, @config.year)
501
369
  b.local_variable_set(:has_rdoc, true)
502
370
  b.local_variable_set(:has_rust, @config.has_rust)
371
+ b.local_variable_set(:rubies, @config.ruby_versions)
372
+ b.local_variable_set(:ext_name, ext_name)
373
+ b.local_variable_set(:nested_module, generate_nested_module(module_parts, "# Namespace for library functionality."))
374
+ b.local_variable_set(:nested_module_with_version, generate_nested_module_with_version(module_parts))
375
+ b.local_variable_set(:update_mode, @update_mode)
503
376
  b.local_variable_set(:dependencies, [])
504
377
  b.local_variable_set(:summary, "Part of the RatatuiRuby ecosystem")
505
378
  b.local_variable_set(:description, "#{@config.module_name} - part of the RatatuiRuby TUI framework ecosystem")
379
+ b.local_variable_set(:github_repo, "setdef/#{@config.module_name}")
380
+ b.local_variable_set(:project_name, @config.module_name)
506
381
  b.local_variable_set(:version_file_require, "lib/#{@config.require_path}/version")
507
382
  b.local_variable_set(:version_file, "lib/#{@config.require_path}/version.rb")
508
383
  b
@@ -513,6 +388,7 @@ end
513
388
  config = ScaffoldConfig.new
514
389
  parent_dir = Dir.pwd
515
390
  force = false
391
+ update_mode = false
516
392
 
517
393
  OptionParser.new do |opts|
518
394
  opts.banner = "Usage: scaffold GEM_NAME [OPTIONS]"
@@ -537,6 +413,10 @@ OptionParser.new do |opts|
537
413
  force = true
538
414
  end
539
415
 
416
+ opts.on("-u", "--update", "Update existing project (diff + prompt for conflicts)") do
417
+ update_mode = true
418
+ end
419
+
540
420
  opts.on("--rust", "Include Rust native extension support") do
541
421
  config.has_rust = true
542
422
  end
@@ -557,12 +437,14 @@ config.gem_name = ARGV[0]
557
437
  config.output_dir = File.join(parent_dir, config.gem_name)
558
438
 
559
439
  if File.exist?(config.output_dir)
560
- if force
440
+ if update_mode
441
+ puts "Updating existing project..."
442
+ elsif force
561
443
  FileUtils.rm_rf(config.output_dir)
562
444
  puts "Removed existing directory: #{config.output_dir}"
563
445
  else
564
446
  warn "Error: Directory already exists: #{config.output_dir}"
565
- warn "Use --force to remove it"
447
+ warn "Use --force to remove it, or --update to update in-place"
566
448
  exit 1
567
449
  end
568
450
  end
@@ -587,76 +469,81 @@ puts " Author: #{config.copyright_holder}"
587
469
  puts " License: #{config.license}"
588
470
  puts ""
589
471
 
590
- renderer = TemplateRenderer.new(config, templates_dir, config.output_dir)
472
+ renderer = TemplateRenderer.new(config, templates_dir, config.output_dir, update_mode:)
591
473
  renderer.render_all
592
474
 
593
- # Initialize git repo with proper branch structure
594
- puts ""
595
- puts "Initializing git repository..."
596
- Dir.chdir(config.output_dir) do
597
- # Initialize git with trunk as the default branch (not main/master)
598
- system("git init --quiet --initial-branch=trunk")
599
-
600
- # Add remote origin for SourceHut
601
- remote_url = "git@git.sr.ht:~kerrick/#{config.gem_name}"
602
- system("git remote add origin #{remote_url}")
603
- puts " Set remote origin: #{remote_url}"
604
-
605
- # Initial add (before setup runs)
606
- system("git add -A")
607
- end
608
- puts " Initialized git with trunk branch"
609
-
610
- # Run setup (installs gems, pre-commit, downloads licenses, etc.)
611
- puts ""
612
- puts "Running bin/setup..."
613
- Dir.chdir(config.output_dir) do
614
- system("mise trust --yes --raw")
615
- system("bin/setup")
616
- end
475
+ if update_mode
476
+ puts ""
477
+ puts "Done! Updated templates at: #{config.output_dir}"
478
+ else
479
+ # Initialize git repo with proper branch structure
480
+ puts ""
481
+ puts "Initializing git repository..."
482
+ Dir.chdir(config.output_dir) do
483
+ # Initialize git with trunk as the default branch (not main/master)
484
+ system("git init --quiet --initial-branch=trunk")
485
+
486
+ # Add remote origin for GitHub
487
+ remote_url = "git@github.com:setdef/#{config.module_name}.git"
488
+ system("git remote add origin #{remote_url}")
489
+ puts " Set remote origin: #{remote_url}"
490
+
491
+ # Initial add (before setup runs)
492
+ system("git add -A")
493
+ end
494
+ puts " Initialized git with trunk branch"
495
+
496
+ # Run setup (installs gems, pre-commit, downloads licenses, etc.)
497
+ puts ""
498
+ puts "Running bin/setup..."
499
+ Dir.chdir(config.output_dir) do
500
+ system("mise trust --yes --raw")
501
+ system("bin/setup")
502
+ end
617
503
 
618
- # Copy LICENSE from primary license for gem distribution
619
- puts ""
620
- puts "Setting up LICENSE..."
621
- Dir.chdir(config.output_dir) do
622
- license_file = "LICENSES/#{config.license}.txt"
623
- if File.exist?(license_file)
624
- FileUtils.cp(license_file, "LICENSE")
625
- puts " Copied #{license_file} to LICENSE"
626
- else
627
- system("mise x -- reuse download #{config.license}")
504
+ # Copy LICENSE from primary license for gem distribution
505
+ puts ""
506
+ puts "Setting up LICENSE..."
507
+ Dir.chdir(config.output_dir) do
508
+ license_file = "LICENSES/#{config.license}.txt"
628
509
  if File.exist?(license_file)
629
510
  FileUtils.cp(license_file, "LICENSE")
630
- puts " Downloaded and copied #{config.license} to LICENSE"
511
+ puts " Copied #{license_file} to LICENSE"
631
512
  else
632
- warn " WARNING: Could not find or download license: #{config.license}"
513
+ system("mise x -- reuse download #{config.license}")
514
+ if File.exist?(license_file)
515
+ FileUtils.cp(license_file, "LICENSE")
516
+ puts " Downloaded and copied #{config.license} to LICENSE"
517
+ else
518
+ warn " WARNING: Could not find or download license: #{config.license}"
519
+ end
633
520
  end
634
521
  end
635
- end
636
522
 
637
- # Create initial commit and tags
638
- puts ""
639
- puts "Creating initial commit and tags..."
640
- Dir.chdir(config.output_dir) do
641
- # Stage ALL files including those created by bin/setup
642
- system("git add -A")
643
-
644
- # Commit (don't use --quiet so we see any errors)
645
- unless system("git commit -m 'chore: scaffold #{config.gem_name} with ratatui_ruby-devtools'")
646
- warn " WARNING: Initial commit failed, retrying with --allow-empty"
647
- system("git commit --allow-empty -m 'chore: scaffold #{config.gem_name} with ratatui_ruby-devtools'")
648
- end
649
-
650
- # Create tag and stable branch
651
- system("git tag v0.1.0 -m 'Initial scaffold release'")
652
- system("git branch stable")
653
- puts " Created branches: trunk (default), stable"
654
- puts " Created tag: v0.1.0"
655
- end
523
+ # Create initial commit and tags
524
+ puts ""
525
+ puts "Creating initial commit and tags..."
526
+ Dir.chdir(config.output_dir) do
527
+ # Stage ALL files including those created by bin/setup
528
+ system("git add -A")
529
+
530
+ # Commit (don't use --quiet so we see any errors)
531
+ unless system("git commit -m 'chore: scaffold #{config.gem_name} with ratatui_ruby-devtools'")
532
+ warn " WARNING: Initial commit failed, retrying with --allow-empty"
533
+ system("git commit --allow-empty -m 'chore: scaffold #{config.gem_name} with ratatui_ruby-devtools'")
534
+ end
656
535
 
657
- puts ""
658
- puts "Done! Your gem is ready at: #{config.output_dir}"
659
- puts ""
660
- puts "Next steps:"
661
- puts " cd #{config.gem_name}"
662
- puts " bundle exec rake"
536
+ # Create tag and stable branch
537
+ system("git tag v0.0.1.pre.1 -m 'Initial scaffold release'")
538
+ system("git branch stable")
539
+ puts " Created branches: trunk (default), stable"
540
+ puts " Created tag: v0.0.1.pre.1"
541
+ end
542
+
543
+ puts ""
544
+ puts "Done! Your gem is ready at: #{config.output_dir}"
545
+ puts ""
546
+ puts "Next steps:"
547
+ puts " cd #{config.gem_name}"
548
+ puts " bundle exec rake"
549
+ end
@@ -34,7 +34,7 @@ namespace :lint do
34
34
  end
35
35
 
36
36
  # Build dynamic task lists based on what's available
37
- docs_tasks = %w[rubycritic reuse:lint]
37
+ docs_tasks = %w[rubycritic]
38
38
  docs_tasks.unshift("autodoc") if Rake::Task.task_defined?("autodoc")
39
39
  docs_tasks << "safe_rdoc_coverage" if Rake::Task.task_defined?("rdoc:coverage")
40
40
 
@@ -43,8 +43,7 @@ namespace :lint do
43
43
 
44
44
  task docs: docs_tasks
45
45
  task code: code_tasks
46
- task licenses: %w[reuse:lint]
47
- task all: %w[docs code licenses]
46
+ task all: %w[docs code]
48
47
 
49
48
  namespace :fix do
50
49
  desc "Auto-fix RuboCop offenses (most aggressive)"
@@ -62,11 +61,8 @@ namespace :lint do
62
61
  end
63
62
  end
64
63
 
65
- desc "Add SPDX headers and normalize Ruby file structure"
66
- task reuse: %w[reuse:fix reuse:normalize_ruby]
67
-
68
64
  # Build dynamic fix:all task
69
- fix_all_tasks = %w[lint:fix:rubocop lint:fix:reuse]
65
+ fix_all_tasks = %w[lint:fix:rubocop]
70
66
  fix_all_tasks.insert(1, "lint:fix:clippy") if Dir.exist?("ext")
71
67
 
72
68
  desc "Run all auto-fix tasks"