react_on_rails 15.0.0.rc.2 → 16.0.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 (74) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +65 -36
  3. data/CLAUDE.md +90 -0
  4. data/CODING_AGENTS.md +312 -0
  5. data/CONTRIBUTING.md +378 -3
  6. data/Gemfile.lock +2 -1
  7. data/LICENSE.md +16 -4
  8. data/LICENSES/README.md +14 -0
  9. data/REACT-ON-RAILS-PRO-LICENSE.md +129 -0
  10. data/README.md +2 -2
  11. data/TODO.md +135 -0
  12. data/eslint.config.ts +2 -0
  13. data/lib/generators/USAGE +4 -5
  14. data/lib/generators/react_on_rails/base_generator.rb +263 -57
  15. data/lib/generators/react_on_rails/bin/dev +38 -22
  16. data/lib/generators/react_on_rails/dev_tests_generator.rb +1 -0
  17. data/lib/generators/react_on_rails/generator_helper.rb +31 -1
  18. data/lib/generators/react_on_rails/generator_messages.rb +138 -17
  19. data/lib/generators/react_on_rails/install_generator.rb +222 -20
  20. data/lib/generators/react_on_rails/react_no_redux_generator.rb +6 -5
  21. data/lib/generators/react_on_rails/react_with_redux_generator.rb +37 -13
  22. data/lib/generators/react_on_rails/templates/base/base/Procfile.dev +5 -0
  23. data/lib/generators/react_on_rails/templates/base/base/Procfile.dev-prod-assets +8 -0
  24. data/lib/generators/react_on_rails/templates/base/base/Procfile.dev-static-assets +2 -0
  25. data/lib/generators/react_on_rails/templates/base/base/app/javascript/bundles/HelloWorld/components/HelloWorld.jsx +0 -5
  26. data/lib/generators/react_on_rails/templates/base/base/app/javascript/packs/server-bundle.js +1 -8
  27. data/lib/generators/react_on_rails/templates/base/base/app/javascript/src/HelloWorld/ror_components/HelloWorld.client.jsx +21 -0
  28. data/lib/generators/react_on_rails/templates/base/base/app/javascript/src/HelloWorld/ror_components/HelloWorld.module.css +4 -0
  29. data/lib/generators/react_on_rails/templates/base/base/app/javascript/src/HelloWorld/ror_components/HelloWorld.server.jsx +5 -0
  30. data/lib/generators/react_on_rails/templates/base/base/app/views/hello_world/index.html.erb.tt +1 -1
  31. data/lib/generators/react_on_rails/templates/base/base/app/views/layouts/hello_world.html.erb +4 -2
  32. data/lib/generators/react_on_rails/templates/base/base/babel.config.js.tt +5 -2
  33. data/lib/generators/react_on_rails/templates/base/base/bin/dev +46 -0
  34. data/lib/generators/react_on_rails/templates/base/base/config/initializers/react_on_rails.rb.tt +3 -3
  35. data/lib/generators/react_on_rails/templates/base/base/config/shakapacker.yml +76 -7
  36. data/lib/generators/react_on_rails/templates/base/base/config/webpack/commonWebpackConfig.js.tt +1 -1
  37. data/lib/generators/react_on_rails/templates/base/base/config/webpack/development.js.tt +8 -8
  38. data/lib/generators/react_on_rails/templates/base/base/config/webpack/production.js.tt +2 -2
  39. data/lib/generators/react_on_rails/templates/base/base/config/webpack/test.js.tt +2 -2
  40. data/lib/generators/react_on_rails/templates/dev_tests/spec/system/hello_world_spec.rb +0 -2
  41. data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/components/HelloWorld.jsx +0 -6
  42. data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/components/HelloWorld.module.css +4 -0
  43. data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/startup/HelloWorldApp.server.jsx +5 -0
  44. data/lib/react_on_rails/configuration.rb +5 -5
  45. data/lib/react_on_rails/controller.rb +5 -3
  46. data/lib/react_on_rails/dev/file_manager.rb +78 -0
  47. data/lib/react_on_rails/dev/pack_generator.rb +27 -0
  48. data/lib/react_on_rails/dev/process_manager.rb +61 -0
  49. data/lib/react_on_rails/dev/server_manager.rb +330 -0
  50. data/lib/react_on_rails/dev.rb +20 -0
  51. data/lib/react_on_rails/engine.rb +6 -0
  52. data/lib/react_on_rails/git_utils.rb +12 -2
  53. data/lib/react_on_rails/helper.rb +61 -17
  54. data/lib/react_on_rails/packer_utils.rb +4 -18
  55. data/lib/react_on_rails/packs_generator.rb +134 -8
  56. data/lib/react_on_rails/react_component/render_options.rb +2 -2
  57. data/lib/react_on_rails/server_rendering_js_code.rb +0 -1
  58. data/lib/react_on_rails/server_rendering_pool/ruby_embedded_java_script.rb +1 -0
  59. data/lib/react_on_rails/test_helper/webpack_assets_status_checker.rb +1 -0
  60. data/lib/react_on_rails/utils.rb +16 -1
  61. data/lib/react_on_rails/version.rb +1 -1
  62. data/lib/react_on_rails/version_syntax_converter.rb +1 -1
  63. data/lib/react_on_rails.rb +1 -0
  64. data/lib/tasks/generate_packs.rake +20 -0
  65. data/react_on_rails.gemspec +1 -0
  66. metadata +40 -11
  67. data/REACT-ON-RAILS-PRO-LICENSE +0 -95
  68. data/lib/generators/react_on_rails/adapt_for_older_shakapacker_generator.rb +0 -41
  69. data/lib/generators/react_on_rails/bin/dev-static +0 -30
  70. data/lib/generators/react_on_rails/templates/base/base/Procfile.dev-static.tt +0 -9
  71. data/lib/generators/react_on_rails/templates/base/base/Procfile.dev.tt +0 -5
  72. data/lib/generators/react_on_rails/templates/base/base/app/javascript/packs/registration.js.tt +0 -8
  73. /data/lib/generators/react_on_rails/templates/base/base/config/webpack/{webpackConfig.js.tt → generateWebpackConfigs.js.tt} +0 -0
  74. /data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/startup/{HelloWorldApp.jsx → HelloWorldApp.client.jsx} +0 -0
@@ -1,11 +1,41 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "package_json"
4
3
  require "rainbow"
4
+ require "json"
5
5
 
6
6
  module GeneratorHelper
7
7
  def package_json
8
+ # Lazy load package_json gem only when actually needed for dependency management
9
+
10
+ require "package_json" unless defined?(PackageJson)
8
11
  @package_json ||= PackageJson.read
12
+ rescue LoadError
13
+ puts "Warning: package_json gem not available. This is expected before Shakapacker installation."
14
+ puts "Dependencies will be installed using the default package manager after Shakapacker setup."
15
+ nil
16
+ rescue StandardError => e
17
+ puts "Warning: Could not read package.json: #{e.message}"
18
+ puts "This is normal before Shakapacker creates the package.json file."
19
+ nil
20
+ end
21
+
22
+ # Safe wrapper for package_json operations
23
+ def add_npm_dependencies(packages, dev: false)
24
+ pj = package_json
25
+ return false unless pj
26
+
27
+ begin
28
+ if dev
29
+ pj.manager.add(packages, type: :dev)
30
+ else
31
+ pj.manager.add(packages)
32
+ end
33
+ true
34
+ rescue StandardError => e
35
+ puts "Warning: Could not add packages via package_json gem: #{e.message}"
36
+ puts "Will fall back to direct npm commands."
37
+ false
38
+ end
9
39
  end
10
40
 
11
41
  # Takes a relative path from the destination root, such as `.gitignore` or `app/assets/javascripts/application.js`
@@ -38,37 +38,158 @@ module GeneratorMessages
38
38
  @output = []
39
39
  end
40
40
 
41
- def helpful_message_after_installation
41
+ def helpful_message_after_installation(component_name: "HelloWorld")
42
+ process_manager_section = build_process_manager_section
43
+ testing_section = build_testing_section
44
+ package_manager = detect_package_manager
45
+ shakapacker_status = build_shakapacker_status_section
46
+
42
47
  <<~MSG
43
48
 
44
- What to do next:
49
+ ╔════════════════════════════════════════════════════════════════════════╗
50
+ ║ 🎉 React on Rails Successfully Installed! ║
51
+ ╚════════════════════════════════════════════════════════════════════════╝
52
+ #{process_manager_section}#{shakapacker_status}
53
+
54
+ 📋 QUICK START:
55
+ ─────────────────────────────────────────────────────────────────────────
56
+ 1. Install dependencies:
57
+ #{Rainbow("bundle && #{package_manager} install").cyan}
58
+
59
+ 2. Start the app:
60
+ ./bin/dev # HMR (Hot Module Replacement) mode
61
+ ./bin/dev static # Static bundles (no HMR, faster initial load)
62
+ ./bin/dev prod # Production-like mode for testing
63
+ ./bin/dev help # See all available options
64
+
65
+ 3. Visit: #{Rainbow('http://localhost:3000/hello_world').cyan.underline}
66
+ ✨ KEY FEATURES:
67
+ ─────────────────────────────────────────────────────────────────────────
68
+ • Auto-registration enabled - Your layout only needs:
69
+ <%= javascript_pack_tag %>
70
+ <%= stylesheet_pack_tag %>
71
+
72
+ • Server-side rendering - Enabled with prerender option in app/views/hello_world/index.html.erb:
73
+ <%= react_component("#{component_name}", props: @hello_world_props, prerender: true) %>
74
+
75
+ 📚 LEARN MORE:
76
+ ─────────────────────────────────────────────────────────────────────────
77
+ • Documentation: #{Rainbow('https://www.shakacode.com/react-on-rails/docs/').cyan.underline}
78
+ • Webpack customization: #{Rainbow('https://github.com/shakacode/shakapacker#webpack-configuration').cyan.underline}
79
+
80
+ 💡 TIP: Run 'bin/dev help' for development server options and troubleshooting#{testing_section}
81
+ MSG
82
+ end
45
83
 
46
- - See the documentation on https://github.com/shakacode/shakapacker#webpack-configuration
47
- for how to customize the default webpack configuration.
84
+ private
85
+
86
+ def build_process_manager_section
87
+ process_manager = detect_process_manager
88
+ if process_manager
89
+ if process_manager == "overmind"
90
+ "\n📦 #{Rainbow("#{process_manager} detected ✓").green} " \
91
+ "#{Rainbow('(Recommended for easier debugging)').blue}"
92
+ else
93
+ "\n📦 #{Rainbow("#{process_manager} detected ✓").green}"
94
+ end
95
+ else
96
+ <<~INSTALL
97
+
98
+ ⚠️ No process manager detected. Install one:
99
+ #{Rainbow('brew install overmind').yellow.bold} # Recommended (easier debugging)
100
+ #{Rainbow('gem install foreman').yellow} # Alternative
101
+ INSTALL
102
+ end
103
+ end
48
104
 
49
- - Include your webpack assets to your application layout.
105
+ def build_testing_section
106
+ # Check if we have any spec files to determine if testing setup is needed
107
+ has_spec_files = File.exist?("spec/rails_helper.rb") || File.exist?("spec/spec_helper.rb")
50
108
 
51
- <%= javascript_pack_tag 'hello-world-bundle' %>
109
+ return "" if has_spec_files
52
110
 
53
- - To start Rails server run:
111
+ <<~TESTING
54
112
 
55
- ./bin/dev # Running with HMR
56
113
 
57
- or
114
+ 🧪 TESTING SETUP (Optional):
115
+ ─────────────────────────────────────────────────────────────────────────
116
+ For JavaScript testing with asset compilation, add this to your RSpec config:
58
117
 
59
- ./bin/dev-static # Running with statically created bundles, without HMR
118
+ # In spec/rails_helper.rb or spec/spec_helper.rb:
119
+ ReactOnRails::TestHelper.configure_rspec_to_compile_assets(config)
120
+ TESTING
121
+ end
60
122
 
61
- - To server render, change this line app/views/hello_world/index.html.erb to
62
- `prerender: true` to see server rendering (right click on page and select "view source").
123
+ def detect_process_manager
124
+ if system("which overmind > /dev/null 2>&1")
125
+ "overmind"
126
+ elsif system("which foreman > /dev/null 2>&1")
127
+ "foreman"
128
+ end
129
+ end
63
130
 
64
- <%= react_component("HelloWorldApp", props: @hello_world_props, prerender: true) %>
131
+ def build_shakapacker_status_section
132
+ version_warning = check_shakapacker_version_warning
133
+
134
+ if File.exist?(".shakapacker_just_installed")
135
+ base_message = <<~SHAKAPACKER
136
+
137
+ 📦 SHAKAPACKER SETUP:
138
+ ─────────────────────────────────────────────────────────────────────────
139
+ #{Rainbow('✓ Added to Gemfile automatically').green}
140
+ #{Rainbow('✓ Installer ran successfully').green}
141
+ #{Rainbow('✓ Webpack integration configured').green}
142
+ SHAKAPACKER
143
+ base_message + version_warning
144
+ elsif File.exist?("bin/shakapacker") && File.exist?("bin/shakapacker-dev-server")
145
+ "\n📦 #{Rainbow('Shakapacker already configured ✓').green}#{version_warning}"
146
+ else
147
+ "\n📦 #{Rainbow('Shakapacker setup may be incomplete').yellow}#{version_warning}"
148
+ end
149
+ end
65
150
 
66
- Alternative steps to run the app:
151
+ def check_shakapacker_version_warning
152
+ # Try to detect Shakapacker version from Gemfile.lock
153
+ return "" unless File.exist?("Gemfile.lock")
67
154
 
68
- - We recommend using Procfile.dev with foreman, overmind, or a similar program. Alternately, you can run each of the processes listed in Procfile.dev in a separate tab in your terminal.
155
+ gemfile_lock_content = File.read("Gemfile.lock")
156
+ shakapacker_match = gemfile_lock_content.match(/shakapacker \((\d+\.\d+\.\d+)\)/)
69
157
 
70
- - Visit http://localhost:3000/hello_world and see your React On Rails app running!
71
- MSG
158
+ return "" unless shakapacker_match
159
+
160
+ version = shakapacker_match[1]
161
+ major_version = version.split(".").first.to_i
162
+
163
+ if major_version < 8
164
+ <<~WARNING
165
+
166
+ ⚠️ #{Rainbow('IMPORTANT: Upgrade Recommended').yellow.bold}
167
+ ─────────────────────────────────────────────────────────────────────────
168
+ You are using Shakapacker #{version}. React on Rails v15+ works best with
169
+ Shakapacker 8.0+ for optimal Hot Module Replacement and build performance.
170
+
171
+ To upgrade: #{Rainbow('bundle update shakapacker').cyan}
172
+
173
+ Learn more: #{Rainbow('https://github.com/shakacode/shakapacker').cyan.underline}
174
+ WARNING
175
+ else
176
+ ""
177
+ end
178
+ rescue StandardError
179
+ # If version detection fails, don't show a warning to avoid noise
180
+ ""
181
+ end
182
+
183
+ def detect_package_manager
184
+ # Check for lock files to determine package manager
185
+ if File.exist?("yarn.lock")
186
+ "yarn"
187
+ elsif File.exist?("pnpm-lock.yaml")
188
+ "pnpm"
189
+ else
190
+ # Default to npm (Shakapacker 8.x default) - covers package-lock.json and no lockfile
191
+ "npm"
192
+ end
72
193
  end
73
194
  end
74
195
  end
@@ -6,6 +6,7 @@ require_relative "generator_messages"
6
6
 
7
7
  module ReactOnRails
8
8
  module Generators
9
+ # rubocop:disable Metrics/ClassLength
9
10
  class InstallGenerator < Rails::Generators::Base
10
11
  include GeneratorHelper
11
12
 
@@ -16,7 +17,7 @@ module ReactOnRails
16
17
  class_option :redux,
17
18
  type: :boolean,
18
19
  default: false,
19
- desc: "Install Redux gems and Redux version of Hello World Example. Default: false",
20
+ desc: "Install Redux package and Redux version of Hello World Example. Default: false",
20
21
  aliases: "-R"
21
22
 
22
23
  # --ignore-warnings
@@ -25,13 +26,22 @@ module ReactOnRails
25
26
  default: false,
26
27
  desc: "Skip warnings. Default: false"
27
28
 
29
+ # Removed: --skip-shakapacker-install (Shakapacker is now a required dependency)
30
+
28
31
  def run_generators
29
32
  if installation_prerequisites_met? || options.ignore_warnings?
30
33
  invoke_generators
31
34
  add_bin_scripts
32
35
  add_post_install_message
33
36
  else
34
- error = "react_on_rails generator prerequisites not met!"
37
+ error = <<~MSG.strip
38
+ 🚫 React on Rails generator prerequisites not met!
39
+
40
+ Please resolve the issues listed above before continuing.
41
+ All prerequisites must be satisfied for a successful installation.
42
+
43
+ Use --ignore-warnings to bypass checks (not recommended).
44
+ MSG
35
45
  GeneratorMessages.add_error(error)
36
46
  end
37
47
  ensure
@@ -47,37 +57,82 @@ module ReactOnRails
47
57
  end
48
58
 
49
59
  def invoke_generators
60
+ ensure_shakapacker_installed
50
61
  invoke "react_on_rails:base"
51
62
  if options.redux?
52
63
  invoke "react_on_rails:react_with_redux"
53
64
  else
54
65
  invoke "react_on_rails:react_no_redux"
55
66
  end
56
-
57
- invoke "react_on_rails:adapt_for_older_shakapacker" unless using_shakapacker_7_or_above?
58
67
  end
59
68
 
60
69
  # NOTE: other requirements for existing files such as .gitignore or application.
61
70
  # js(.coffee) are not checked by this method, but instead produce warning messages
62
71
  # and allow the build to continue
63
72
  def installation_prerequisites_met?
64
- !(missing_node? || missing_yarn? || ReactOnRails::GitUtils.uncommitted_changes?(GeneratorMessages))
73
+ !(missing_node? || missing_package_manager? || ReactOnRails::GitUtils.uncommitted_changes?(GeneratorMessages))
65
74
  end
66
75
 
67
- def missing_yarn?
68
- return false unless ReactOnRails::Utils.running_on_windows? ? `where yarn`.blank? : `which yarn`.blank?
76
+ def missing_node?
77
+ node_missing = ReactOnRails::Utils.running_on_windows? ? `where node`.blank? : `which node`.blank?
78
+
79
+ if node_missing
80
+ error = <<~MSG.strip
81
+ 🚫 Node.js is required but not found on your system.
69
82
 
70
- error = "yarn is required. Please install it before continuing. https://yarnpkg.com/en/docs/install"
71
- GeneratorMessages.add_error(error)
72
- true
83
+ Please install Node.js before continuing:
84
+ • Download from: https://nodejs.org/en/
85
+ • Recommended: Use a version manager like nvm, fnm, or volta
86
+ • Minimum required version: Node.js 18+
87
+
88
+ After installation, restart your terminal and try again.
89
+ MSG
90
+ GeneratorMessages.add_error(error)
91
+ return true
92
+ end
93
+
94
+ # Check Node.js version if available
95
+ check_node_version
96
+ false
73
97
  end
74
98
 
75
- def missing_node?
76
- return false unless ReactOnRails::Utils.running_on_windows? ? `where node`.blank? : `which node`.blank?
99
+ def check_node_version
100
+ node_version = `node --version 2>/dev/null`.strip
101
+ return if node_version.blank?
77
102
 
78
- error = "** nodejs is required. Please install it before continuing. https://nodejs.org/en/"
79
- GeneratorMessages.add_error(error)
80
- true
103
+ # Extract major version number (e.g., "v18.17.0" -> 18)
104
+ major_version = node_version[/v(\d+)/, 1]&.to_i
105
+ return unless major_version
106
+
107
+ return unless major_version < 18
108
+
109
+ warning = <<~MSG.strip
110
+ ⚠️ Node.js version #{node_version} detected.
111
+
112
+ React on Rails recommends Node.js 18+ for best compatibility.
113
+ You may experience issues with older versions.
114
+
115
+ Consider upgrading: https://nodejs.org/en/
116
+ MSG
117
+ GeneratorMessages.add_warning(warning)
118
+ end
119
+
120
+ def ensure_shakapacker_installed
121
+ return if shakapacker_configured?
122
+
123
+ print_shakapacker_setup_banner
124
+ ensure_shakapacker_in_gemfile
125
+ install_shakapacker
126
+ finalize_shakapacker_setup
127
+ end
128
+
129
+ # Checks whether "shakapacker" is explicitly declared in this project's Gemfile.
130
+ # We only check the Gemfile text, not lockfile or dependencies, because
131
+ # shakapacker might be present as a dependency of react_on_rails but not
132
+ # properly configured for this specific Rails application.
133
+ def shakapacker_in_gemfile?
134
+ gem_name = "shakapacker"
135
+ shakapacker_in_gemfile_text?(gem_name)
81
136
  end
82
137
 
83
138
  def add_bin_scripts
@@ -97,13 +152,160 @@ module ReactOnRails
97
152
  GeneratorMessages.add_info(GeneratorMessages.helpful_message_after_installation)
98
153
  end
99
154
 
100
- def using_shakapacker_7_or_above?
101
- shakapacker_gem = Gem::Specification.find_by_name("shakapacker")
102
- shakapacker_gem.version.segments.first >= 7
103
- rescue Gem::MissingSpecError
104
- # In case using Webpacker
155
+ def shakapacker_loaded_in_process?(gem_name)
156
+ Gem.loaded_specs.key?(gem_name)
157
+ end
158
+
159
+ def shakapacker_in_lockfile?(gem_name)
160
+ gemfile = ENV["BUNDLE_GEMFILE"] || "Gemfile"
161
+ lockfile = File.join(File.dirname(gemfile), "Gemfile.lock")
162
+
163
+ File.file?(lockfile) && File.foreach(lockfile).any? { |l| l.match?(/^\s{4}#{Regexp.escape(gem_name)}\s\(/) }
164
+ end
165
+
166
+ def shakapacker_in_bundler_specs?(gem_name)
167
+ require "bundler"
168
+ Bundler.load.specs.any? { |s| s.name == gem_name }
169
+ rescue StandardError
170
+ false
171
+ end
172
+
173
+ def shakapacker_in_gemfile_text?(gem_name)
174
+ gemfile = ENV["BUNDLE_GEMFILE"] || "Gemfile"
175
+
176
+ File.file?(gemfile) &&
177
+ File.foreach(gemfile).any? { |l| l.match?(/^\s*gem\s+['"]#{Regexp.escape(gem_name)}['"]/) }
178
+ end
179
+
180
+ def cli_exists?(command)
181
+ system("which #{command} > /dev/null 2>&1")
182
+ end
183
+
184
+ def shakapacker_binaries_exist?
185
+ File.exist?("bin/shakapacker") && File.exist?("bin/shakapacker-dev-server")
186
+ end
187
+
188
+ def shakapacker_configured?
189
+ # Check for essential shakapacker configuration files and binaries
190
+ shakapacker_binaries_exist? &&
191
+ File.exist?("config/shakapacker.yml") &&
192
+ File.exist?("config/webpack/webpack.config.js")
193
+ end
194
+
195
+ def print_shakapacker_setup_banner
196
+ puts Rainbow("\n#{'=' * 80}").cyan
197
+ puts Rainbow("🔧 SHAKAPACKER SETUP").cyan.bold
198
+ puts Rainbow("=" * 80).cyan
199
+ end
200
+
201
+ def ensure_shakapacker_in_gemfile
202
+ return if shakapacker_in_gemfile?
203
+
204
+ puts Rainbow("📝 Adding Shakapacker to Gemfile...").yellow
205
+ success = system("bundle add shakapacker --strict")
206
+ return if success
207
+
208
+ handle_shakapacker_gemfile_error
209
+ end
210
+
211
+ def install_shakapacker
212
+ puts Rainbow("⚙️ Installing Shakapacker (required for webpack integration)...").yellow
213
+
214
+ # First run bundle install to make shakapacker available
215
+ puts Rainbow("📦 Running bundle install...").yellow
216
+ bundle_success = system("bundle install")
217
+ unless bundle_success
218
+ handle_shakapacker_install_error
219
+ return
220
+ end
221
+
222
+ # Then run the shakapacker installer
223
+ success = system("bundle exec rails shakapacker:install")
224
+ return if success
225
+
226
+ handle_shakapacker_install_error
227
+ end
228
+
229
+ def finalize_shakapacker_setup
230
+ puts Rainbow("✅ Shakapacker installed successfully!").green
231
+ puts Rainbow("=" * 80).cyan
232
+ puts Rainbow("🚀 CONTINUING WITH REACT ON RAILS SETUP").cyan.bold
233
+ puts "#{Rainbow('=' * 80).cyan}\n"
234
+
235
+ # Create marker file so base generator can avoid copying shakapacker.yml
236
+ File.write(".shakapacker_just_installed", "")
237
+ end
238
+
239
+ def handle_shakapacker_gemfile_error
240
+ error = <<~MSG.strip
241
+ 🚫 Failed to add Shakapacker to your Gemfile.
242
+
243
+ This could be due to:
244
+ • Bundle installation issues
245
+ • Network connectivity problems
246
+ • Gemfile permissions
247
+
248
+ Please try manually:
249
+ bundle add shakapacker --strict
250
+
251
+ Then re-run: rails generate react_on_rails:install
252
+ MSG
253
+ GeneratorMessages.add_error(error)
254
+ raise Thor::Error, error unless options.ignore_warnings?
255
+ end
256
+
257
+ def handle_shakapacker_install_error
258
+ error = <<~MSG.strip
259
+ 🚫 Failed to install Shakapacker automatically.
260
+
261
+ This could be due to:
262
+ • Missing Node.js or npm/yarn
263
+ • Network connectivity issues
264
+ • Incomplete bundle installation
265
+ • Missing write permissions
266
+
267
+ Troubleshooting steps:
268
+ 1. Ensure Node.js is installed: node --version
269
+ 2. Run: bundle install
270
+ 3. Try manually: bundle exec rails shakapacker:install
271
+ 4. Check for error output above
272
+ 5. Re-run: rails generate react_on_rails:install
273
+
274
+ Need help? Visit: https://github.com/shakacode/shakapacker/blob/main/docs/installation.md
275
+ MSG
276
+ GeneratorMessages.add_error(error)
277
+ raise Thor::Error, error unless options.ignore_warnings?
278
+ end
279
+
280
+ def missing_package_manager?
281
+ package_managers = %w[npm pnpm yarn bun]
282
+ missing = package_managers.none? { |pm| cli_exists?(pm) }
283
+
284
+ if missing
285
+ error = <<~MSG.strip
286
+ 🚫 No JavaScript package manager found on your system.
287
+
288
+ React on Rails requires a JavaScript package manager to install dependencies.
289
+ Please install one of the following:
290
+
291
+ • npm: Usually comes with Node.js (https://nodejs.org/en/)
292
+ • yarn: npm install -g yarn (https://yarnpkg.com/)
293
+ • pnpm: npm install -g pnpm (https://pnpm.io/)
294
+ • bun: Install from https://bun.sh/
295
+
296
+ After installation, restart your terminal and try again.
297
+ MSG
298
+ GeneratorMessages.add_error(error)
299
+ return true
300
+ end
301
+
105
302
  false
106
303
  end
304
+
305
+ # Removed: Shakapacker auto-installation logic (now explicit dependency)
306
+
307
+ # Removed: Shakapacker 8+ is now required as explicit dependency
107
308
  end
309
+ # rubocop:enable Metrics/ClassLength
108
310
  end
109
311
  end
@@ -7,24 +7,25 @@ module ReactOnRails
7
7
  module Generators
8
8
  class ReactNoReduxGenerator < Rails::Generators::Base
9
9
  include GeneratorHelper
10
+
10
11
  Rails::Generators.hide_namespace(namespace)
11
12
  source_root(File.expand_path("templates", __dir__))
12
13
 
13
14
  def copy_base_files
14
15
  base_js_path = "base/base"
15
- base_files = %w[app/javascript/bundles/HelloWorld/components/HelloWorld.jsx]
16
+ base_files = %w[app/javascript/src/HelloWorld/ror_components/HelloWorld.client.jsx
17
+ app/javascript/src/HelloWorld/ror_components/HelloWorld.server.jsx
18
+ app/javascript/src/HelloWorld/ror_components/HelloWorld.module.css]
16
19
  base_files.each { |file| copy_file("#{base_js_path}/#{file}", file) }
17
20
  end
18
21
 
19
22
  def create_appropriate_templates
20
23
  base_path = "base/base"
21
24
  config = {
22
- component_name: "HelloWorld",
23
- app_relative_path: "../bundles/HelloWorld/components/HelloWorld"
25
+ component_name: "HelloWorld"
24
26
  }
25
27
 
26
- template("#{base_path}/app/javascript/packs/registration.js.tt",
27
- "app/javascript/packs/hello-world-bundle.js", config)
28
+ # Only create the view template - no manual bundle needed for auto registration
28
29
  template("#{base_path}/app/views/hello_world/index.html.erb.tt",
29
30
  "app/views/hello_world/index.html.erb", config)
30
31
  end
@@ -9,14 +9,30 @@ module ReactOnRails
9
9
  source_root(File.expand_path("templates", __dir__))
10
10
 
11
11
  def create_redux_directories
12
- dirs = %w[actions constants containers reducers store startup]
13
- dirs.each { |name| empty_directory("app/javascript/bundles/HelloWorld/#{name}") }
12
+ # Create auto-registration directory structure for Redux
13
+ empty_directory("app/javascript/src/HelloWorldApp/ror_components")
14
+
15
+ # Create Redux support directories within the component directory
16
+ dirs = %w[actions constants containers reducers store components]
17
+ dirs.each { |name| empty_directory("app/javascript/src/HelloWorldApp/#{name}") }
14
18
  end
15
19
 
16
20
  def copy_base_files
17
21
  base_js_path = "redux/base"
18
- base_files = %w[app/javascript/bundles/HelloWorld/components/HelloWorld.jsx]
19
- base_files.each { |file| copy_file("#{base_js_path}/#{file}", file) }
22
+
23
+ # Copy Redux-connected component to auto-registration structure
24
+ copy_file("#{base_js_path}/app/javascript/bundles/HelloWorld/startup/HelloWorldApp.client.jsx",
25
+ "app/javascript/src/HelloWorldApp/ror_components/HelloWorldApp.client.jsx")
26
+ copy_file("#{base_js_path}/app/javascript/bundles/HelloWorld/startup/HelloWorldApp.server.jsx",
27
+ "app/javascript/src/HelloWorldApp/ror_components/HelloWorldApp.server.jsx")
28
+ copy_file("#{base_js_path}/app/javascript/bundles/HelloWorld/components/HelloWorld.module.css",
29
+ "app/javascript/src/HelloWorldApp/components/HelloWorld.module.css")
30
+
31
+ # Update import paths in client component
32
+ ror_client_file = "app/javascript/src/HelloWorldApp/ror_components/HelloWorldApp.client.jsx"
33
+ gsub_file(ror_client_file, "../store/helloWorldStore", "../store/helloWorldStore")
34
+ gsub_file(ror_client_file, "../containers/HelloWorldContainer",
35
+ "../containers/HelloWorldContainer")
20
36
  end
21
37
 
22
38
  def copy_base_redux_files
@@ -26,26 +42,34 @@ module ReactOnRails
26
42
  constants/helloWorldConstants.js
27
43
  reducers/helloWorldReducer.js
28
44
  store/helloWorldStore.js
29
- startup/HelloWorldApp.jsx].each do |file|
45
+ components/HelloWorld.jsx].each do |file|
30
46
  copy_file("#{base_hello_world_path}/#{file}",
31
- "app/javascript/bundles/HelloWorld/#{file}")
47
+ "app/javascript/src/HelloWorldApp/#{file}")
32
48
  end
33
49
  end
34
50
 
35
51
  def create_appropriate_templates
36
52
  base_path = "base/base"
37
- base_js_path = "#{base_path}/app/javascript"
38
53
  config = {
39
- component_name: "HelloWorldApp",
40
- app_relative_path: "../bundles/HelloWorld/startup/HelloWorldApp"
54
+ component_name: "HelloWorldApp"
41
55
  }
42
56
 
43
- template("#{base_js_path}/packs/registration.js.tt", "app/javascript/packs/hello-world-bundle.js", config)
44
- template("#{base_path}/app/views/hello_world/index.html.erb.tt", "app/views/hello_world/index.html.erb", config)
57
+ # Only create the view template - no manual bundle needed for auto registration
58
+ template("#{base_path}/app/views/hello_world/index.html.erb.tt",
59
+ "app/views/hello_world/index.html.erb", config)
60
+ end
61
+
62
+ def add_redux_npm_dependencies
63
+ run "npm install redux react-redux"
45
64
  end
46
65
 
47
- def add_redux_yarn_dependencies
48
- run "yarn add redux react-redux"
66
+ def add_redux_specific_messages
67
+ # Override the generic messages with Redux-specific instructions
68
+ require_relative "generator_messages"
69
+ GeneratorMessages.output.clear
70
+ GeneratorMessages.add_info(
71
+ GeneratorMessages.helpful_message_after_installation(component_name: "HelloWorldApp")
72
+ )
49
73
  end
50
74
  end
51
75
  end
@@ -0,0 +1,5 @@
1
+ # Procfile for development using HMR
2
+ # You can run these commands in separate shells
3
+ rails: bundle exec rails s -p 3000
4
+ wp-client: WEBPACK_SERVE=true bin/shakapacker-dev-server
5
+ wp-server: SERVER_BUNDLE_ONLY=yes bin/shakapacker --watch
@@ -0,0 +1,8 @@
1
+ # Procfile for development with production assets
2
+ # Uses production-optimized, precompiled assets with development environment
3
+ # Uncomment additional processes as needed for your app
4
+
5
+ rails: bundle exec rails s -p 3001
6
+ # sidekiq: bundle exec sidekiq -C config/sidekiq.yml
7
+ # redis: redis-server
8
+ # mailcatcher: mailcatcher --foreground
@@ -0,0 +1,2 @@
1
+ web: bin/rails server -p 3000
2
+ js: bin/shakapacker --watch
@@ -1,4 +1,3 @@
1
- import PropTypes from 'prop-types';
2
1
  import React, { useState } from 'react';
3
2
  import * as style from './HelloWorld.module.css';
4
3
 
@@ -19,8 +18,4 @@ const HelloWorld = (props) => {
19
18
  );
20
19
  };
21
20
 
22
- HelloWorld.propTypes = {
23
- name: PropTypes.string.isRequired, // this is passed from the Rails view
24
- };
25
-
26
21
  export default HelloWorld;