rails-blocks-cli 0.1.0 → 0.1.1
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
- data/README.md +8 -0
- data/lib/rails_blocks/cli.rb +155 -28
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c4f132c448c8e065b701c21e0e5b777d24fea8ab32c32870f51c84d4a68626cc
|
|
4
|
+
data.tar.gz: 4e703d0337c0a8504773d803afef6b83b6cf6a62ec9dca8a2d048fcd4175bec0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e616247429678b871ce785aa979b2b5922deaaa153b94f0fefeec5261fa8791318a8938ae67c11a602db1a48f884e794334f3df25dac0a82c2be8fa667ab671a
|
|
7
|
+
data.tar.gz: d8271aef17019e1244f64fb098bdfbd35ce975f16f5b989143a5ba2df72f4946c121a8dd4e9da48a267bc45c78cb3999e83411f385b9302ee330775b3051ef77
|
data/README.md
CHANGED
|
@@ -16,6 +16,12 @@ rails-blocks docs accordion
|
|
|
16
16
|
# Install one component as shared ERB partials
|
|
17
17
|
rails-blocks install accordion --as erb_template
|
|
18
18
|
|
|
19
|
+
# Install interactively and let the CLI ask for ERB partials or ViewComponent
|
|
20
|
+
rails-blocks install accordion
|
|
21
|
+
|
|
22
|
+
# Override the component destination
|
|
23
|
+
rails-blocks install accordion --as erb_template --path app/views/components
|
|
24
|
+
|
|
19
25
|
# Preview installing all free components as shared partials
|
|
20
26
|
rails-blocks install --all --free --as partial --dry-run
|
|
21
27
|
|
|
@@ -43,3 +49,5 @@ rails-blocks install --all --pro --as view_component
|
|
|
43
49
|
# Update all Pro-access Stimulus controllers
|
|
44
50
|
rails-blocks update stimulus --all --pro
|
|
45
51
|
```
|
|
52
|
+
|
|
53
|
+
Default install paths are `app/views/shared/<component>/` for ERB partials, `app/components/<component>/` for ViewComponents, and `app/javascript/controllers/` for required Stimulus controllers. Component installs automatically add missing required Stimulus controllers.
|
data/lib/rails_blocks/cli.rb
CHANGED
|
@@ -2,6 +2,7 @@ require "fileutils"
|
|
|
2
2
|
require "json"
|
|
3
3
|
require "net/http"
|
|
4
4
|
require "open3"
|
|
5
|
+
require "pathname"
|
|
5
6
|
require "tempfile"
|
|
6
7
|
require "uri"
|
|
7
8
|
require "zip"
|
|
@@ -44,18 +45,22 @@ module RailsBlocks
|
|
|
44
45
|
attr_reader :argv
|
|
45
46
|
|
|
46
47
|
def list
|
|
47
|
-
components.
|
|
48
|
-
|
|
48
|
+
selected = components.select { |component| include_component?(component) }
|
|
49
|
+
width = selected.map { |component| component_label(component).length }.max || 0
|
|
49
50
|
|
|
50
|
-
|
|
51
|
+
selected.each do |component|
|
|
52
|
+
puts "#{component_label(component).ljust(width)} #{muted('-')} #{component['title']}"
|
|
51
53
|
end
|
|
52
54
|
end
|
|
53
55
|
|
|
54
56
|
def search(query)
|
|
55
57
|
abort "Usage: rails-blocks search QUERY" if query.to_s.strip.empty?
|
|
56
58
|
|
|
57
|
-
components.select { |component| component["slug"].include?(query.tr("-", "_")) || component["title"].downcase.include?(query.downcase) }
|
|
58
|
-
|
|
59
|
+
selected = components.select { |component| component["slug"].include?(query.tr("-", "_")) || component["title"].downcase.include?(query.downcase) }
|
|
60
|
+
width = selected.map { |component| component_label(component).length }.max || 0
|
|
61
|
+
|
|
62
|
+
selected.each do |component|
|
|
63
|
+
puts "#{component_label(component).ljust(width)} #{muted('-')} #{component['title']}"
|
|
59
64
|
end
|
|
60
65
|
end
|
|
61
66
|
|
|
@@ -65,12 +70,22 @@ module RailsBlocks
|
|
|
65
70
|
end
|
|
66
71
|
|
|
67
72
|
def docs(slug)
|
|
73
|
+
return docs_usage if slug.to_s.strip.empty?
|
|
74
|
+
|
|
68
75
|
component = find_component(slug)
|
|
69
76
|
if component["pro"]
|
|
70
77
|
puts api_get("/api/v1/components/#{component['slug']}/docs")["markdown"]
|
|
71
78
|
else
|
|
72
|
-
|
|
73
|
-
|
|
79
|
+
begin
|
|
80
|
+
path = cached_component_file(component["slug"], "#{component['slug'].upcase}.md")
|
|
81
|
+
if File.exist?(path)
|
|
82
|
+
puts File.read(path)
|
|
83
|
+
else
|
|
84
|
+
puts download(component["public_markdown_url"] || markdown_docs_url(component))
|
|
85
|
+
end
|
|
86
|
+
rescue DownloadError
|
|
87
|
+
docs_links(component)
|
|
88
|
+
end
|
|
74
89
|
end
|
|
75
90
|
end
|
|
76
91
|
|
|
@@ -87,12 +102,12 @@ module RailsBlocks
|
|
|
87
102
|
end
|
|
88
103
|
|
|
89
104
|
def install(slug)
|
|
90
|
-
implementation = package_implementation
|
|
105
|
+
implementation = package_implementation(prompt: true)
|
|
91
106
|
dry_run = argv.include?("--dry-run")
|
|
92
107
|
|
|
93
108
|
return install_all(implementation, dry_run: dry_run) if slug == "--all"
|
|
94
109
|
|
|
95
|
-
abort "Usage: rails-blocks install COMPONENT [--as erb_template|view_component|partial] [--dry-run] [--force]" if slug.to_s.empty?
|
|
110
|
+
abort "Usage: rails-blocks install COMPONENT [--as erb_template|view_component|partial] [--path PATH] [--stimulus-path PATH] [--dry-run] [--force]" if slug.to_s.empty?
|
|
96
111
|
|
|
97
112
|
component = find_component(slug)
|
|
98
113
|
install_component(component, implementation, dry_run: dry_run)
|
|
@@ -105,7 +120,7 @@ module RailsBlocks
|
|
|
105
120
|
failures = []
|
|
106
121
|
|
|
107
122
|
selected_components.each do |component|
|
|
108
|
-
|
|
123
|
+
say_heading("Installing #{component['slug']}#{component['pro'] ? ' (pro)' : ''} as #{implementation}")
|
|
109
124
|
install_component(component, implementation, dry_run: dry_run)
|
|
110
125
|
rescue StandardError => error
|
|
111
126
|
failures << [component["slug"], error.message]
|
|
@@ -125,6 +140,8 @@ module RailsBlocks
|
|
|
125
140
|
else
|
|
126
141
|
install_free(component, implementation, dry_run: dry_run)
|
|
127
142
|
end
|
|
143
|
+
|
|
144
|
+
install_component_stimulus(component, dry_run: dry_run)
|
|
128
145
|
end
|
|
129
146
|
|
|
130
147
|
def diff(target)
|
|
@@ -171,7 +188,7 @@ module RailsBlocks
|
|
|
171
188
|
|
|
172
189
|
def change_component_files(component, mode:)
|
|
173
190
|
data = component["pro"] ? pro_package_data(component, package_implementation) : free_package_data(component, package_implementation)
|
|
174
|
-
process_zip(data, mode: mode) { |entry_name|
|
|
191
|
+
process_zip(data, mode: mode) { |entry_name| component_destination(entry_name, package_implementation) }
|
|
175
192
|
end
|
|
176
193
|
|
|
177
194
|
def change_stimulus_files(name, mode:)
|
|
@@ -229,27 +246,32 @@ module RailsBlocks
|
|
|
229
246
|
end
|
|
230
247
|
|
|
231
248
|
def doctor
|
|
232
|
-
|
|
233
|
-
puts "
|
|
234
|
-
puts "
|
|
249
|
+
say_heading("Rails Blocks CLI")
|
|
250
|
+
puts "Registry #{muted(registry_url)}"
|
|
251
|
+
puts "API #{muted(api_url)}"
|
|
252
|
+
puts "Token #{api_token ? success('present') : 'missing'}"
|
|
235
253
|
end
|
|
236
254
|
|
|
237
255
|
def help
|
|
238
256
|
puts "Usage: rails-blocks [list|search|show|docs|examples|install|diff|update|login|logout|whoami|agents|mcp|doctor]"
|
|
239
|
-
puts " rails-blocks install COMPONENT [--as erb_template|view_component|partial] [--dry-run] [--force]"
|
|
240
|
-
puts " rails-blocks install --all [--free|--pro] [--as erb_template|view_component|partial] [--dry-run] [--force]"
|
|
241
|
-
puts " rails-blocks diff COMPONENT|--all [--free|--pro] [--as erb_template|view_component|partial]"
|
|
242
|
-
puts " rails-blocks update COMPONENT|--all [--free|--pro] [--as erb_template|view_component|partial] [--dry-run]"
|
|
257
|
+
puts " rails-blocks install COMPONENT [--as erb_template|view_component|partial] [--path PATH] [--stimulus-path PATH] [--dry-run] [--force]"
|
|
258
|
+
puts " rails-blocks install --all [--free|--pro] [--as erb_template|view_component|partial] [--path PATH] [--stimulus-path PATH] [--dry-run] [--force]"
|
|
259
|
+
puts " rails-blocks diff COMPONENT|--all [--free|--pro] [--as erb_template|view_component|partial] [--path PATH]"
|
|
260
|
+
puts " rails-blocks update COMPONENT|--all [--free|--pro] [--as erb_template|view_component|partial] [--path PATH] [--dry-run]"
|
|
243
261
|
puts " rails-blocks diff stimulus NAME|--all [--free|--pro]"
|
|
244
262
|
puts " rails-blocks update stimulus NAME|--all [--free|--pro] [--dry-run]"
|
|
245
263
|
end
|
|
246
264
|
|
|
247
265
|
def install_free(component, implementation, dry_run:)
|
|
248
|
-
install_zip(free_package_data(component, implementation), dry_run: dry_run)
|
|
266
|
+
install_zip(free_package_data(component, implementation), dry_run: dry_run) do |entry_name|
|
|
267
|
+
component_destination(entry_name, implementation)
|
|
268
|
+
end
|
|
249
269
|
end
|
|
250
270
|
|
|
251
271
|
def install_pro(component, implementation, dry_run:)
|
|
252
|
-
install_zip(pro_package_data(component, implementation), dry_run: dry_run)
|
|
272
|
+
install_zip(pro_package_data(component, implementation), dry_run: dry_run) do |entry_name|
|
|
273
|
+
component_destination(entry_name, implementation)
|
|
274
|
+
end
|
|
253
275
|
end
|
|
254
276
|
|
|
255
277
|
def free_package_data(component, implementation)
|
|
@@ -272,18 +294,104 @@ module RailsBlocks
|
|
|
272
294
|
download(api_url_for("/api/v1/stimulus_controllers?type=#{type}"), auth: type == "pro")
|
|
273
295
|
end
|
|
274
296
|
|
|
297
|
+
def component_stimulus_package_data(component)
|
|
298
|
+
if component["stimulus_package_url"]
|
|
299
|
+
download(component["stimulus_package_url"])
|
|
300
|
+
else
|
|
301
|
+
stimulus_package_data
|
|
302
|
+
end
|
|
303
|
+
end
|
|
304
|
+
|
|
275
305
|
def install_zip(data, dry_run:)
|
|
276
306
|
zip_entries(data).each do |entry|
|
|
277
|
-
destination = Pathname.pwd.join(entry[:name])
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
307
|
+
destination = block_given? ? yield(entry[:name]) : Pathname.pwd.join(entry[:name])
|
|
308
|
+
write_file(destination, entry[:content], dry_run: dry_run)
|
|
309
|
+
end
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
def install_component_stimulus(component, dry_run:)
|
|
313
|
+
controllers = Array(component["required_stimulus_controllers"])
|
|
314
|
+
return if controllers.empty?
|
|
315
|
+
|
|
316
|
+
say_heading("Stimulus controllers")
|
|
317
|
+
zip_entries(component_stimulus_package_data(component)).each do |entry|
|
|
318
|
+
filename = File.basename(entry[:name])
|
|
319
|
+
next unless controllers.include?(filename)
|
|
281
320
|
|
|
282
|
-
|
|
283
|
-
|
|
321
|
+
destination = stimulus_destination(filename)
|
|
322
|
+
if destination.exist? && !argv.include?("--force")
|
|
323
|
+
puts "#{muted('Skip')} #{destination} #{muted('(already exists)')}"
|
|
324
|
+
next
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
write_file(destination, entry[:content], dry_run: dry_run)
|
|
284
328
|
end
|
|
285
329
|
end
|
|
286
330
|
|
|
331
|
+
def write_file(destination, content, dry_run:)
|
|
332
|
+
puts "#{dry_run ? muted('Would write') : success('Writing')} #{destination}"
|
|
333
|
+
return if dry_run
|
|
334
|
+
|
|
335
|
+
raise "#{destination} already exists. Use --force to overwrite it." if destination.exist? && !argv.include?("--force")
|
|
336
|
+
|
|
337
|
+
FileUtils.mkdir_p(destination.dirname)
|
|
338
|
+
File.binwrite(destination, content)
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
def component_destination(entry_name, implementation)
|
|
342
|
+
root = Pathname.pwd.join(option_value("--path") || default_component_path(implementation))
|
|
343
|
+
root.join(entry_name)
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
def stimulus_destination(filename)
|
|
347
|
+
Pathname.pwd.join(option_value("--stimulus-path") || "app/javascript/controllers", filename)
|
|
348
|
+
end
|
|
349
|
+
|
|
350
|
+
def default_component_path(implementation)
|
|
351
|
+
implementation == "view_component" ? "app/components" : "app/views/shared"
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
def docs_usage
|
|
355
|
+
puts "Usage: rails-blocks docs COMPONENT"
|
|
356
|
+
puts "Example: rails-blocks docs accordion"
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
def docs_links(component)
|
|
360
|
+
puts "Docs for #{component['title']}:"
|
|
361
|
+
puts " Web: #{web_docs_url(component)}"
|
|
362
|
+
puts " Markdown: #{markdown_docs_url(component)}"
|
|
363
|
+
end
|
|
364
|
+
|
|
365
|
+
def web_docs_url(component)
|
|
366
|
+
component["docs_url"] || "https://railsblocks.com/docs/#{component['slug'].tr('_', '-')}"
|
|
367
|
+
end
|
|
368
|
+
|
|
369
|
+
def markdown_docs_url(component)
|
|
370
|
+
component["markdown_url"] || "https://railsblocks.com/docs/#{component['slug'].tr('_', '-')}.md"
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
def say_heading(message)
|
|
374
|
+
puts "\n#{bold(message)}"
|
|
375
|
+
end
|
|
376
|
+
|
|
377
|
+
def success(message)
|
|
378
|
+
color(message, 32)
|
|
379
|
+
end
|
|
380
|
+
|
|
381
|
+
def muted(message)
|
|
382
|
+
color(message, 2)
|
|
383
|
+
end
|
|
384
|
+
|
|
385
|
+
def bold(message)
|
|
386
|
+
color(message, 1)
|
|
387
|
+
end
|
|
388
|
+
|
|
389
|
+
def color(message, code)
|
|
390
|
+
return message unless $stdout.tty? && ENV["NO_COLOR"].to_s.empty?
|
|
391
|
+
|
|
392
|
+
"\e[#{code}m#{message}\e[0m"
|
|
393
|
+
end
|
|
394
|
+
|
|
287
395
|
def process_zip(data, mode:)
|
|
288
396
|
counts = { changed: 0, unchanged: 0, skipped: 0 }
|
|
289
397
|
|
|
@@ -386,6 +494,10 @@ module RailsBlocks
|
|
|
386
494
|
end
|
|
387
495
|
end
|
|
388
496
|
|
|
497
|
+
def component_label(component)
|
|
498
|
+
component["pro"] ? "#{component['slug']} (pro)" : component["slug"]
|
|
499
|
+
end
|
|
500
|
+
|
|
389
501
|
def component_filter
|
|
390
502
|
return :pro if argv.include?("--pro")
|
|
391
503
|
return :free if argv.include?("--free")
|
|
@@ -451,14 +563,29 @@ module RailsBlocks
|
|
|
451
563
|
index ? argv[index + 1] : nil
|
|
452
564
|
end
|
|
453
565
|
|
|
454
|
-
def package_implementation
|
|
455
|
-
|
|
566
|
+
def package_implementation(prompt: false)
|
|
567
|
+
explicit = option_value("--as")
|
|
568
|
+
implementation = explicit || (prompt ? prompt_for_implementation : "erb_template")
|
|
456
569
|
return "erb_template" if implementation == "partial"
|
|
457
570
|
return implementation if %w[erb_template view_component].include?(implementation)
|
|
458
571
|
|
|
459
572
|
abort "Unsupported implementation: #{implementation}. Use erb_template, view_component, or partial."
|
|
460
573
|
end
|
|
461
574
|
|
|
575
|
+
def prompt_for_implementation
|
|
576
|
+
return "erb_template" unless $stdin.tty? && $stdout.tty?
|
|
577
|
+
|
|
578
|
+
puts bold("Choose an install format")
|
|
579
|
+
puts " 1. ERB partials #{muted('app/views/shared/<component>/')}"
|
|
580
|
+
puts " 2. ViewComponent #{muted('app/components/<component>/')}"
|
|
581
|
+
print "Select an option [1]: "
|
|
582
|
+
|
|
583
|
+
case $stdin.gets&.strip
|
|
584
|
+
when "2", "view_component" then "view_component"
|
|
585
|
+
else "erb_template"
|
|
586
|
+
end
|
|
587
|
+
end
|
|
588
|
+
|
|
462
589
|
def registry_url
|
|
463
590
|
ENV.fetch("RAILS_BLOCKS_REGISTRY_URL", config.fetch("registry_url", DEFAULT_REGISTRY_URL))
|
|
464
591
|
end
|