Class: Ruact::Generators::InstallGenerator
- Inherits:
-
Rails::Generators::Base
- Object
- Rails::Generators::Base
- Ruact::Generators::InstallGenerator
- Defined in:
- lib/generators/ruact/install/install_generator.rb
Overview
Installs ruact into the current Rails application.
Performs the following actions:
- Creates config/initializers/ruact.rb
- Injects
include Ruact::Controllerinto ApplicationController - Injects the React root div into app/views/layouts/application.html.erb
- Creates app/javascript/components/.keep
- Creates vite.config.js (or shows manual instructions if one exists)
- Creates package.json (react/react-dom/vite/@vitejs/plugin-react) so a fresh app has JS deps to install (Story 14.6, FR101).
- Creates Procfile.dev + bin/dev (foreman) so the single
bin/devcommand boots BOTH Rails and the Vite dev server (Story 14.6, Epic 14 DoD — a ruact app needs both processes). - Runs
npm installso JavaScript dependencies are ready (FR101); skippable via --skip-npm.
Story 14.2 (FR104) — the generator no longer writes a bootstrap entry into
the user's tree. ruact's React entry is served as the virtual module
virtual:ruact/bootstrap by the bundled Vite plugin (the generated
vite.config input points at it), so a fresh install leaves
app/javascript/ with only the user's components/ (plus the gitignored,
typed .ruact/server-functions.ts).
Run: rails generate ruact:install
Instance Method Summary collapse
-
#advise_plumbing_migration ⇒ Object
Story 14.2 (FR104, AC7) — an app upgrading from the earlier layout still has app/javascript/applicationapplication.jsx,flight-clientapplication.jsx,flight-client.js,ruact-routerapplication.jsx,flight-client.js,ruact-router.js on disk.
- #append_gitignore_entries ⇒ Object
- #create_components_directory ⇒ Object
- #create_initializer ⇒ Object
-
#create_launch_files ⇒ Object
Story 14.6 (Epic 14 DoD) — emit
Procfile.dev+ a foreman-basedbin/devso the literalbin/devboots BOTH processes a ruact app needs: Rails (HTML shell + Flight + server functions) and the Vite dev server (React/HMR + the bundled ruact plugin). -
#create_package_json ⇒ Object
Story 14.6 (FR101, Epic 14 DoD) — a fresh ruact app needs a package.json declaring its JavaScript dependencies (React + Vite + the React Vite plugin) so the
npm installthat Story 14.1 runs has something to resolve andbin/dev'snpm run dev(Vite) has adevscript. -
#create_server_functions_directory ⇒ Object
Story 8.0a — scaffold the directory the codegen writes into and add the generated artifacts to .gitignore.
- #create_vite_config ⇒ Object
- #inject_controller_concern ⇒ Object
- #inject_layout_shell ⇒ Object
-
#install_javascript_dependencies ⇒ Object
Story 14.1 (FR101) — install JavaScript dependencies so a fresh app is runnable in one command.
-
#prime_server_functions_codegen ⇒ Object
Invokes
ruact:server_functions:generateso a fresh install completes with the AC8-required empty-but-valid generated module on disk. - #show_post_install_message ⇒ Object
Instance Method Details
#advise_plumbing_migration ⇒ Object
Story 14.2 (FR104, AC7) — an app upgrading from the earlier layout still has app/javascript/Ruact::Generators::InstallGenerator.applicationapplication.jsx,flight-clientapplication.jsx,flight-client.js,ruact-routerapplication.jsx,flight-client.js,ruact-router.js on disk. The generator never deletes user files, so it prints the exact manual steps to reach the hidden-plumbing layout — otherwise the app is left half-wired, with the stale entry shadowing the virtual bootstrap. No-op on a fresh install (none of those files exist).
190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 |
# File 'lib/generators/ruact/install/install_generator.rb', line 190 def advise_plumbing_migration stale = legacy_plumbing_files return if stale.empty? say_status "notice", "earlier ruact layout detected — finish the Story 14.2 migration:", :yellow say "" say " ruact's bootstrap entry + Flight runtime are now hidden behind the" say " virtual module '#{Ruact.bootstrap_virtual_id}' (served from the gem)." say " Remove these now-obsolete files so they don't shadow the virtual entry:" stale.each { |f| say " - delete #{f}" } say "" say " Then set your vite.config build input to '#{Ruact.bootstrap_virtual_id}'" say " (or re-run with --force to regenerate vite.config.js), and let the" say " controller's HTML shell — or the `ruact_js_assets` view helper in your" say " layout — emit the entry <script> tags." say "" end |
#append_gitignore_entries ⇒ Object
97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 |
# File 'lib/generators/ruact/install/install_generator.rb', line 97 def append_gitignore_entries gitignore = Pathname(destination_root).join(".gitignore") return unless gitignore.exist? entries = [ "app/javascript/.ruact/server-functions.ts", "tmp/cache/ruact/" ] # Substring matches (`existing.include?(entry)`) were unsafe — they # would skip "tmp/cache/ruact/" when the file already contained # "tmp/cache/ruact/some-cache.bin", leaving the directory itself # un-ignored. Match by exact normalized line instead. existing_lines = File.read(gitignore).each_line.to_set { |line| line.chomp.strip } new_entries = entries.reject { |e| existing_lines.include?(e) } return if new_entries.empty? append_to_file ".gitignore", "\n# ruact (Story 8.0a — auto-generated server-functions module)\n" new_entries.each { |entry| append_to_file ".gitignore", "#{entry}\n" } end |
#create_components_directory ⇒ Object
81 82 83 84 85 |
# File 'lib/generators/ruact/install/install_generator.rb', line 81 def create_components_directory empty_directory "app/javascript/components" create_file "app/javascript/components/.keep" unless File.exist?(Pathname(destination_root).join("app/javascript/components/.keep")) end |
#create_initializer ⇒ Object
47 48 49 |
# File 'lib/generators/ruact/install/install_generator.rb', line 47 def create_initializer template "initializer.rb.tt", "config/initializers/ruact.rb" end |
#create_launch_files ⇒ Object
Story 14.6 (Epic 14 DoD) — emit Procfile.dev + a foreman-based bin/dev
so the literal bin/dev boots BOTH processes a ruact app needs: Rails
(HTML shell + Flight + server functions) and the Vite dev server
(React/HMR + the bundled ruact plugin). Without these, bin/dev would
start only Rails and the React assets would never be served.
Procfile.dev is guarded (skip if present unless --force) — the app may
already drive its own processes through one. bin/dev, however, is
OWNED by ruact: see install_foreman_launcher. bin/dev is made
executable.
176 177 178 179 180 181 182 |
# File 'lib/generators/ruact/install/install_generator.rb', line 176 def create_launch_files create_guarded_file "Procfile.dev", "Procfile.dev.tt" install_foreman_launcher # Ensure bin/dev is executable whether we just wrote it or it pre-existed # (a skipped, already-foreman launcher should still be runnable). chmod "bin/dev", 0o755, verbose: false if Pathname(destination_root).join("bin/dev").exist? end |
#create_package_json ⇒ Object
Story 14.6 (FR101, Epic 14 DoD) — a fresh ruact app needs a package.json
declaring its JavaScript dependencies (React + Vite + the React Vite
plugin) so the npm install that Story 14.1 runs has something to
resolve and bin/dev's npm run dev (Vite) has a dev script. The
bundled ruact Vite plugin is NOT a package.json dependency — vite.config
imports it by the absolute Ruact.vite_plugin_path and it uses only
node: builtins. Guarded like vite.config.js: an existing package.json
is left untouched (the app may already have one) unless --force.
154 155 156 157 158 159 160 161 162 163 164 |
# File 'lib/generators/ruact/install/install_generator.rb', line 154 def create_package_json package_json_file = Pathname(destination_root).join("package.json") if package_json_file.exist? && ![:force] say_status "skip", "package.json already exists — ensure it has react, react-dom, " \ "vite and @vitejs/plugin-react (re-run with --force to overwrite)", :yellow return end template "package.json.tt", "package.json" end |
#create_server_functions_directory ⇒ Object
Story 8.0a — scaffold the directory the codegen writes into and add the generated artifacts to .gitignore. The TS module is regenerated on every boot from the action and query registries, so it should never be version-controlled; same for the bridge JSON under tmp/cache/.
91 92 93 94 95 |
# File 'lib/generators/ruact/install/install_generator.rb', line 91 def create_server_functions_directory empty_directory "app/javascript/.ruact" create_file "app/javascript/.ruact/.gitkeep" unless File.exist?(Pathname(destination_root).join("app/javascript/.ruact/.gitkeep")) end |
#create_vite_config ⇒ Object
127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 |
# File 'lib/generators/ruact/install/install_generator.rb', line 127 def create_vite_config vite_config_file = Pathname(destination_root).join("vite.config.js") # `--force` must actually regenerate (the message below promises it). The # guard skips the template ONLY when the file exists AND force was not # passed; with --force we fall through to `template`, which overwrites. if vite_config_file.exist? && ![:force] say_status "notice", "vite.config.js already exists — add the plugin manually:", :yellow say " 1. At the top of vite.config.js, add:" say " import ruact from '#{Ruact.vite_plugin_path}';" say " 2. In the plugins array, add: ruact()" say " 3. Set build.rollupOptions.input to '#{Ruact.bootstrap_virtual_id}'" say "" say " Re-run `rails generate ruact:install --force` to overwrite vite.config.js." else template "vite.config.js.tt", "vite.config.js" end end |
#inject_controller_concern ⇒ Object
51 52 53 54 55 56 57 58 59 60 61 62 63 64 |
# File 'lib/generators/ruact/install/install_generator.rb', line 51 def inject_controller_concern controller_file = "app/controllers/application_controller.rb" return unless File.exist?(Pathname(destination_root).join(controller_file)) content = File.read(Pathname(destination_root).join(controller_file)) if content.include?("Ruact::Controller") say_status "skip", "Ruact::Controller already included in ApplicationController", :yellow return end inject_into_file controller_file, "\n include Ruact::Controller\n", after: /class ApplicationController.*\n/ end |
#inject_layout_shell ⇒ Object
66 67 68 69 70 71 72 73 74 75 76 77 78 79 |
# File 'lib/generators/ruact/install/install_generator.rb', line 66 def inject_layout_shell layout_file = "app/views/layouts/application.html.erb" return unless File.exist?(Pathname(destination_root).join(layout_file)) content = File.read(Pathname(destination_root).join(layout_file)) if content.include?("ruact: root") say_status "skip", "Rails RSC root already present in layout", :yellow return end inject_into_file layout_file, "\n <%# ruact: root %>\n <div id=\"root\"></div>\n", before: " </body>" end |
#install_javascript_dependencies ⇒ Object
Story 14.1 (FR101) — install JavaScript dependencies so a fresh app is runnable in one command. Runs LAST among the file-producing actions (after every file is written) so a failure here leaves the generated files in place and reported. Records the outcome in @npm_outcome so show_post_install_message can tell the truth about what happened.
213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 |
# File 'lib/generators/ruact/install/install_generator.rb', line 213 def install_javascript_dependencies if [:skip_npm] @npm_outcome = :skipped say_status "skip", "npm install (--skip-npm) — install JS deps manually before bin/dev", :yellow return end unless npm_on_path? @npm_outcome = :unavailable warn_npm_unavailable return end # Thor's `run` returns false on a non-zero exit (Rails generators set # exit_on_failure? = false, so a failed `npm install` does NOT raise), # and nil under `--pretend` (the command is previewed, not executed). # Branch so the post-install message never claims deps are installed # when npm failed (AC#3) — and a `--pretend` dry run is NOT misreported # as a failure (run_npm_install still prints the previewed command). if run_npm_install || [:pretend] @npm_outcome = [:pretend] ? :pretend : :installed else @npm_outcome = :failed warn_npm_install_failed end end |
#prime_server_functions_codegen ⇒ Object
Invokes ruact:server_functions:generate so a fresh install completes
with the AC8-required empty-but-valid generated module on disk.
Failures (a NameBridge violation, a collision, an unwritable
tmp/cache/ruact/ directory) propagate intentionally — silencing
them via a rescue would let an install finish in a broken state, which
is the bug the Re-run review caught.
123 124 125 |
# File 'lib/generators/ruact/install/install_generator.rb', line 123 def prime_server_functions_codegen rake "ruact:server_functions:generate" end |
#show_post_install_message ⇒ Object
240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 |
# File 'lib/generators/ruact/install/install_generator.rb', line 240 def say "\n#{'=' * 60}\n ruact installed successfully!\n#{'=' * 60}\n" if @npm_outcome == :installed say "JavaScript dependencies are installed." say "" say "Next step:" say " Start your app: bin/dev" else say "JavaScript dependencies are not yet installed." say "" say "Next steps:" say " 1. Install JS dependencies: npm install" say " 2. Start your app: bin/dev" end say "\nThen add <MyComponent /> to any ERB view.\n" say "Note: re-run this generator after updating the ruact gem to refresh" say "the bundled Vite plugin path in vite.config.js." say "" end |