Class: Terrazzo::Generators::InstallGenerator
- Inherits:
-
Rails::Generators::Base
- Object
- Rails::Generators::Base
- Terrazzo::Generators::InstallGenerator
- Defined in:
- lib/generators/terrazzo/install/install_generator.rb
Constant Summary collapse
- TAILWIND_PACKAGE_NAME =
"tailwindcss"- TAILWIND_CLI_PACKAGE_NAME =
"@tailwindcss/cli"- TAILWIND_VITE_PACKAGE_NAME =
"@tailwindcss/vite"- TAILWIND_RAILS_GEM_NAME =
"tailwindcss-rails"- TAILWIND_MINIMUM_VERSION =
"4.0.0"- FRONTEND_DEPENDENCIES =
( %w[ terrazzo react react-dom react-redux @reduxjs/toolkit @thoughtbot/superglue @radix-ui/react-avatar @radix-ui/react-dialog @radix-ui/react-dropdown-menu @radix-ui/react-label @radix-ui/react-popover @radix-ui/react-select @radix-ui/react-separator @radix-ui/react-slot @radix-ui/react-tooltip class-variance-authority lucide-react ] + [TAILWIND_PACKAGE_NAME] ).freeze
- FRONTEND_DEPENDENCY_MINIMUMS =
{ "terrazzo" => Terrazzo::VERSION, "react" => "18.0.0", "react-dom" => "18.0.0", "react-redux" => "8.0.0", "@reduxjs/toolkit" => "1.9.0", "@thoughtbot/superglue" => "1.0.0", "@radix-ui/react-avatar" => "1.0.0", "@radix-ui/react-dialog" => "1.0.0", "@radix-ui/react-dropdown-menu" => "2.0.0", "@radix-ui/react-label" => "2.0.0", "@radix-ui/react-popover" => "1.0.0", "@radix-ui/react-select" => "2.0.0", "@radix-ui/react-separator" => "1.0.0", "@radix-ui/react-slot" => "1.0.0", "@radix-ui/react-tooltip" => "1.0.0", "class-variance-authority" => "0.7.0", "lucide-react" => "0.300.0", TAILWIND_PACKAGE_NAME => TAILWIND_MINIMUM_VERSION, }.freeze
- VITE_FRONTEND_DEPENDENCIES =
%w[ vite vite-plugin-ruby ].freeze
- SUPPORTED_BUNDLERS =
%w[vite esbuild].freeze
Instance Method Summary collapse
- #configure_vite_dev_server ⇒ Object
- #configure_vite_watch_paths ⇒ Object
- #create_application_controller ⇒ Object
- #create_application_visit ⇒ Object
- #create_components_json ⇒ Object
- #create_custom_page_mapping ⇒ Object
- #create_esbuild_entry_point ⇒ Object
- #create_flash_slice ⇒ Object
- #create_generated_page_mapping ⇒ Object
- #create_js_entry_point ⇒ Object
- #create_jsconfig ⇒ Object
- #create_json_props_layout ⇒ Object
- #create_layout ⇒ Object
- #create_page_to_page_mapping ⇒ Object
- #create_store ⇒ Object
- #create_stylesheet ⇒ Object
- #create_tailwind_build_script ⇒ Object
- #create_vite_build_script ⇒ Object
- #create_vite_entry_point ⇒ Object
- #run_dashboard_generators ⇒ Object
- #run_routes_generator ⇒ Object
- #run_views_generator ⇒ Object
- #validate_bundler ⇒ Object
- #validate_namespace ⇒ Object
- #verify_database_schema ⇒ Object
- #verify_frontend_dependencies ⇒ Object
- #verify_tailwind_build_pipeline ⇒ Object
- #verify_vite_bundler ⇒ Object
Instance Method Details
#configure_vite_dev_server ⇒ Object
176 177 178 179 180 181 |
# File 'lib/generators/terrazzo/install/install_generator.rb', line 176 def configure_vite_dev_server return unless vite? ensure_vite_procfile ensure_vite_bin_dev end |
#configure_vite_watch_paths ⇒ Object
158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 |
# File 'lib/generators/terrazzo/install/install_generator.rb', line 158 def configure_vite_watch_paths return unless vite? config = parsed_vite_config return unless config watch_paths = vite_watch_paths return if watch_paths.empty? config["all"] = {} unless config["all"].is_a?(Hash) existing_paths = Array(config["all"]["watchAdditionalPaths"]) updated_paths = existing_paths | watch_paths return if updated_paths == existing_paths config["all"]["watchAdditionalPaths"] = updated_paths create_file "config/vite.json", "#{JSON.pretty_generate(config)}\n", force: true end |
#create_application_controller ⇒ Object
131 132 133 134 |
# File 'lib/generators/terrazzo/install/install_generator.rb', line 131 def create_application_controller template "application_controller.rb.erb", "app/controllers/#{namespace_name}/application_controller.rb" end |
#create_application_visit ⇒ Object
210 211 212 213 |
# File 'lib/generators/terrazzo/install/install_generator.rb', line 210 def create_application_visit template "application_visit.js.erb", "app/javascript/#{namespace_name}/application_visit.js" end |
#create_components_json ⇒ Object
262 263 264 265 266 267 268 269 |
# File 'lib/generators/terrazzo/install/install_generator.rb', line 262 def create_components_json if File.exist?(File.join(destination_root, "components.json")) say_status :skip, "components.json already exists", :yellow return end template "components.json.erb", "components.json" end |
#create_custom_page_mapping ⇒ Object
205 206 207 208 |
# File 'lib/generators/terrazzo/install/install_generator.rb', line 205 def create_custom_page_mapping template "custom_page_mapping.js.erb", "app/javascript/#{namespace_name}/custom_page_mapping.js" end |
#create_esbuild_entry_point ⇒ Object
183 184 185 186 187 188 |
# File 'lib/generators/terrazzo/install/install_generator.rb', line 183 def create_esbuild_entry_point return unless esbuild? create_file "app/javascript/#{namespace_name}.js", %(import "./#{namespace_name}/application.jsx"\n) end |
#create_flash_slice ⇒ Object
215 216 217 218 |
# File 'lib/generators/terrazzo/install/install_generator.rb', line 215 def create_flash_slice template "flash_slice.js.erb", "app/javascript/#{namespace_name}/slices/flash.js" end |
#create_generated_page_mapping ⇒ Object
200 201 202 203 |
# File 'lib/generators/terrazzo/install/install_generator.rb', line 200 def create_generated_page_mapping template "generated_page_mapping.js.erb", "app/javascript/#{namespace_name}/generated_page_mapping.js" end |
#create_js_entry_point ⇒ Object
146 147 148 |
# File 'lib/generators/terrazzo/install/install_generator.rb', line 146 def create_js_entry_point template "application.js.erb", js_entry_point_path end |
#create_jsconfig ⇒ Object
271 272 273 274 275 276 277 278 279 280 281 282 283 |
# File 'lib/generators/terrazzo/install/install_generator.rb', line 271 def create_jsconfig if File.exist?(File.join(destination_root, "jsconfig.json")) say_status :skip, "jsconfig.json already exists", :yellow return end if File.exist?(File.join(destination_root, "tsconfig.json")) say_status :skip, "tsconfig.json already exists", :yellow return end copy_file "jsconfig.json", "jsconfig.json" end |
#create_json_props_layout ⇒ Object
141 142 143 144 |
# File 'lib/generators/terrazzo/install/install_generator.rb', line 141 def create_json_props_layout template "application.json.props.tt", "app/views/layouts/#{namespace_name}/application.json.props" end |
#create_layout ⇒ Object
136 137 138 139 |
# File 'lib/generators/terrazzo/install/install_generator.rb', line 136 def create_layout template "superglue.html.erb.erb", "app/views/layouts/#{namespace_name}/application.html.erb" end |
#create_page_to_page_mapping ⇒ Object
195 196 197 198 |
# File 'lib/generators/terrazzo/install/install_generator.rb', line 195 def create_page_to_page_mapping template "page_to_page_mapping.js.erb", "app/javascript/#{namespace_name}/page_to_page_mapping.js" end |
#create_store ⇒ Object
190 191 192 193 |
# File 'lib/generators/terrazzo/install/install_generator.rb', line 190 def create_store template "store.js.erb", "app/javascript/#{namespace_name}/store.js" end |
#create_stylesheet ⇒ Object
220 221 222 223 |
# File 'lib/generators/terrazzo/install/install_generator.rb', line 220 def create_stylesheet copy_file "admin.css", "app/assets/stylesheets/#{namespace_name}.css" end |
#create_tailwind_build_script ⇒ Object
225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 |
# File 'lib/generators/terrazzo/install/install_generator.rb', line 225 def create_tailwind_build_script return unless package_json_file? return if tailwind_build_pipeline? return unless tailwind_cli_package? return unless tailwind_cli_supported? package_json = parsed_package_json return if package_json.empty? scripts = package_json["scripts"] scripts = package_json["scripts"] = {} unless scripts.is_a?(Hash) return if scripts.key?(tailwind_build_script_name) scripts[tailwind_build_script_name] = tailwind_build_command create_file "package.json", "#{JSON.pretty_generate(package_json)}\n", force: true end |
#create_vite_build_script ⇒ Object
242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 |
# File 'lib/generators/terrazzo/install/install_generator.rb', line 242 def create_vite_build_script return unless vite? return unless package_json_file? package_json = parsed_package_json return if package_json.empty? scripts = package_json["scripts"] scripts = package_json["scripts"] = {} unless scripts.is_a?(Hash) return if scripts.key?("build") build_commands = ["vite build"] if scripts[tailwind_build_script_name] == tailwind_build_command build_commands << tailwind_build_command end scripts["build"] = build_commands.join(" && ") create_file "package.json", "#{JSON.pretty_generate(package_json)}\n", force: true end |
#create_vite_entry_point ⇒ Object
150 151 152 153 154 155 156 |
# File 'lib/generators/terrazzo/install/install_generator.rb', line 150 def create_vite_entry_point return unless vite? return if vite_entry_point_path == js_entry_point_path create_file vite_entry_point_path, %(import "#{relative_import_path(vite_entry_point_directory, js_entry_point_path)}"\n) end |
#run_dashboard_generators ⇒ Object
293 294 295 296 297 |
# File 'lib/generators/terrazzo/install/install_generator.rb', line 293 def run_dashboard_generators application_models.each do |model| generate "terrazzo:dashboard", model.name, "--namespace=#{namespace_name}", "--bundler=#{[:bundler]}" end end |
#run_routes_generator ⇒ Object
289 290 291 |
# File 'lib/generators/terrazzo/install/install_generator.rb', line 289 def run_routes_generator generate "terrazzo:routes", "--namespace=#{namespace_name}" end |
#run_views_generator ⇒ Object
285 286 287 |
# File 'lib/generators/terrazzo/install/install_generator.rb', line 285 def run_views_generator generate "terrazzo:views", "--namespace=#{namespace_name}" end |
#validate_bundler ⇒ Object
72 73 74 75 76 77 78 79 80 81 |
# File 'lib/generators/terrazzo/install/install_generator.rb', line 72 def validate_bundler return if SUPPORTED_BUNDLERS.include?([:bundler]) raise Thor::Error, <<~MESSAGE Unsupported bundler '#{[:bundler]}'. Terrazzo generates React/JSX admin views and supports Vite or esbuild. Re-run with `--bundler=vite` or `--bundler=esbuild`. MESSAGE end |
#validate_namespace ⇒ Object
83 84 85 86 87 88 89 90 91 92 93 |
# File 'lib/generators/terrazzo/install/install_generator.rb', line 83 def validate_namespace return if namespace_argument.blank? return if [:namespace] == "admin" || [:namespace] == namespace_argument raise Thor::Error, <<~MESSAGE Conflicting admin namespaces: '#{namespace_argument}' and '#{[:namespace]}'. Use either `rails generate terrazzo:install #{namespace_argument}` or `rails generate terrazzo:install --namespace=#{[:namespace]}`, not both. MESSAGE end |
#verify_database_schema ⇒ Object
116 117 118 119 120 121 122 123 124 125 126 127 128 129 |
# File 'lib/generators/terrazzo/install/install_generator.rb', line 116 def verify_database_schema models = application_models raise_no_models_error if models.empty? missing_tables = models.reject { |model| table_exists_for?(model) } return if missing_tables.empty? model_names = missing_tables.map(&:name).join(", ") raise Thor::Error, <<~MESSAGE Terrazzo could not generate dashboards because these models do not have database tables: #{model_names} Run `bin/rails db:prepare` first, then run `bin/rails generate terrazzo:install` again. MESSAGE end |
#verify_frontend_dependencies ⇒ Object
299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 |
# File 'lib/generators/terrazzo/install/install_generator.rb', line 299 def verify_frontend_dependencies missing = missing_frontend_dependencies outdated = outdated_frontend_dependencies if missing.any? say_status :warning, "Missing frontend dependencies required by Terrazzo:", :yellow say " #{missing.join(" ")}" say "Run: #{frontend_install_command(missing)}" end return if outdated.empty? say_status :warning, "Frontend dependencies below Terrazzo's supported versions:", :yellow outdated.each do |dependency| say " #{dependency[:name]} #{dependency[:requirement]} (requires >= #{dependency[:minimum]})" end say "Run: #{frontend_install_command(outdated.map { |dependency| "#{dependency[:name]}@latest" })}" end |
#verify_tailwind_build_pipeline ⇒ Object
318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 |
# File 'lib/generators/terrazzo/install/install_generator.rb', line 318 def verify_tailwind_build_pipeline if tailwind_cli_package? && !tailwind_cli_supported? say_status :warning, "Installed #{TAILWIND_CLI_PACKAGE_NAME} is below Terrazzo's supported version.", :yellow say "Terrazzo generated app/assets/stylesheets/#{namespace_name}.css with Tailwind #{TAILWIND_MINIMUM_VERSION}+ syntax." say "Run: #{package_manager_add_dev_command} #{TAILWIND_CLI_PACKAGE_NAME}@latest" return end if tailwind_rails_gem? && !tailwind_rails_gem_supported? say_status :warning, "Installed #{TAILWIND_RAILS_GEM_NAME} is below Terrazzo's supported version.", :yellow say "Terrazzo generated app/assets/stylesheets/#{namespace_name}.css with Tailwind #{TAILWIND_MINIMUM_VERSION}+ syntax." say "Update #{TAILWIND_RAILS_GEM_NAME} to #{TAILWIND_MINIMUM_VERSION} or newer, or install #{TAILWIND_CLI_PACKAGE_NAME}@latest." return end return if tailwind_build_pipeline? say_status :warning, "Terrazzo generated app/assets/stylesheets/#{namespace_name}.css but no Tailwind build pipeline was detected.", :yellow say "Make sure your app compiles that file with Tailwind before serving the admin UI." if tailwind_vite_plugin_package? say "#{TAILWIND_VITE_PACKAGE_NAME} is installed, but Terrazzo links #{admin_stylesheet_path} as a Rails stylesheet asset." end say "For package.json scripts, install the Tailwind CLI and add a build script:" say " #{package_manager_add_dev_command} #{TAILWIND_CLI_PACKAGE_NAME}@latest" say %( "#{tailwind_build_script_name}": "#{tailwind_build_command}") end |
#verify_vite_bundler ⇒ Object
95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 |
# File 'lib/generators/terrazzo/install/install_generator.rb', line 95 def verify_vite_bundler return unless vite? unless vite_rails_installed? && parsed_vite_config raise Thor::Error, <<~MESSAGE Terrazzo is configured for Vite, but vite_rails is not installed and configured. Add `gem "vite_rails"` and run `bundle exec vite install` first, or re-run Terrazzo with `--bundler=esbuild`. MESSAGE end missing = missing_vite_frontend_dependencies return if missing.empty? raise Thor::Error, <<~MESSAGE Terrazzo is configured for Vite, but package.json is missing required Vite dependencies: #{missing.join(", ")}. Run `bundle exec vite install` successfully, install the generated package.json dependencies, or re-run Terrazzo with `--bundler=esbuild`. MESSAGE end |