react_on_rails 16.0.0 → 16.0.1.rc.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 (41) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +117 -77
  3. data/CLAUDE.md +14 -2
  4. data/Gemfile.lock +1 -1
  5. data/LICENSE.md +15 -1
  6. data/README.md +68 -18
  7. data/eslint.config.ts +3 -0
  8. data/knip.ts +20 -9
  9. data/lib/generators/react_on_rails/USAGE +65 -0
  10. data/lib/generators/react_on_rails/base_generator.rb +7 -7
  11. data/lib/generators/react_on_rails/generator_helper.rb +4 -0
  12. data/lib/generators/react_on_rails/generator_messages.rb +2 -2
  13. data/lib/generators/react_on_rails/install_generator.rb +115 -7
  14. data/lib/generators/react_on_rails/react_no_redux_generator.rb +16 -4
  15. data/lib/generators/react_on_rails/react_with_redux_generator.rb +83 -14
  16. data/lib/generators/react_on_rails/templates/base/base/app/javascript/src/HelloWorld/ror_components/HelloWorld.client.tsx +25 -0
  17. data/lib/generators/react_on_rails/templates/base/base/app/javascript/src/HelloWorld/ror_components/HelloWorld.server.tsx +5 -0
  18. data/lib/generators/react_on_rails/templates/base/base/bin/dev +12 -24
  19. data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/actions/helloWorldActionCreators.ts +18 -0
  20. data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/components/HelloWorld.tsx +24 -0
  21. data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/constants/helloWorldConstants.ts +6 -0
  22. data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/containers/HelloWorldContainer.ts +20 -0
  23. data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/reducers/helloWorldReducer.ts +22 -0
  24. data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/startup/HelloWorldApp.client.tsx +23 -0
  25. data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/startup/HelloWorldApp.server.tsx +5 -0
  26. data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/store/helloWorldStore.ts +18 -0
  27. data/lib/react_on_rails/configuration.rb +10 -6
  28. data/lib/react_on_rails/dev/server_manager.rb +185 -28
  29. data/lib/react_on_rails/doctor.rb +1149 -0
  30. data/lib/react_on_rails/helper.rb +9 -78
  31. data/lib/react_on_rails/pro/NOTICE +21 -0
  32. data/lib/react_on_rails/pro/helper.rb +122 -0
  33. data/lib/react_on_rails/pro/utils.rb +53 -0
  34. data/lib/react_on_rails/react_component/render_options.rb +6 -2
  35. data/lib/react_on_rails/system_checker.rb +659 -0
  36. data/lib/react_on_rails/version.rb +1 -1
  37. data/lib/tasks/doctor.rake +51 -0
  38. data/lib/tasks/generate_packs.rake +127 -4
  39. data/package-lock.json +11984 -0
  40. metadata +21 -6
  41. data/lib/generators/react_on_rails/bin/dev +0 -46
@@ -8,16 +8,16 @@ module ReactOnRails
8
8
  module Dev
9
9
  class ServerManager
10
10
  class << self
11
- def start(mode = :development, procfile = nil, verbose: false)
11
+ def start(mode = :development, procfile = nil, verbose: false, route: nil, rails_env: nil)
12
12
  case mode
13
13
  when :production_like
14
- run_production_like(_verbose: verbose)
14
+ run_production_like(_verbose: verbose, route: route, rails_env: rails_env)
15
15
  when :static
16
16
  procfile ||= "Procfile.dev-static-assets"
17
- run_static_development(procfile, verbose: verbose)
17
+ run_static_development(procfile, verbose: verbose, route: route)
18
18
  when :development, :hmr
19
19
  procfile ||= "Procfile.dev"
20
- run_development(procfile, verbose: verbose)
20
+ run_development(procfile, verbose: verbose, route: route)
21
21
  else
22
22
  raise ArgumentError, "Unknown mode: #{mode}"
23
23
  end
@@ -116,6 +116,50 @@ module ReactOnRails
116
116
  puts help_troubleshooting
117
117
  end
118
118
 
119
+ def run_from_command_line(args = ARGV)
120
+ require "optparse"
121
+
122
+ options = { route: nil, rails_env: nil }
123
+
124
+ OptionParser.new do |opts|
125
+ opts.banner = "Usage: dev [command] [options]"
126
+
127
+ opts.on("--route ROUTE", "Specify the route to display in URLs (default: root)") do |route|
128
+ options[:route] = route
129
+ end
130
+
131
+ opts.on("--rails-env ENV", "Override RAILS_ENV for assets:precompile step only (prod mode only)") do |env|
132
+ options[:rails_env] = env
133
+ end
134
+
135
+ opts.on("-h", "--help", "Prints this help") do
136
+ show_help
137
+ exit
138
+ end
139
+ end.parse!(args)
140
+
141
+ # Get the command (anything that's not parsed as an option)
142
+ command = args[0]
143
+
144
+ # Main execution
145
+ case command
146
+ when "production-assets", "prod"
147
+ start(:production_like, nil, verbose: false, route: options[:route], rails_env: options[:rails_env])
148
+ when "static"
149
+ start(:static, "Procfile.dev-static-assets", verbose: false, route: options[:route])
150
+ when "kill"
151
+ kill_processes
152
+ when "help", "--help", "-h"
153
+ show_help
154
+ when "hmr", nil
155
+ start(:development, "Procfile.dev", verbose: false, route: options[:route])
156
+ else
157
+ puts "Unknown argument: #{command}"
158
+ puts "Run 'dev help' for usage information"
159
+ exit 1
160
+ end
161
+ end
162
+
119
163
  private
120
164
 
121
165
  def help_usage
@@ -142,12 +186,21 @@ module ReactOnRails
142
186
  end
143
187
  # rubocop:enable Metrics/AbcSize
144
188
 
189
+ # rubocop:disable Metrics/AbcSize
145
190
  def help_options
146
191
  <<~OPTIONS
147
192
  #{Rainbow('⚙️ OPTIONS:').cyan.bold}
148
- #{Rainbow('--verbose, -v').green.bold} #{Rainbow('Enable verbose output for pack generation').white}
193
+ #{Rainbow('--route ROUTE').green.bold} #{Rainbow('Specify route to display in URLs (default: root)').white}
194
+ #{Rainbow('--rails-env ENV').green.bold} #{Rainbow('Override RAILS_ENV for assets:precompile step only (prod mode only)').white}
195
+ #{Rainbow('--verbose, -v').green.bold} #{Rainbow('Enable verbose output for pack generation').white}
196
+
197
+ #{Rainbow('📝 EXAMPLES:').cyan.bold}
198
+ #{Rainbow('bin/dev prod').green.bold} #{Rainbow('# NODE_ENV=production, RAILS_ENV=development').white}
199
+ #{Rainbow('bin/dev prod --rails-env=production').green.bold} #{Rainbow('# NODE_ENV=production, RAILS_ENV=production').white}
200
+ #{Rainbow('bin/dev prod --route=dashboard').green.bold} #{Rainbow('# Custom route in URLs').white}
149
201
  OPTIONS
150
202
  end
203
+ # rubocop:enable Metrics/AbcSize
151
204
 
152
205
  def help_customization
153
206
  <<~CUSTOMIZATION
@@ -172,7 +225,7 @@ module ReactOnRails
172
225
  #{Rainbow('•').yellow} #{Rainbow('Source maps for debugging').white}
173
226
  #{Rainbow('•').yellow} #{Rainbow('May have Flash of Unstyled Content (FOUC)').white}
174
227
  #{Rainbow('•').yellow} #{Rainbow('Fast recompilation').white}
175
- #{Rainbow('•').yellow} #{Rainbow('Access at:').white} #{Rainbow('http://localhost:3000/hello_world').cyan.underline}
228
+ #{Rainbow('•').yellow} #{Rainbow('Access at:').white} #{Rainbow('http://localhost:3000/<route>').cyan.underline}
176
229
 
177
230
  #{Rainbow('📦 Static development mode').cyan.bold} - #{Rainbow('Procfile.dev-static-assets').green}:
178
231
  #{Rainbow('•').yellow} #{Rainbow('No HMR (static assets with auto-recompilation)').white}
@@ -181,24 +234,26 @@ module ReactOnRails
181
234
  #{Rainbow('•').yellow} #{Rainbow('CSS extracted to separate files (no FOUC)').white}
182
235
  #{Rainbow('•').yellow} #{Rainbow('Development environment (faster builds than production)').white}
183
236
  #{Rainbow('•').yellow} #{Rainbow('Source maps for debugging').white}
184
- #{Rainbow('•').yellow} #{Rainbow('Access at:').white} #{Rainbow('http://localhost:3000/hello_world').cyan.underline}
237
+ #{Rainbow('•').yellow} #{Rainbow('Access at:').white} #{Rainbow('http://localhost:3000/<route>').cyan.underline}
185
238
 
186
239
  #{Rainbow('🏭 Production-assets mode').cyan.bold} - #{Rainbow('Procfile.dev-prod-assets').green}:
187
240
  #{Rainbow('•').yellow} #{Rainbow('React on Rails pack generation before Procfile start').white}
188
- #{Rainbow('•').yellow} #{Rainbow('Asset precompilation with production optimizations').white}
189
- #{Rainbow('•').yellow} #{Rainbow('Optimized, minified bundles').white}
190
- #{Rainbow('•').yellow} #{Rainbow('Extracted CSS files (no FOUC)').white}
241
+ #{Rainbow('•').yellow} #{Rainbow('Asset precompilation with NODE_ENV=production (webpack optimizations)').white}
242
+ #{Rainbow('•').yellow} #{Rainbow('RAILS_ENV=development by default for assets:precompile (avoids credentials)').white}
243
+ #{Rainbow('•').yellow} #{Rainbow('Use --rails-env=production for assets:precompile only (not server processes)').white}
244
+ #{Rainbow('•').yellow} #{Rainbow('Server processes controlled by Procfile.dev-prod-assets environment').white}
245
+ #{Rainbow('•').yellow} #{Rainbow('Optimized, minified bundles with CSS extraction').white}
191
246
  #{Rainbow('•').yellow} #{Rainbow('No HMR (static assets)').white}
192
- #{Rainbow('•').yellow} #{Rainbow('Slower recompilation').white}
193
- #{Rainbow('•').yellow} #{Rainbow('Access at:').white} #{Rainbow('http://localhost:3001/hello_world').cyan.underline}
247
+ #{Rainbow('•').yellow} #{Rainbow('Access at:').white} #{Rainbow('http://localhost:3001/<route>').cyan.underline}
194
248
  MODES
195
249
  end
196
250
  # rubocop:enable Metrics/AbcSize
197
251
 
198
- def run_production_like(_verbose: false)
252
+ # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
253
+ def run_production_like(_verbose: false, route: nil, rails_env: nil)
199
254
  procfile = "Procfile.dev-prod-assets"
200
255
 
201
- print_procfile_info(procfile)
256
+ print_procfile_info(procfile, route: route)
202
257
  print_server_info(
203
258
  "🏭 Starting production-like development server...",
204
259
  [
@@ -208,25 +263,124 @@ module ReactOnRails
208
263
  "No HMR (Hot Module Replacement)",
209
264
  "CSS extracted to separate files (no FOUC)"
210
265
  ],
211
- 3001
266
+ 3001,
267
+ route: route
212
268
  )
213
269
 
214
- # Precompile assets in production mode (includes pack generation automatically)
215
- puts "🔨 Precompiling assets..."
216
- success = system "RAILS_ENV=production NODE_ENV=production bundle exec rails assets:precompile"
270
+ # Precompile assets with production webpack optimizations (includes pack generation automatically)
271
+ env = { "NODE_ENV" => "production" }
272
+
273
+ # Validate and sanitize rails_env to prevent shell injection
274
+ if rails_env
275
+ unless rails_env.match?(/\A[a-z0-9_]+\z/i)
276
+ puts "❌ Invalid rails_env: '#{rails_env}'. Must contain only letters, numbers, and underscores."
277
+ exit 1
278
+ end
279
+ env["RAILS_ENV"] = rails_env
280
+ end
281
+
282
+ argv = ["bundle", "exec", "rails", "assets:precompile"]
283
+
284
+ puts "🔨 Precompiling assets with production webpack optimizations..."
285
+ puts ""
286
+
287
+ puts Rainbow("ℹ️ Asset Precompilation Environment:").blue
288
+ puts " • NODE_ENV=production → Webpack optimizations (minification, compression)"
289
+ if rails_env
290
+ puts " • RAILS_ENV=#{rails_env} → Custom Rails environment for assets:precompile only"
291
+ puts " • Note: RAILS_ENV=production requires credentials, database setup, etc."
292
+ puts " • Server processes will use environment from Procfile.dev-prod-assets"
293
+ else
294
+ puts " • RAILS_ENV=development → Simpler Rails setup (no credentials needed)"
295
+ puts " • Use --rails-env=production for assets:precompile step only"
296
+ puts " • Server processes will use environment from Procfile.dev-prod-assets"
297
+ puts " • Gets production webpack bundles without production Rails complexity"
298
+ end
299
+ puts ""
300
+
301
+ env_display = env.map { |k, v| "#{k}=#{v}" }.join(" ")
302
+ puts "#{Rainbow('💻 Running:').blue} #{env_display} #{argv.join(' ')}"
303
+ puts ""
217
304
 
218
- if success
305
+ # Capture both stdout and stderr
306
+ require "open3"
307
+ stdout, stderr, status = Open3.capture3(env, *argv)
308
+
309
+ if status.success?
219
310
  puts "✅ Assets precompiled successfully"
220
311
  ProcessManager.ensure_procfile(procfile)
221
312
  ProcessManager.run_with_process_manager(procfile)
222
313
  else
223
314
  puts "❌ Asset precompilation failed"
315
+ puts ""
316
+
317
+ # Combine and display all output
318
+ all_output = []
319
+ all_output << stdout unless stdout.empty?
320
+ all_output << stderr unless stderr.empty?
321
+
322
+ unless all_output.empty?
323
+ puts Rainbow("📋 Full Command Output:").red.bold
324
+ puts Rainbow("─" * 60).red
325
+ all_output.each { |output| puts output }
326
+ puts Rainbow("─" * 60).red
327
+ puts ""
328
+ end
329
+
330
+ puts Rainbow("🛠️ To debug this issue:").yellow.bold
331
+ command_display = "#{env_display} #{argv.join(' ')}"
332
+ puts "#{Rainbow('1.').cyan} #{Rainbow('Run the command separately to see detailed output:').white}"
333
+ puts " #{Rainbow(command_display).cyan}"
334
+ puts ""
335
+ puts "#{Rainbow('2.').cyan} #{Rainbow('Add --trace for full stack trace:').white}"
336
+ puts " #{Rainbow("#{command_display} --trace").cyan}"
337
+ puts ""
338
+ puts "#{Rainbow('3.').cyan} #{Rainbow('Or try with development webpack (faster, less optimized):').white}"
339
+ puts " #{Rainbow('NODE_ENV=development bundle exec rails assets:precompile').cyan}"
340
+ puts ""
341
+
342
+ puts Rainbow("💡 Common fixes:").yellow.bold
343
+
344
+ # Provide specific guidance based on error content
345
+ error_content = "#{stderr} #{stdout}".downcase
346
+
347
+ if error_content.include?("secret_key_base")
348
+ puts "#{Rainbow('•').yellow} #{Rainbow('Missing secret_key_base:').white.bold} " \
349
+ "Run #{Rainbow('bin/rails credentials:edit').cyan}"
350
+ end
351
+
352
+ if error_content.include?("database") || error_content.include?("relation") ||
353
+ error_content.include?("table")
354
+ puts "#{Rainbow('•').yellow} #{Rainbow('Database issues:').white.bold} " \
355
+ "Run #{Rainbow('bin/rails db:create db:migrate').cyan}"
356
+ end
357
+
358
+ if error_content.include?("gem") || error_content.include?("bundle") || error_content.include?("load error")
359
+ puts "#{Rainbow('•').yellow} #{Rainbow('Missing dependencies:').white.bold} " \
360
+ "Run #{Rainbow('bundle install && npm install').cyan}"
361
+ end
362
+
363
+ if error_content.include?("webpack") || error_content.include?("module") ||
364
+ error_content.include?("compilation")
365
+ puts "#{Rainbow('•').yellow} #{Rainbow('Webpack compilation:').white.bold} " \
366
+ "Check JavaScript/webpack errors above"
367
+ end
368
+
369
+ # Always show these general options
370
+ puts "#{Rainbow('•').yellow} #{Rainbow('Environment config:').white} " \
371
+ "Check #{Rainbow('config/environments/production.rb').cyan}"
372
+
373
+ puts ""
374
+ puts Rainbow("ℹ️ Alternative for development:").blue
375
+ puts " #{Rainbow('bin/dev static').green} # Static assets without production optimizations"
376
+ puts ""
224
377
  exit 1
225
378
  end
226
379
  end
380
+ # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
227
381
 
228
- def run_static_development(procfile, verbose: false)
229
- print_procfile_info(procfile)
382
+ def run_static_development(procfile, verbose: false, route: nil)
383
+ print_procfile_info(procfile, route: route)
230
384
  print_server_info(
231
385
  "⚡ Starting development server with static assets...",
232
386
  [
@@ -235,7 +389,8 @@ module ReactOnRails
235
389
  "CSS extracted to separate files (no FOUC)",
236
390
  "Development environment (source maps, faster builds)",
237
391
  "Auto-recompiles on file changes"
238
- ]
392
+ ],
393
+ route: route
239
394
  )
240
395
 
241
396
  PackGenerator.generate(verbose: verbose)
@@ -243,25 +398,27 @@ module ReactOnRails
243
398
  ProcessManager.run_with_process_manager(procfile)
244
399
  end
245
400
 
246
- def run_development(procfile, verbose: false)
247
- print_procfile_info(procfile)
401
+ def run_development(procfile, verbose: false, route: nil)
402
+ print_procfile_info(procfile, route: route)
248
403
  PackGenerator.generate(verbose: verbose)
249
404
  ProcessManager.ensure_procfile(procfile)
250
405
  ProcessManager.run_with_process_manager(procfile)
251
406
  end
252
407
 
253
- def print_server_info(title, features, port = 3000)
408
+ def print_server_info(title, features, port = 3000, route: nil)
254
409
  puts title
255
410
  features.each { |feature| puts " - #{feature}" }
256
411
  puts ""
257
412
  puts ""
258
- puts "💡 Access at: #{Rainbow("http://localhost:#{port}/hello_world").cyan.underline}"
413
+ url = route ? "http://localhost:#{port}/#{route}" : "http://localhost:#{port}"
414
+ puts "💡 Access at: #{Rainbow(url).cyan.underline}"
259
415
  puts ""
260
416
  end
261
417
 
262
- def print_procfile_info(procfile)
418
+ def print_procfile_info(procfile, route: nil)
263
419
  port = procfile_port(procfile)
264
420
  box_width = 60
421
+ url = route ? "http://localhost:#{port}/#{route}" : "http://localhost:#{port}"
265
422
 
266
423
  puts ""
267
424
  puts box_border(box_width)
@@ -269,7 +426,7 @@ module ReactOnRails
269
426
  puts format_box_line("📋 Using Procfile: #{procfile}", box_width)
270
427
  puts format_box_line("🔧 Customize this file for your app's needs", box_width)
271
428
  puts box_empty_line(box_width)
272
- puts format_box_line("💡 Access at: #{Rainbow("http://localhost:#{port}/hello_world").cyan.underline}",
429
+ puts format_box_line("💡 Access at: #{Rainbow(url).cyan.underline}",
273
430
  box_width)
274
431
  puts box_empty_line(box_width)
275
432
  puts box_bottom(box_width)