railsui 3.2.7 → 3.3.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
- data/Gemfile.lock +8 -1
- data/README.md +196 -42
- data/app/assets/javascripts/railsui-controllers.js +12 -0
- data/app/controllers/railsui/configurations_controller.rb +11 -2
- data/app/helpers/railsui/application_helper.rb +12 -0
- data/app/javascript/controllers/index.js +3 -31
- data/app/javascript/controllers/railsui_anchor_controller.js +4 -3
- data/app/javascript/controllers/railsui_auto_expand_text_area_controller.js +1 -1
- data/app/javascript/controllers/railsui_canvas_controller.js +1 -1
- data/app/javascript/controllers/railsui_code_controller.js +3 -28
- data/app/javascript/controllers/railsui_color_controller.js +1 -1
- data/app/javascript/controllers/railsui_configuration_controller.js +1 -1
- data/app/javascript/controllers/railsui_dialog_controller.js +1 -1
- data/app/javascript/controllers/railsui_flash_controller.js +1 -1
- data/app/javascript/controllers/railsui_helper_controller.js +1 -1
- data/app/javascript/controllers/railsui_loading_controller.js +1 -1
- data/app/javascript/controllers/railsui_modal_controller.js +4 -3
- data/app/javascript/controllers/railsui_nav_controller.js +4 -3
- data/app/javascript/controllers/railsui_pages_controller.js +1 -1
- data/app/javascript/controllers/railsui_prevent_controller.js +1 -1
- data/app/javascript/controllers/railsui_scroll_controller.js +1 -1
- data/app/javascript/controllers/railsui_scroll_spy_controller.js +1 -1
- data/app/javascript/controllers/railsui_search_controller.js +1 -1
- data/app/javascript/controllers/railsui_smooth_controller.js +1 -1
- data/app/javascript/controllers/railsui_snippet_controller.js +1 -1
- data/app/views/layouts/railsui/application.html.erb +7 -5
- data/app/views/layouts/railsui/fullwidth.html.erb +4 -4
- data/app/views/layouts/railsui/landing.html.erb +3 -4
- data/app/views/layouts/railsui/routes.html.erb +4 -3
- data/app/views/railsui/admin/_form.html.erb +18 -1
- data/app/views/railsui/admin/fields/_theme.html.erb +0 -1
- data/app/views/railsui/shared/_cdn_dependencies.html.erb +121 -0
- data/app/views/railsui/shared/_inline_controllers.html.erb +498 -0
- data/app/views/railsui/shared/_snippet.html.erb +23 -1
- data/app/views/railsui/themes/hound/forms/_input_group.html.erb +3 -1
- data/app/views/railsui/themes/shepherd/authentication/devise/_overview.html.erb +30 -28
- data/app/views/railsui/themes/shepherd/authentication/static/_overview.html.erb +8 -8
- data/app/views/railsui/themes/shepherd/content/typography/_headings.html.erb +23 -21
- data/app/views/railsui/themes/shepherd/forms/_input.html.erb +1 -1
- data/guides/CONFIGURATION.md +199 -0
- data/guides/MIGRATION_GUIDE.md +220 -0
- data/lib/generators/railsui/install/install_generator.rb +124 -38
- data/lib/generators/railsui/install/templates/Procfile.dev.build +1 -0
- data/lib/generators/railsui/install/templates/Procfile.dev.nobuild +2 -0
- data/lib/generators/railsui/install/templates/bin/dev +21 -0
- data/lib/generators/railsui/install/templates/themes/corgie/stylesheets/railsui/actiontext.css +0 -1
- data/lib/generators/railsui/install/templates/themes/corgie/views/layouts/rui/railsui.html.erb +7 -2
- data/lib/generators/railsui/install/templates/themes/corgie/views/layouts/rui/railsui_admin.html.erb +7 -2
- data/lib/generators/railsui/install/templates/themes/corgie/views/layouts/rui/railsui_auth.html.erb +6 -2
- data/lib/generators/railsui/install/templates/themes/corgie/views/rui/pages/{privacy.html.erb → privacy_policy.html.erb} +1 -1
- data/lib/generators/railsui/install/templates/themes/corgie/views/rui/pages/terms.html.erb +2 -2
- data/lib/generators/railsui/install/templates/themes/corgie/views/rui/shared/sidebar/_link.html.erb +4 -4
- data/lib/generators/railsui/install/templates/themes/hound/stylesheets/railsui/actiontext.css +0 -1
- data/lib/generators/railsui/install/templates/themes/hound/views/layouts/rui/railsui.html.erb +6 -2
- data/lib/generators/railsui/install/templates/themes/hound/views/layouts/rui/railsui_admin.html.erb +6 -2
- data/lib/generators/railsui/install/templates/themes/shepherd/stylesheets/railsui/actiontext.css +0 -1
- data/lib/generators/railsui/install/templates/themes/shepherd/views/layouts/rui/railsui.html.erb +6 -2
- data/lib/generators/railsui/install/templates/themes/shepherd/views/layouts/rui/railsui_admin.html.erb +6 -2
- data/lib/generators/railsui/update/update_generator.rb +40 -4
- data/lib/railsui/configuration.rb +116 -15
- data/lib/railsui/engine.rb +15 -0
- data/lib/railsui/theme_setup.rb +598 -38
- data/lib/railsui/version.rb +1 -1
- data/lib/railsui.rb +10 -7
- data/lib/tasks/install.rake +9 -3
- data/lib/tasks/migrate.rake +219 -0
- metadata +26 -4
- data/.claude/settings.local.json +0 -10
data/lib/railsui/theme_setup.rb
CHANGED
|
@@ -7,12 +7,39 @@ module Railsui
|
|
|
7
7
|
# gems
|
|
8
8
|
def install_gems
|
|
9
9
|
rails_command "generate railsui_icon:install"
|
|
10
|
-
|
|
10
|
+
|
|
11
|
+
# Only install Action Text if not already installed
|
|
12
|
+
unless action_text_installed?
|
|
13
|
+
rails_command "action_text:install"
|
|
14
|
+
else
|
|
15
|
+
say "✓ Action Text already installed", :green
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def action_text_installed?
|
|
20
|
+
# Check if Action Text migration exists
|
|
21
|
+
migration_files = Dir.glob(Rails.root.join("db/migrate/*_create_action_text_tables*.rb"))
|
|
22
|
+
return true if migration_files.any?
|
|
23
|
+
|
|
24
|
+
# Check if table exists in database
|
|
25
|
+
ActiveRecord::Base.connection.table_exists?('action_text_rich_texts')
|
|
26
|
+
rescue
|
|
27
|
+
false
|
|
11
28
|
end
|
|
12
29
|
|
|
13
30
|
# Assets
|
|
14
31
|
def copy_theme_javascript(theme)
|
|
15
|
-
|
|
32
|
+
config = Railsui::Configuration.load!
|
|
33
|
+
|
|
34
|
+
if config.nobuild?
|
|
35
|
+
copy_theme_javascript_nobuild(theme)
|
|
36
|
+
else
|
|
37
|
+
copy_theme_javascript_build(theme)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def copy_theme_javascript_build(theme)
|
|
42
|
+
say("Adding theme-specific stimulus.js controllers (build mode)", :yellow)
|
|
16
43
|
|
|
17
44
|
# Define paths
|
|
18
45
|
source_path = "themes/#{theme}/javascript/controllers/railsui"
|
|
@@ -36,8 +63,7 @@ module Railsui
|
|
|
36
63
|
controller_name = File.basename(file, ".js").sub("_controller", "")
|
|
37
64
|
import_name = controller_name.camelize
|
|
38
65
|
registration_name = controller_name.dasherize
|
|
39
|
-
"import #{import_name}Controller from \"./#{File.basename(file,
|
|
40
|
-
".js")}\";\napplication.register(\"#{registration_name}\", #{import_name}Controller);"
|
|
66
|
+
"import #{import_name}Controller from \"./#{File.basename(file, ".js")}\";\napplication.register(\"#{registration_name}\", #{import_name}Controller);"
|
|
41
67
|
end.join("\n")
|
|
42
68
|
|
|
43
69
|
js_content = <<-JAVASCRIPT.strip_heredoc
|
|
@@ -76,9 +102,92 @@ module Railsui
|
|
|
76
102
|
|
|
77
103
|
# Write the updated content back to main index.js
|
|
78
104
|
create_file index_js_path, new_index_js_content, force: true
|
|
79
|
-
say(
|
|
80
|
-
|
|
81
|
-
|
|
105
|
+
say("Updated app/javascript/controllers/index.js and created app/javascript/controllers/railsui/index.js successfully.", :green)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def copy_theme_javascript_nobuild(theme)
|
|
109
|
+
say("Adding theme-specific stimulus.js controllers (importmap mode)", :yellow)
|
|
110
|
+
|
|
111
|
+
# Define paths
|
|
112
|
+
source_path = "themes/#{theme}/javascript/controllers/railsui"
|
|
113
|
+
destination_path = "app/javascript/controllers/railsui"
|
|
114
|
+
railsui_index_js_path = Rails.root.join("app/javascript/controllers/railsui/index.js")
|
|
115
|
+
|
|
116
|
+
# Empty the railsui folder
|
|
117
|
+
remove_dir destination_path
|
|
118
|
+
empty_directory destination_path
|
|
119
|
+
|
|
120
|
+
# Copy the directory
|
|
121
|
+
directory source_path, destination_path, force: true
|
|
122
|
+
|
|
123
|
+
# Get the list of controller files
|
|
124
|
+
controller_files = Dir.children(Rails.root.join(destination_path)).select { |f| f.end_with?("_controller.js") }
|
|
125
|
+
say("Controller files: 🗄️ #{controller_files}", :cyan)
|
|
126
|
+
|
|
127
|
+
# Generate import and register statements for theme-specific controllers
|
|
128
|
+
theme_controllers = controller_files.map do |file|
|
|
129
|
+
controller_name = File.basename(file, ".js").sub("_controller", "")
|
|
130
|
+
import_name = controller_name.camelize
|
|
131
|
+
registration_name = controller_name.dasherize
|
|
132
|
+
"import #{import_name}Controller from \"controllers/railsui/#{File.basename(file, ".js")}\"\napplication.register(\"#{registration_name}\", #{import_name}Controller)"
|
|
133
|
+
end.join("\n")
|
|
134
|
+
|
|
135
|
+
# Import railsui-stimulus components and register them
|
|
136
|
+
js_content = <<-JAVASCRIPT.strip_heredoc
|
|
137
|
+
import { application } from "controllers/application"
|
|
138
|
+
import {
|
|
139
|
+
RailsuiClipboard,
|
|
140
|
+
RailsuiCountUp,
|
|
141
|
+
RailsuiCombobox,
|
|
142
|
+
RailsuiDateRangePicker,
|
|
143
|
+
RailsuiDropdown,
|
|
144
|
+
RailsuiModal,
|
|
145
|
+
RailsuiRange,
|
|
146
|
+
RailsuiReadMore,
|
|
147
|
+
RailsuiSelectAll,
|
|
148
|
+
RailsuiTabs,
|
|
149
|
+
RailsuiToast,
|
|
150
|
+
RailsuiToggle,
|
|
151
|
+
RailsuiTooltip
|
|
152
|
+
} from "railsui-stimulus"
|
|
153
|
+
|
|
154
|
+
// Register railsui-stimulus components
|
|
155
|
+
application.register("railsui-clipboard", RailsuiClipboard)
|
|
156
|
+
application.register("railsui-count-up", RailsuiCountUp)
|
|
157
|
+
application.register("railsui-combobox", RailsuiCombobox)
|
|
158
|
+
application.register("railsui-date-range-picker", RailsuiDateRangePicker)
|
|
159
|
+
application.register("railsui-dropdown", RailsuiDropdown)
|
|
160
|
+
application.register("railsui-modal", RailsuiModal)
|
|
161
|
+
application.register("railsui-range", RailsuiRange)
|
|
162
|
+
application.register("railsui-read-more", RailsuiReadMore)
|
|
163
|
+
application.register("railsui-select-all", RailsuiSelectAll)
|
|
164
|
+
application.register("railsui-tabs", RailsuiTabs)
|
|
165
|
+
application.register("railsui-toast", RailsuiToast)
|
|
166
|
+
application.register("railsui-toggle", RailsuiToggle)
|
|
167
|
+
application.register("railsui-tooltip", RailsuiTooltip)
|
|
168
|
+
|
|
169
|
+
// Register theme-specific controllers
|
|
170
|
+
#{theme_controllers}
|
|
171
|
+
JAVASCRIPT
|
|
172
|
+
|
|
173
|
+
# Write the railsui/index.js file
|
|
174
|
+
create_file railsui_index_js_path, js_content, force: true
|
|
175
|
+
|
|
176
|
+
# Update the main index.js to import railsui controllers
|
|
177
|
+
index_js_path = Rails.root.join("app/javascript/controllers/index.js")
|
|
178
|
+
if File.exist?(index_js_path)
|
|
179
|
+
index_js_content = File.read(index_js_path)
|
|
180
|
+
# Use absolute importmap path (importmap maps index.js to controllers/railsui without /index)
|
|
181
|
+
unless index_js_content.include?('import "controllers/railsui"')
|
|
182
|
+
# Remove old relative import if it exists
|
|
183
|
+
index_js_content.gsub!(/import\s+["']\.\/railsui["']\s*\n?/, '')
|
|
184
|
+
File.write(index_js_path, index_js_content)
|
|
185
|
+
File.open(index_js_path, 'a') { |f| f.write("\nimport \"controllers/railsui\"\n") }
|
|
186
|
+
say("✓ Added railsui import to controllers/index.js", :green)
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
say("✅ Controllers configured for importmap mode", :green)
|
|
82
191
|
end
|
|
83
192
|
|
|
84
193
|
def copy_theme_stylesheets(theme)
|
|
@@ -87,7 +196,18 @@ module Railsui
|
|
|
87
196
|
# Define paths
|
|
88
197
|
source_path = "themes/#{theme}/stylesheets/railsui"
|
|
89
198
|
destination_path = "app/assets/stylesheets/railsui"
|
|
90
|
-
|
|
199
|
+
|
|
200
|
+
# Use tailwindcss-rails v4 default path
|
|
201
|
+
application_css_path = Rails.root.join("app/assets/tailwind/application.css")
|
|
202
|
+
|
|
203
|
+
# Ensure the directory exists
|
|
204
|
+
FileUtils.mkdir_p(application_css_path.dirname)
|
|
205
|
+
|
|
206
|
+
# Create the file if it doesn't exist (in case tailwindcss:install failed)
|
|
207
|
+
unless File.exist?(application_css_path)
|
|
208
|
+
File.write(application_css_path, '@import "tailwindcss";')
|
|
209
|
+
say "✓ Created #{application_css_path.relative_path_from(Rails.root)}", :green
|
|
210
|
+
end
|
|
91
211
|
|
|
92
212
|
# Empty the destination directory before copying
|
|
93
213
|
FileUtils.rm_rf(Dir.glob("#{destination_path}/*"))
|
|
@@ -100,80 +220,306 @@ module Railsui
|
|
|
100
220
|
puts "Stylesheet files: 🗄️ #{stylesheet_files}"
|
|
101
221
|
|
|
102
222
|
# Generate import statements for stylesheets
|
|
223
|
+
# Path is relative from app/assets/tailwind/ to app/assets/stylesheets/railsui/
|
|
103
224
|
import_statements = stylesheet_files.map do |file|
|
|
104
|
-
"@import \"
|
|
225
|
+
"@import \"../stylesheets/railsui/#{File.basename(file, ".css")}\";"
|
|
105
226
|
end.join("\n")
|
|
106
227
|
|
|
107
228
|
# Read the existing application.tailwind.css content
|
|
108
229
|
application_css_content = File.exist?(application_css_path) ? File.read(application_css_path) : ""
|
|
109
230
|
|
|
110
|
-
# Remove old
|
|
231
|
+
# Remove old import statements for tailwindcss and railsui stylesheets
|
|
232
|
+
# BUT preserve actiontext import (core Rails ActionText CSS)
|
|
111
233
|
cleaned_css_content = application_css_content
|
|
112
234
|
cleaned_css_content = cleaned_css_content.gsub(/@import "tailwindcss";\n*/, "")
|
|
113
235
|
cleaned_css_content = cleaned_css_content.gsub(%r{@import "\.\./stylesheets/railsui/.*";\n*}, "")
|
|
114
236
|
cleaned_css_content = cleaned_css_content.gsub(%r{@import "railsui/.*";\n*}, "")
|
|
115
237
|
cleaned_css_content = cleaned_css_content.gsub(%r{@import "\./railsui/.*";\n*}, "")
|
|
116
238
|
|
|
239
|
+
# Extract and preserve actiontext import if it exists
|
|
240
|
+
actiontext_import_match = cleaned_css_content.match(%r{@import ['"]\.\./stylesheets/actiontext['"];?\n*})
|
|
241
|
+
cleaned_css_content = cleaned_css_content.gsub(%r{@import ['"]\.\./stylesheets/actiontext['"];?\n*}, "") if actiontext_import_match
|
|
242
|
+
|
|
243
|
+
# Check if actiontext.css exists (from action_text:install)
|
|
244
|
+
actiontext_css_path = Rails.root.join("app/assets/stylesheets/actiontext.css")
|
|
245
|
+
actiontext_import = File.exist?(actiontext_css_path) ? '@import "../stylesheets/actiontext";' : nil
|
|
246
|
+
|
|
117
247
|
# Add the new import statements in the correct order
|
|
118
248
|
new_application_css_content = [
|
|
119
249
|
'@import "tailwindcss";',
|
|
250
|
+
actiontext_import,
|
|
120
251
|
cleaned_css_content.strip, # Preserving existing content
|
|
121
252
|
import_statements
|
|
122
|
-
].join("\n")
|
|
253
|
+
].compact.join("\n")
|
|
123
254
|
|
|
124
255
|
# Write the updated content back to application.tailwind.css
|
|
125
256
|
File.write(application_css_path, new_application_css_content)
|
|
126
|
-
say("Updated
|
|
257
|
+
say("Updated #{application_css_path.relative_path_from(Rails.root)} successfully.", :green)
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
# CSS Dependencies (unified for both build and nobuild modes)
|
|
261
|
+
def install_css_dependencies
|
|
262
|
+
say("Installing CSS dependencies (tailwindcss-rails)", :yellow)
|
|
263
|
+
|
|
264
|
+
unless gem_installed?('tailwindcss-rails')
|
|
265
|
+
say "tailwindcss-rails not found. Installing...", :green
|
|
266
|
+
begin
|
|
267
|
+
run "bundle add tailwindcss-rails"
|
|
268
|
+
rescue => e
|
|
269
|
+
say "❌ Failed to install tailwindcss-rails gem", :red
|
|
270
|
+
say "Error: #{e.message}", :red
|
|
271
|
+
say "Please install manually: bundle add tailwindcss-rails", :yellow
|
|
272
|
+
raise
|
|
273
|
+
end
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
# Create builds directory for tailwindcss-rails
|
|
277
|
+
unless File.exist?(Rails.root.join("app/assets/builds/.keep"))
|
|
278
|
+
say "Setting up Tailwind CSS...", :yellow
|
|
279
|
+
FileUtils.mkdir_p(Rails.root.join("app/assets/builds"))
|
|
280
|
+
FileUtils.touch(Rails.root.join("app/assets/builds/.keep"))
|
|
281
|
+
say "✓ Tailwind CSS configured", :green
|
|
282
|
+
end
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
# JS Dependencies for build mode (existing behavior)
|
|
286
|
+
def install_js_dependencies_build(theme)
|
|
287
|
+
say("Installing JS dependencies via package manager", :yellow)
|
|
288
|
+
|
|
289
|
+
# Fix application.js imports for bundler (convert importmap style to bundler style)
|
|
290
|
+
fix_application_js_for_bundler
|
|
291
|
+
|
|
292
|
+
# Install theme-specific packages
|
|
293
|
+
add_yarn_packages(js_theme_dependencies(theme))
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
def fix_application_js_for_bundler
|
|
297
|
+
app_js = Rails.root.join("app/javascript/application.js")
|
|
298
|
+
return unless File.exist?(app_js)
|
|
299
|
+
|
|
300
|
+
content = File.read(app_js)
|
|
301
|
+
original = content.dup
|
|
302
|
+
|
|
303
|
+
# Fix relative imports
|
|
304
|
+
content.gsub!('import "controllers"', 'import "./controllers"')
|
|
305
|
+
|
|
306
|
+
File.write(app_js, content) if content != original
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
# JS Dependencies for nobuild mode (importmap)
|
|
310
|
+
def install_js_dependencies_nobuild(theme)
|
|
311
|
+
say("Installing JS dependencies via importmap", :yellow)
|
|
312
|
+
|
|
313
|
+
# Check if importmap-rails gem is installed
|
|
314
|
+
unless gem_installed?('importmap-rails')
|
|
315
|
+
say "📦 importmap-rails not found. Installing...", :green
|
|
316
|
+
begin
|
|
317
|
+
run "bundle add importmap-rails"
|
|
318
|
+
rescue => e
|
|
319
|
+
say "❌ Failed to install importmap-rails gem", :red
|
|
320
|
+
say "Error: #{e.message}", :red
|
|
321
|
+
say "Please install manually: bundle add importmap-rails", :yellow
|
|
322
|
+
raise
|
|
323
|
+
end
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
# Only run importmap:install if importmap.rb doesn't exist
|
|
327
|
+
# (Rails 8 apps already have it configured)
|
|
328
|
+
unless File.exist?(Rails.root.join("config/importmap.rb"))
|
|
329
|
+
begin
|
|
330
|
+
rails_command "importmap:install"
|
|
331
|
+
rescue => e
|
|
332
|
+
say "❌ Failed to run importmap:install", :red
|
|
333
|
+
say "Error: #{e.message}", :red
|
|
334
|
+
say "Please run manually: rails importmap:install", :yellow
|
|
335
|
+
raise
|
|
336
|
+
end
|
|
337
|
+
end
|
|
338
|
+
|
|
339
|
+
pin_importmap_dependencies(theme)
|
|
340
|
+
add_importmap_css_dependencies(theme)
|
|
127
341
|
end
|
|
128
342
|
|
|
343
|
+
# Legacy method for backward compatibility
|
|
129
344
|
def install_theme_dependencies(theme)
|
|
130
|
-
|
|
131
|
-
add_yarn_packages(theme_dependencies(theme))
|
|
345
|
+
install_js_dependencies_build(theme)
|
|
132
346
|
end
|
|
133
347
|
|
|
134
348
|
def remove_action_text_defaults
|
|
135
|
-
|
|
136
|
-
#
|
|
137
|
-
|
|
138
|
-
gsub_file "app/assets/stylesheets/application.tailwind.css", /@import 'actiontext.css';/, ""
|
|
349
|
+
# This method is kept for backwards compatibility but no longer needed
|
|
350
|
+
# ActionText CSS import is now handled in copy_theme_stylesheets
|
|
139
351
|
end
|
|
140
352
|
|
|
141
353
|
def humanize_theme(theme)
|
|
142
354
|
theme.humanize
|
|
143
355
|
end
|
|
144
356
|
|
|
145
|
-
|
|
357
|
+
# JS-only dependencies (Tailwind v4 plugins handled by standalone binary via @plugin directive)
|
|
358
|
+
def js_theme_dependencies(theme)
|
|
146
359
|
case theme
|
|
147
360
|
when "hound"
|
|
148
|
-
["
|
|
361
|
+
["apexcharts", "railsui-stimulus", "stimulus-use", "tippy.js"]
|
|
149
362
|
when "shepherd"
|
|
150
|
-
["
|
|
151
|
-
"stimulus-use", "tippy.js", "tailwindcss@latest", "@tailwindcss/cli@latest"]
|
|
363
|
+
["apexcharts", "flatpickr", "hotkeys-js", "photoswipe", "railsui-stimulus", "stimulus-use", "tippy.js"]
|
|
152
364
|
when "corgie"
|
|
153
|
-
["
|
|
365
|
+
["railsui-stimulus", "stimulus-use", "tippy.js", "marked", "highlight.js", "sanitize-html"]
|
|
154
366
|
else
|
|
155
|
-
["
|
|
156
|
-
|
|
367
|
+
["railsui-stimulus", "stimulus-use", "tippy.js"]
|
|
368
|
+
end
|
|
369
|
+
end
|
|
370
|
+
|
|
371
|
+
# Legacy method for backward compatibility
|
|
372
|
+
def theme_dependencies(theme)
|
|
373
|
+
js_theme_dependencies(theme)
|
|
374
|
+
end
|
|
375
|
+
|
|
376
|
+
# Importmap pins for nobuild mode
|
|
377
|
+
def importmap_theme_dependencies(theme)
|
|
378
|
+
# Core railsui-stimulus dependencies (required for all themes)
|
|
379
|
+
# Note: @hotwired/stimulus is already provided by Rails, so we don't pin it
|
|
380
|
+
# Using esm.sh CDN which provides optimized ESM builds without process.env references
|
|
381
|
+
base_pins = {
|
|
382
|
+
"railsui-stimulus" => "https://unpkg.com/railsui-stimulus@1.1.2/dist/importmap/index.js",
|
|
383
|
+
"tippy.js" => "https://esm.sh/tippy.js@6.3.7",
|
|
384
|
+
"@popperjs/core" => "https://esm.sh/@popperjs/core@2.11.8",
|
|
385
|
+
"stimulus-use" => "https://esm.sh/stimulus-use@0.52.2",
|
|
386
|
+
"flatpickr" => "https://esm.sh/flatpickr@4.6.13"
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
# Theme-specific dependencies
|
|
390
|
+
theme_specific = case theme
|
|
391
|
+
when "hound"
|
|
392
|
+
{
|
|
393
|
+
"apexcharts" => "https://esm.sh/apexcharts@3.45.2"
|
|
394
|
+
}
|
|
395
|
+
when "shepherd"
|
|
396
|
+
{
|
|
397
|
+
"apexcharts" => "https://esm.sh/apexcharts@3.45.2",
|
|
398
|
+
"hotkeys-js" => "https://esm.sh/hotkeys-js@3.13.15",
|
|
399
|
+
"photoswipe" => "https://esm.sh/photoswipe@5.4.3",
|
|
400
|
+
"photoswipe/lightbox" => "https://esm.sh/photoswipe@5.4.3/lightbox"
|
|
401
|
+
}
|
|
402
|
+
when "corgie"
|
|
403
|
+
{
|
|
404
|
+
"marked" => "https://esm.sh/marked@11.1.1",
|
|
405
|
+
"highlight.js" => "https://esm.sh/highlight.js@11.9.0",
|
|
406
|
+
"sanitize-html" => "https://esm.sh/sanitize-html@2.11.0"
|
|
407
|
+
}
|
|
408
|
+
else
|
|
409
|
+
{}
|
|
410
|
+
end
|
|
411
|
+
|
|
412
|
+
base_pins.merge(theme_specific)
|
|
413
|
+
end
|
|
414
|
+
|
|
415
|
+
def pin_importmap_dependencies(theme)
|
|
416
|
+
importmap_file = Rails.root.join("config/importmap.rb")
|
|
417
|
+
return unless File.exist?(importmap_file)
|
|
418
|
+
|
|
419
|
+
importmap_content = File.read(importmap_file)
|
|
420
|
+
|
|
421
|
+
say "Pinning Rails UI dependencies to importmap...", :yellow
|
|
422
|
+
|
|
423
|
+
# Pin external theme dependencies (railsui-stimulus, tippy.js, etc.)
|
|
424
|
+
importmap_theme_dependencies(theme).each do |package, url|
|
|
425
|
+
pin_statement = "pin \"#{package}\", to: \"#{url}\"\n"
|
|
426
|
+
unless importmap_content.include?("pin \"#{package}\"")
|
|
427
|
+
File.open(importmap_file, 'a') { |f| f.write(pin_statement) }
|
|
428
|
+
say "✓ Pinned #{package}", :green
|
|
429
|
+
else
|
|
430
|
+
say "- #{package} already pinned", :cyan
|
|
431
|
+
end
|
|
432
|
+
end
|
|
433
|
+
|
|
434
|
+
# Note: Gem's internal controllers are NOT pinned to importmap because:
|
|
435
|
+
# - They're only used on /railsui/* routes (gem's internal pages)
|
|
436
|
+
# - Those pages use CDN dependencies loaded inline via _cdn_dependencies.html.erb
|
|
437
|
+
# - User-installed theme pages (/rui/*) use theme-specific controllers from app/javascript/controllers/railsui/
|
|
438
|
+
|
|
439
|
+
say "✅ All importmap dependencies pinned successfully!", :green
|
|
440
|
+
end
|
|
441
|
+
|
|
442
|
+
def gem_controllers_dependencies
|
|
443
|
+
# Map import names to propshaft-served paths for gem controllers
|
|
444
|
+
{
|
|
445
|
+
"controllers/railsui/index" => "controllers/railsui/index.js",
|
|
446
|
+
"controllers/railsui/application" => "controllers/application.js",
|
|
447
|
+
"controllers/railsui_anchor_controller" => "controllers/railsui_anchor_controller.js",
|
|
448
|
+
"controllers/railsui_configuration_controller" => "controllers/railsui_configuration_controller.js",
|
|
449
|
+
"controllers/railsui_code_controller" => "controllers/railsui_code_controller.js",
|
|
450
|
+
"controllers/railsui_canvas_controller" => "controllers/railsui_canvas_controller.js",
|
|
451
|
+
"controllers/railsui_dialog_controller" => "controllers/railsui_dialog_controller.js",
|
|
452
|
+
"controllers/railsui_flash_controller" => "controllers/railsui_flash_controller.js",
|
|
453
|
+
"controllers/railsui_helper_controller" => "controllers/railsui_helper_controller.js",
|
|
454
|
+
"controllers/railsui_nav_controller" => "controllers/railsui_nav_controller.js",
|
|
455
|
+
"controllers/railsui_prevent_controller" => "controllers/railsui_prevent_controller.js",
|
|
456
|
+
"controllers/railsui_scroll_controller" => "controllers/railsui_scroll_controller.js",
|
|
457
|
+
"controllers/railsui_scroll_spy_controller" => "controllers/railsui_scroll_spy_controller.js",
|
|
458
|
+
"controllers/railsui_search_controller" => "controllers/railsui_search_controller.js",
|
|
459
|
+
"controllers/railsui_smooth_controller" => "controllers/railsui_smooth_controller.js",
|
|
460
|
+
"controllers/railsui_snippet_controller" => "controllers/railsui_snippet_controller.js",
|
|
461
|
+
"controllers/railsui_pages_controller" => "controllers/railsui_pages_controller.js",
|
|
462
|
+
"controllers/railsui_loading_controller" => "controllers/railsui_loading_controller.js"
|
|
463
|
+
}
|
|
464
|
+
end
|
|
465
|
+
|
|
466
|
+
def add_importmap_css_dependencies(theme)
|
|
467
|
+
application_css_path = Rails.root.join("app/assets/tailwind/application.css")
|
|
468
|
+
return unless File.exist?(application_css_path)
|
|
469
|
+
|
|
470
|
+
css_content = File.read(application_css_path)
|
|
471
|
+
|
|
472
|
+
# Required CSS dependencies for railsui-stimulus components
|
|
473
|
+
css_imports = []
|
|
474
|
+
css_imports << '@import "https://unpkg.com/tippy.js@6.3.7/dist/tippy.css";'
|
|
475
|
+
|
|
476
|
+
# Add flatpickr CSS for themes that use date picker
|
|
477
|
+
if ["shepherd"].include?(theme)
|
|
478
|
+
css_imports << '@import "https://unpkg.com/flatpickr@4.6.13/dist/flatpickr.min.css";'
|
|
479
|
+
end
|
|
480
|
+
|
|
481
|
+
css_imports.each do |import_statement|
|
|
482
|
+
unless css_content.include?(import_statement)
|
|
483
|
+
# Add after @import "tailwindcss" if it exists, otherwise at the top
|
|
484
|
+
if css_content.include?('@import "tailwindcss"')
|
|
485
|
+
css_content.sub!('@import "tailwindcss";', "@import \"tailwindcss\";\n#{import_statement}")
|
|
486
|
+
else
|
|
487
|
+
css_content = "#{import_statement}\n#{css_content}"
|
|
488
|
+
end
|
|
489
|
+
say "✓ Added CSS import: #{import_statement}", :green
|
|
490
|
+
end
|
|
157
491
|
end
|
|
492
|
+
|
|
493
|
+
File.write(application_css_path, css_content)
|
|
494
|
+
say "✅ CSS dependencies added for importmap mode", :green
|
|
158
495
|
end
|
|
159
496
|
|
|
160
497
|
def add_yarn_packages(packages)
|
|
161
498
|
package_manager = detect_package_manager
|
|
162
499
|
say "Using #{package_manager} to install packages...", :green
|
|
163
500
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
501
|
+
begin
|
|
502
|
+
case package_manager
|
|
503
|
+
when "yarn"
|
|
504
|
+
run "yarn add #{packages.join(" ")}"
|
|
505
|
+
when "npm"
|
|
506
|
+
run "npm install #{packages.join(" ")}"
|
|
507
|
+
when "pnpm"
|
|
508
|
+
run "pnpm add #{packages.join(" ")}"
|
|
509
|
+
when "bun"
|
|
510
|
+
run "bun add #{packages.join(" ")}"
|
|
511
|
+
else
|
|
512
|
+
# Fallback to yarn if no package manager detected
|
|
513
|
+
say "⚠️ No package manager detected, falling back to yarn", :yellow
|
|
514
|
+
run "yarn add #{packages.join(" ")}"
|
|
515
|
+
end
|
|
516
|
+
say "✅ Installed packages: #{packages.join(', ')}", :green
|
|
517
|
+
rescue => e
|
|
518
|
+
say "❌ Failed to install JavaScript packages", :red
|
|
519
|
+
say "Error: #{e.message}", :red
|
|
520
|
+
say "Please install manually:", :yellow
|
|
521
|
+
say " #{package_manager} add #{packages.join(' ')}", :yellow
|
|
522
|
+
raise
|
|
177
523
|
end
|
|
178
524
|
end
|
|
179
525
|
|
|
@@ -195,6 +541,153 @@ module Railsui
|
|
|
195
541
|
end
|
|
196
542
|
end
|
|
197
543
|
|
|
544
|
+
def detect_js_bundler
|
|
545
|
+
# Check for bundler configs first (since Rails 8 apps have importmap.rb by default)
|
|
546
|
+
# Check package.json for build script as primary indicator
|
|
547
|
+
package_json = Rails.root.join("package.json")
|
|
548
|
+
if File.exist?(package_json)
|
|
549
|
+
content = File.read(package_json)
|
|
550
|
+
data = JSON.parse(content) rescue {}
|
|
551
|
+
build_script = data.dig("scripts", "build").to_s
|
|
552
|
+
|
|
553
|
+
return "esbuild" if build_script.include?("esbuild")
|
|
554
|
+
return "webpack" if build_script.include?("webpack")
|
|
555
|
+
return "rollup" if build_script.include?("rollup")
|
|
556
|
+
return "bun" if build_script.include?("bun")
|
|
557
|
+
end
|
|
558
|
+
|
|
559
|
+
# Fall back to config file detection
|
|
560
|
+
if File.exist?(Rails.root.join("esbuild.config.js")) || File.exist?(Rails.root.join("esbuild.config.mjs"))
|
|
561
|
+
"esbuild"
|
|
562
|
+
elsif File.exist?(Rails.root.join("webpack.config.js"))
|
|
563
|
+
"webpack"
|
|
564
|
+
elsif File.exist?(Rails.root.join("rollup.config.js"))
|
|
565
|
+
"rollup"
|
|
566
|
+
elsif File.exist?(Rails.root.join("bun.lockb"))
|
|
567
|
+
"bun"
|
|
568
|
+
elsif File.exist?(Rails.root.join("config/importmap.rb"))
|
|
569
|
+
"importmap"
|
|
570
|
+
else
|
|
571
|
+
"unknown"
|
|
572
|
+
end
|
|
573
|
+
end
|
|
574
|
+
|
|
575
|
+
def gem_installed?(gem_name)
|
|
576
|
+
Gem::Specification.find_by_name(gem_name)
|
|
577
|
+
true
|
|
578
|
+
rescue Gem::LoadError
|
|
579
|
+
false
|
|
580
|
+
end
|
|
581
|
+
|
|
582
|
+
def detect_and_warn_about_setup
|
|
583
|
+
warnings = []
|
|
584
|
+
recommendations = []
|
|
585
|
+
|
|
586
|
+
# Check for existing setup
|
|
587
|
+
has_cssbundling = gem_installed?('cssbundling-rails')
|
|
588
|
+
has_jsbundling = gem_installed?('jsbundling-rails')
|
|
589
|
+
has_importmap = File.exist?(Rails.root.join("config/importmap.rb"))
|
|
590
|
+
|
|
591
|
+
# Check for Tailwind in package.json
|
|
592
|
+
has_tailwind_npm = false
|
|
593
|
+
package_json_path = Rails.root.join("package.json")
|
|
594
|
+
if File.exist?(package_json_path)
|
|
595
|
+
package_json = JSON.parse(File.read(package_json_path))
|
|
596
|
+
has_tailwind_npm = package_json.dig('dependencies', 'tailwindcss') ||
|
|
597
|
+
package_json.dig('devDependencies', 'tailwindcss')
|
|
598
|
+
end
|
|
599
|
+
|
|
600
|
+
# Detect legacy build mode setup (cssbundling + jsbundling)
|
|
601
|
+
legacy_build_setup = (has_cssbundling || has_tailwind_npm) && has_jsbundling
|
|
602
|
+
|
|
603
|
+
if legacy_build_setup && !options[:build]
|
|
604
|
+
say ""
|
|
605
|
+
say "=" * 70, :red
|
|
606
|
+
say "❌ Build mode setup detected but --build flag not provided", :red
|
|
607
|
+
say "=" * 70, :red
|
|
608
|
+
say ""
|
|
609
|
+
say "Your app uses build mode (cssbundling + jsbundling).", :yellow
|
|
610
|
+
say "You must choose a mode before installing:", :yellow
|
|
611
|
+
say ""
|
|
612
|
+
say "Keep build mode (recommended):", :cyan
|
|
613
|
+
say " rails railsui:install --build", :cyan
|
|
614
|
+
say " rails railsui:migrate_to_tailwindcss_rails", :cyan
|
|
615
|
+
say ""
|
|
616
|
+
say "Or switch to no-build (requires manual JS refactoring):", :cyan
|
|
617
|
+
say " bundle add importmap-rails && rails importmap:install", :cyan
|
|
618
|
+
say " rails railsui:install", :cyan
|
|
619
|
+
say ""
|
|
620
|
+
exit(1)
|
|
621
|
+
elsif has_cssbundling && !has_jsbundling
|
|
622
|
+
warnings << "⚠️ Detected cssbundling-rails gem"
|
|
623
|
+
recommendations << "After install, migrate to tailwindcss-rails:"
|
|
624
|
+
recommendations << " rails railsui:migrate_to_tailwindcss_rails"
|
|
625
|
+
elsif has_jsbundling && !has_importmap && !options[:build]
|
|
626
|
+
say ""
|
|
627
|
+
say "=" * 70, :red
|
|
628
|
+
say "❌ jsbundling-rails detected but --build flag not provided", :red
|
|
629
|
+
say "=" * 70, :red
|
|
630
|
+
say ""
|
|
631
|
+
say "Your app has jsbundling-rails installed.", :yellow
|
|
632
|
+
say "You must choose a mode before installing:", :yellow
|
|
633
|
+
say ""
|
|
634
|
+
say "Use JS bundler (recommended): rails railsui:install --build", :cyan
|
|
635
|
+
say ""
|
|
636
|
+
say "Or switch to importmap (requires manual JS refactoring):", :cyan
|
|
637
|
+
say " bundle add importmap-rails && rails importmap:install", :cyan
|
|
638
|
+
say " rails railsui:install", :cyan
|
|
639
|
+
say ""
|
|
640
|
+
exit(1)
|
|
641
|
+
end
|
|
642
|
+
|
|
643
|
+
# Inform about coexisting importmap when using build mode
|
|
644
|
+
if options[:build] && has_importmap && bundler != "importmap"
|
|
645
|
+
warnings << "ℹ️ Both importmap.rb and JS bundler detected"
|
|
646
|
+
recommendations << "Rails UI will install packages for the bundler and migrate imports"
|
|
647
|
+
recommendations << "It's recommended to use the bundler format rather than mixing both"
|
|
648
|
+
recommendations << "You can remove config/importmap.rb if you don't need it"
|
|
649
|
+
recommendations << ""
|
|
650
|
+
recommendations << "Note: You may see build errors during installation - these will be"
|
|
651
|
+
recommendations << "resolved as Rails UI installs the required packages"
|
|
652
|
+
end
|
|
653
|
+
|
|
654
|
+
if !has_jsbundling && !has_importmap
|
|
655
|
+
warnings << "ℹ️ No JS bundler or importmap detected"
|
|
656
|
+
if options[:build]
|
|
657
|
+
recommendations << "Please install a JS bundler first:"
|
|
658
|
+
recommendations << " bundle add jsbundling-rails"
|
|
659
|
+
recommendations << " rails javascript:install:[bun|esbuild|rollup|webpack]"
|
|
660
|
+
recommendations << "Then run: rails railsui:install --build"
|
|
661
|
+
else
|
|
662
|
+
recommendations << "Will install importmap-rails for you (Rails 8 default)"
|
|
663
|
+
end
|
|
664
|
+
end
|
|
665
|
+
|
|
666
|
+
# Display warnings
|
|
667
|
+
if warnings.any?
|
|
668
|
+
say ""
|
|
669
|
+
say "=" * 60, :yellow
|
|
670
|
+
warnings.each { |w| say w, :yellow }
|
|
671
|
+
say "=" * 60, :yellow
|
|
672
|
+
say ""
|
|
673
|
+
if recommendations.any?
|
|
674
|
+
say "Recommendations:", :cyan
|
|
675
|
+
recommendations.each { |r| say " #{r}", :cyan }
|
|
676
|
+
say ""
|
|
677
|
+
end
|
|
678
|
+
|
|
679
|
+
unless ENV['RAILSUI_SKIP_WARNINGS']
|
|
680
|
+
print "Continue with installation? (y/n): "
|
|
681
|
+
response = STDIN.gets.chomp.downcase
|
|
682
|
+
unless response == 'y'
|
|
683
|
+
say "Installation cancelled", :red
|
|
684
|
+
exit
|
|
685
|
+
end
|
|
686
|
+
say ""
|
|
687
|
+
end
|
|
688
|
+
end
|
|
689
|
+
end
|
|
690
|
+
|
|
198
691
|
# Mailers
|
|
199
692
|
def generate_sample_mailers(theme)
|
|
200
693
|
say "Adding Rails UI mailers", :yellow
|
|
@@ -346,6 +839,73 @@ module Railsui
|
|
|
346
839
|
directory theme_images_dir, target_images_dir, force: true
|
|
347
840
|
end
|
|
348
841
|
|
|
842
|
+
def copy_procfile
|
|
843
|
+
config = Railsui::Configuration.load!
|
|
844
|
+
procfile_path = Rails.root.join("Procfile.dev")
|
|
845
|
+
|
|
846
|
+
if config.build?
|
|
847
|
+
# In build mode, jsbundling-rails already created the Procfile
|
|
848
|
+
# We just need to add the CSS line if it's not already there
|
|
849
|
+
if File.exist?(procfile_path)
|
|
850
|
+
content = File.read(procfile_path)
|
|
851
|
+
unless content.include?("tailwindcss:watch")
|
|
852
|
+
File.open(procfile_path, "a") do |f|
|
|
853
|
+
f.puts "css: bin/rails tailwindcss:watch"
|
|
854
|
+
end
|
|
855
|
+
say("Added CSS watch to existing Procfile.dev", :green)
|
|
856
|
+
else
|
|
857
|
+
say("✓ Procfile.dev already has CSS watch (skipping)", :yellow)
|
|
858
|
+
end
|
|
859
|
+
else
|
|
860
|
+
# Fallback if Procfile doesn't exist (shouldn't happen with jsbundling-rails)
|
|
861
|
+
copy_file "Procfile.dev.build", procfile_path
|
|
862
|
+
say("Created Procfile.dev for build mode", :green)
|
|
863
|
+
end
|
|
864
|
+
else
|
|
865
|
+
# In nobuild mode, ensure required processes exist without being destructive
|
|
866
|
+
if File.exist?(procfile_path)
|
|
867
|
+
content = File.read(procfile_path)
|
|
868
|
+
lines_to_add = []
|
|
869
|
+
|
|
870
|
+
# Check for web process
|
|
871
|
+
unless content.match?(/^web:/)
|
|
872
|
+
lines_to_add << "web: bin/rails server -p 3000"
|
|
873
|
+
end
|
|
874
|
+
|
|
875
|
+
# Check for CSS process
|
|
876
|
+
unless content.include?("tailwindcss:watch")
|
|
877
|
+
lines_to_add << "css: bin/rails tailwindcss:watch"
|
|
878
|
+
end
|
|
879
|
+
|
|
880
|
+
if lines_to_add.any?
|
|
881
|
+
File.open(procfile_path, "a") do |f|
|
|
882
|
+
lines_to_add.each { |line| f.puts line }
|
|
883
|
+
end
|
|
884
|
+
say("Added missing processes to Procfile.dev: #{lines_to_add.join(', ')}", :green)
|
|
885
|
+
else
|
|
886
|
+
say("✓ Procfile.dev already has required processes (skipping)", :yellow)
|
|
887
|
+
end
|
|
888
|
+
else
|
|
889
|
+
# No Procfile exists, create it
|
|
890
|
+
copy_file "Procfile.dev.nobuild", procfile_path
|
|
891
|
+
say("Created Procfile.dev for no-build mode", :green)
|
|
892
|
+
end
|
|
893
|
+
end
|
|
894
|
+
end
|
|
895
|
+
|
|
896
|
+
def copy_bin_dev
|
|
897
|
+
bin_dev_path = Rails.root.join("bin/dev")
|
|
898
|
+
|
|
899
|
+
# Only copy if it doesn't exist or if it's the simple Rails server version
|
|
900
|
+
if !File.exist?(bin_dev_path) || File.read(bin_dev_path).include?('exec "./bin/rails", "server"')
|
|
901
|
+
copy_file "bin/dev", bin_dev_path, force: true
|
|
902
|
+
chmod bin_dev_path, 0755
|
|
903
|
+
say("Created bin/dev script", :green)
|
|
904
|
+
else
|
|
905
|
+
say("✓ bin/dev already exists (skipping)", :yellow)
|
|
906
|
+
end
|
|
907
|
+
end
|
|
908
|
+
|
|
349
909
|
private
|
|
350
910
|
|
|
351
911
|
def remove_directory(directory_path, thing)
|