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
data/TODO.md ADDED
@@ -0,0 +1,135 @@
1
+ # React on Rails TODO
2
+
3
+ ## Generator Improvements
4
+
5
+ ### HelloWorld Component Structure
6
+
7
+ - [x] Fix bad import in HelloWorld.server.jsx (server importing from client)
8
+ - [x] Simplify to single HelloWorld.jsx file with documentation
9
+ - [ ] **Consider alternative approach**: Create second example component showing client/server split
10
+ - Add `client_server_different.jsx` in sibling directory like `/examples/` or `/advanced/`
11
+ - Show real-world use case (React Router, styled-components, etc.)
12
+ - Keep HelloWorld simple for beginners
13
+
14
+ ### File Organization
15
+
16
+ - [x] **Improved ror_components directory structure**
17
+ - Documentation now suggests moving shared components to `../components/` directory
18
+ - Keeps ror_components clean for React on Rails specific entry points
19
+ - Recommended structure:
20
+ ```
21
+ src/HelloWorld/
22
+ ├── components/
23
+ │ └── HelloWorld.jsx # Shared component implementation
24
+ └── ror_components/
25
+ ├── HelloWorld.client.jsx # Client entry point (when needed)
26
+ └── HelloWorld.server.jsx # Server entry point (when needed)
27
+ ```
28
+ - [ ] **Consider adding generator flag to create this structure automatically**
29
+
30
+ ### Generator Options
31
+
32
+ - [ ] **Add generator flags for different patterns**
33
+ - `--simple` (default): Single component file
34
+ - `--split`: Generate client/server split example
35
+ - `--example-name`: Customize component name beyond HelloWorld
36
+
37
+ ## Documentation Improvements
38
+
39
+ ### Component Architecture Guide
40
+
41
+ - [ ] **Add comprehensive docs on client/server patterns**
42
+ - When to use single vs split files
43
+ - Common libraries requiring server setup (React Router, styled-components, Apollo)
44
+ - Migration path from simple to split architecture
45
+ - Auto-registration behavior explanation
46
+
47
+ ### Code Comments
48
+
49
+ - [x] Add inline documentation to HelloWorld.jsx explaining split pattern
50
+ - [ ] Add JSDoc comments for better IDE support
51
+ - [ ] Include links to relevant documentation sections
52
+
53
+ ## Testing Infrastructure
54
+
55
+ - [ ] **Test generator output for both simple and split patterns**
56
+ - [ ] **Validate that auto-registration works correctly**
57
+ - [ ] **Add integration tests for client/server rendering differences**
58
+
59
+ ## Developer Experience
60
+
61
+ - [ ] **bin/dev help command enhancements**
62
+
63
+ - [x] Add emojis and colors for better readability
64
+ - [ ] Add section about component development patterns
65
+ - [ ] Include troubleshooting for client/server split issues
66
+
67
+ - [ ] **Babel Configuration Conflict Detection**
68
+
69
+ - [ ] Add validation in generator/initializer to detect conflicting Babel configs
70
+ - [ ] Improve error messaging for duplicate preset issues
71
+ - [ ] Common conflict: babel.config.js + package.json "babel" section
72
+ - [ ] Specific guidance for yalc development workflow
73
+ - [ ] Add troubleshooting section for this common issue:
74
+
75
+ ```
76
+ ❌ BABEL CONFIGURATION CONFLICT DETECTED
77
+ Found duplicate Babel configurations:
78
+ • babel.config.js ✓ (recommended)
79
+ • package.json "babel" section ❌ (conflicting)
80
+
81
+ 🔧 FIX: Remove the "babel" section from package.json
82
+ ```
83
+
84
+ ### IDE Support
85
+
86
+ - [ ] **Improve TypeScript support**
87
+ - Add .d.ts files for better type inference
88
+ - Document TypeScript patterns for client/server split
89
+ - Consider TypeScript generator templates
90
+
91
+ ## Performance & Bundle Analysis
92
+
93
+ - [ ] **Bundle splitting documentation**
94
+ - How React on Rails handles client/server bundles
95
+ - Best practices for code splitting
96
+ - webpack bundle analysis guidance
97
+
98
+ ## Real-World Examples
99
+
100
+ - [ ] **Create example apps showing advanced patterns**
101
+ - React Router with SSR
102
+ - styled-components with server-side rendering
103
+ - Apollo Client hydration
104
+ - Next.js-style patterns
105
+
106
+ ## Migration Guide
107
+
108
+ - [ ] **Document upgrade paths**
109
+ - Converting from Webpacker to Shakapacker
110
+ - Migrating from single to split components
111
+ - Updating existing projects to new patterns
112
+
113
+ ## Community & Ecosystem
114
+
115
+ - [ ] **Plugin ecosystem considerations**
116
+ - Standard patterns for community components
117
+ - Guidelines for React on Rails compatible libraries
118
+ - Template repository for component patterns
119
+
120
+ ---
121
+
122
+ ## Current Known Issues
123
+
124
+ - Generator installer still has remaining issues (mentioned in context)
125
+ - Version mismatch warnings with yalc during development
126
+ - Need clearer documentation on when to use different patterns
127
+ - **Babel configuration conflicts** - Common during yalc development when package.json and babel.config.js both define presets
128
+
129
+ ## Priority Order
130
+
131
+ 1. Fix remaining generator installer issues
132
+ 2. Improve HelloWorld component documentation
133
+ 3. Add alternative example showing client/server split
134
+ 4. Create comprehensive architecture documentation
135
+ 5. Add generator flags for different patterns
data/eslint.config.ts CHANGED
@@ -152,6 +152,8 @@ const config = tsEslint.config([
152
152
  'import/no-unresolved': 'off',
153
153
  // We have `const [name, setName] = useState(props.name)` so can't just destructure props
154
154
  'react/destructuring-assignment': 'off',
155
+ // React 19 doesn't need PropTypes - we're targeting modern React
156
+ 'react/prop-types': 'off',
155
157
  },
156
158
  },
157
159
  {
data/lib/generators/USAGE CHANGED
@@ -1,9 +1,8 @@
1
1
  Description:
2
2
 
3
- The react_on_rails:install generator integrates Webpack with Rails with ease. You
4
- can pass the redux option if you'd like to have redux setup for you automatically.
3
+ The react_on_rails:install generator integrates a React frontend, including SSR, with Rails.
5
4
 
6
- * Redux
5
+ * Redux (Optional)
7
6
 
8
7
  Passing the --redux generator option causes the generated Hello World example
9
8
  to integrate the Redux state container framework. The necessary node modules
@@ -13,11 +12,11 @@ can pass the redux option if you'd like to have redux setup for you automaticall
13
12
 
14
13
  After running the generator, you will want to:
15
14
 
16
- bundle && yarn
15
+ bundle && npm install # or yarn install, or pnpm install
17
16
 
18
17
  Then you may run
19
18
 
20
- foreman start -f Procfile.dev
19
+ ./bin/dev
21
20
 
22
21
  More Details:
23
22
 
@@ -1,12 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "rails/generators"
4
+ require "fileutils"
4
5
  require_relative "generator_messages"
5
6
  require_relative "generator_helper"
6
7
  module ReactOnRails
7
8
  module Generators
8
9
  class BaseGenerator < Rails::Generators::Base
9
10
  include GeneratorHelper
11
+
10
12
  Rails::Generators.hide_namespace(namespace)
11
13
  source_root(File.expand_path("templates", __dir__))
12
14
 
@@ -14,7 +16,7 @@ module ReactOnRails
14
16
  class_option :redux,
15
17
  type: :boolean,
16
18
  default: false,
17
- desc: "Install Redux gems and Redux version of Hello World Example",
19
+ desc: "Install Redux package and Redux version of Hello World Example",
18
20
  aliases: "-R"
19
21
 
20
22
  def add_hello_world_route
@@ -22,17 +24,21 @@ module ReactOnRails
22
24
  end
23
25
 
24
26
  def create_react_directories
25
- dirs = %w[components]
26
- dirs.each { |name| empty_directory("app/javascript/bundles/HelloWorld/#{name}") }
27
+ # Create auto-registration directory structure for non-Redux components only
28
+ # Redux components handle their own directory structure
29
+ return if options.redux?
30
+
31
+ empty_directory("app/javascript/src/HelloWorld/ror_components")
27
32
  end
28
33
 
29
34
  def copy_base_files
30
35
  base_path = "base/base/"
31
36
  base_files = %w[app/controllers/hello_world_controller.rb
32
- app/views/layouts/hello_world.html.erb]
33
- base_templates = %w[config/initializers/react_on_rails.rb
34
- Procfile.dev
35
- Procfile.dev-static]
37
+ app/views/layouts/hello_world.html.erb
38
+ Procfile.dev
39
+ Procfile.dev-static-assets
40
+ Procfile.dev-prod-assets]
41
+ base_templates = %w[config/initializers/react_on_rails.rb]
36
42
  base_files.each { |file| copy_file("#{base_path}#{file}", file) }
37
43
  base_templates.each do |file|
38
44
  template("#{base_path}/#{file}.tt", file, { packer_type: ReactOnRails::PackerUtils.packer_type })
@@ -41,9 +47,12 @@ module ReactOnRails
41
47
 
42
48
  def copy_js_bundle_files
43
49
  base_path = "base/base/"
44
- base_files = %w[app/javascript/packs/server-bundle.js
45
- app/javascript/bundles/HelloWorld/components/HelloWorldServer.js
46
- app/javascript/bundles/HelloWorld/components/HelloWorld.module.css]
50
+ base_files = %w[app/javascript/packs/server-bundle.js]
51
+
52
+ # Only copy HelloWorld.module.css for non-Redux components
53
+ # Redux components handle their own CSS files
54
+ base_files << "app/javascript/src/HelloWorld/ror_components/HelloWorld.module.css" unless options.redux?
55
+
47
56
  base_files.each { |file| copy_file("#{base_path}#{file}", file) }
48
57
  end
49
58
 
@@ -57,15 +66,25 @@ module ReactOnRails
57
66
  config/webpack/development.js
58
67
  config/webpack/production.js
59
68
  config/webpack/serverWebpackConfig.js
60
- config/webpack/webpack.config.js
61
- config/webpack/webpackConfig.js]
69
+ config/webpack/generateWebpackConfigs.js]
62
70
  config = {
63
71
  message: "// The source code including full typescript support is available at:"
64
72
  }
65
73
  base_files.each { |file| template("#{base_path}/#{file}.tt", file, config) }
74
+
75
+ # Handle webpack.config.js separately with smart replacement
76
+ copy_webpack_main_config(base_path, config)
66
77
  end
67
78
 
68
79
  def copy_packer_config
80
+ # Skip copying if Shakapacker was just installed (to avoid conflicts)
81
+ # Check for a temporary marker file that indicates fresh Shakapacker install
82
+ if File.exist?(".shakapacker_just_installed")
83
+ puts "Skipping Shakapacker config copy (already installed by Shakapacker installer)"
84
+ File.delete(".shakapacker_just_installed") # Clean up marker
85
+ return
86
+ end
87
+
69
88
  puts "Adding Shakapacker #{ReactOnRails::PackerUtils.shakapacker_version} config"
70
89
  base_path = "base/base/"
71
90
  config = "config/shakapacker.yml"
@@ -77,39 +96,55 @@ module ReactOnRails
77
96
  end
78
97
 
79
98
  def add_js_dependencies
80
- major_minor_patch_only = /\A\d+\.\d+\.\d+\z/
81
- if ReactOnRails::VERSION.match?(major_minor_patch_only)
82
- package_json.manager.add(["react-on-rails@#{ReactOnRails::VERSION}"])
83
- else
84
- # otherwise add latest
85
- puts "Adding the latest react-on-rails NPM module. Double check this is correct in package.json"
86
- package_json.manager.add(["react-on-rails"])
99
+ add_react_on_rails_package
100
+ add_react_dependencies
101
+ add_css_dependencies
102
+ add_dev_dependencies
103
+ end
104
+
105
+ def install_js_dependencies
106
+ # Detect which package manager to use
107
+ success = if File.exist?(File.join(destination_root, "yarn.lock"))
108
+ run "yarn install"
109
+ elsif File.exist?(File.join(destination_root, "pnpm-lock.yaml"))
110
+ run "pnpm install"
111
+ elsif File.exist?(File.join(destination_root, "package-lock.json")) ||
112
+ File.exist?(File.join(destination_root, "package.json"))
113
+ # Use npm for package-lock.json or as default fallback
114
+ run "npm install"
115
+ else
116
+ true # No package manager detected, skip
117
+ end
118
+
119
+ unless success
120
+ GeneratorMessages.add_warning(<<~MSG.strip)
121
+ ⚠️ JavaScript dependencies installation failed.
122
+
123
+ This could be due to network issues or missing package manager.
124
+ You can install dependencies manually later by running:
125
+ • npm install (if using npm)
126
+ • yarn install (if using yarn)
127
+ • pnpm install (if using pnpm)
128
+ MSG
87
129
  end
88
130
 
89
- puts "Adding React dependencies"
90
- package_json.manager.add([
91
- "react",
92
- "react-dom",
93
- "@babel/preset-react",
94
- "prop-types",
95
- "babel-plugin-transform-react-remove-prop-types",
96
- "babel-plugin-macros"
97
- ])
131
+ success
132
+ end
98
133
 
99
- puts "Adding CSS handlers"
134
+ def update_gitignore_for_auto_registration
135
+ gitignore_path = File.join(destination_root, ".gitignore")
136
+ return unless File.exist?(gitignore_path)
100
137
 
101
- package_json.manager.add(%w[
102
- css-loader
103
- css-minimizer-webpack-plugin
104
- mini-css-extract-plugin
105
- style-loader
106
- ])
138
+ gitignore_content = File.read(gitignore_path)
139
+ return if gitignore_content.include?("**/generated/**")
107
140
 
108
- puts "Adding dev dependencies"
109
- package_json.manager.add([
110
- "@pmmmwh/react-refresh-webpack-plugin",
111
- "react-refresh"
112
- ], type: :dev)
141
+ append_to_file ".gitignore" do
142
+ <<~GITIGNORE
143
+
144
+ # Generated React on Rails packs
145
+ **/generated/**
146
+ GITIGNORE
147
+ end
113
148
  end
114
149
 
115
150
  def append_to_spec_rails_helper
@@ -118,26 +153,70 @@ module ReactOnRails
118
153
  add_configure_rspec_to_compile_assets(rails_helper)
119
154
  else
120
155
  spec_helper = File.join(destination_root, "spec/spec_helper.rb")
121
- if File.exist?(spec_helper)
122
- add_configure_rspec_to_compile_assets(spec_helper)
123
- else
124
- # rubocop:disable Layout/EmptyLinesAroundArguments
125
- GeneratorMessages.add_info(
126
- <<-MSG.strip_heredoc
156
+ add_configure_rspec_to_compile_assets(spec_helper) if File.exist?(spec_helper)
157
+ end
158
+ end
127
159
 
128
- We did not find a spec/rails_helper.rb or spec/spec_helper.rb to add
129
- the React on Rails Test helper, which ensures that if we are running
130
- js tests, then we are using latest webpack assets. You can later add
131
- this to your rspec config:
160
+ def add_react_on_rails_package
161
+ major_minor_patch_only = /\A\d+\.\d+\.\d+\z/
132
162
 
133
- # This will use the defaults of :js and :server_rendering meta tags
134
- ReactOnRails::TestHelper.configure_rspec_to_compile_assets(config)
135
- MSG
136
- )
137
- # rubocop:enable Layout/EmptyLinesAroundArguments
163
+ # Try to use package_json gem first, fall back to direct npm commands
164
+ react_on_rails_pkg = if ReactOnRails::VERSION.match?(major_minor_patch_only)
165
+ ["react-on-rails@#{ReactOnRails::VERSION}"]
166
+ else
167
+ puts "Adding the latest react-on-rails NPM module. " \
168
+ "Double check this is correct in package.json"
169
+ ["react-on-rails"]
170
+ end
138
171
 
139
- end
140
- end
172
+ puts "Installing React on Rails package..."
173
+ return if add_npm_dependencies(react_on_rails_pkg)
174
+
175
+ puts "Using direct npm commands as fallback"
176
+ success = run "npm install #{react_on_rails_pkg.join(' ')}"
177
+ handle_npm_failure("react-on-rails package", react_on_rails_pkg) unless success
178
+ end
179
+
180
+ def add_react_dependencies
181
+ puts "Installing React dependencies..."
182
+ react_deps = %w[
183
+ react
184
+ react-dom
185
+ @babel/preset-react
186
+ prop-types
187
+ babel-plugin-transform-react-remove-prop-types
188
+ babel-plugin-macros
189
+ ]
190
+ return if add_npm_dependencies(react_deps)
191
+
192
+ success = run "npm install #{react_deps.join(' ')}"
193
+ handle_npm_failure("React dependencies", react_deps) unless success
194
+ end
195
+
196
+ def add_css_dependencies
197
+ puts "Installing CSS handling dependencies..."
198
+ css_deps = %w[
199
+ css-loader
200
+ css-minimizer-webpack-plugin
201
+ mini-css-extract-plugin
202
+ style-loader
203
+ ]
204
+ return if add_npm_dependencies(css_deps)
205
+
206
+ success = run "npm install #{css_deps.join(' ')}"
207
+ handle_npm_failure("CSS dependencies", css_deps) unless success
208
+ end
209
+
210
+ def add_dev_dependencies
211
+ puts "Installing development dependencies..."
212
+ dev_deps = %w[
213
+ @pmmmwh/react-refresh-webpack-plugin
214
+ react-refresh
215
+ ]
216
+ return if add_npm_dependencies(dev_deps, dev: true)
217
+
218
+ success = run "npm install --save-dev #{dev_deps.join(' ')}"
219
+ handle_npm_failure("development dependencies", dev_deps, dev: true) unless success
141
220
  end
142
221
 
143
222
  CONFIGURE_RSPEC_TO_COMPILE_ASSETS = <<-STR.strip_heredoc
@@ -145,10 +224,137 @@ module ReactOnRails
145
224
  # Ensure that if we are running js tests, we are using latest webpack assets
146
225
  # This will use the defaults of :js and :server_rendering meta tags
147
226
  ReactOnRails::TestHelper.configure_rspec_to_compile_assets(config)
227
+ end
148
228
  STR
149
229
 
150
230
  private
151
231
 
232
+ def handle_npm_failure(dependency_type, packages, dev: false)
233
+ install_command = dev ? "npm install --save-dev" : "npm install"
234
+ GeneratorMessages.add_warning(<<~MSG.strip)
235
+ ⚠️ Failed to install #{dependency_type}.
236
+
237
+ The following packages could not be installed automatically:
238
+ #{packages.map { |pkg| " • #{pkg}" }.join("\n")}
239
+
240
+ This could be due to network issues or missing package manager.
241
+ You can install them manually later by running:
242
+ #{install_command} #{packages.join(' ')}
243
+ MSG
244
+ end
245
+
246
+ def copy_webpack_main_config(base_path, config)
247
+ webpack_config_path = "config/webpack/webpack.config.js"
248
+
249
+ if File.exist?(webpack_config_path)
250
+ existing_content = File.read(webpack_config_path)
251
+
252
+ # Check if it's the standard Shakapacker config that we can safely replace
253
+ if standard_shakapacker_config?(existing_content)
254
+ # Remove the file first to avoid conflict prompt, then recreate it
255
+ remove_file(webpack_config_path, verbose: false)
256
+ # Show what we're doing
257
+ puts " #{set_color('replace', :green)} #{webpack_config_path} " \
258
+ "(auto-upgrading from standard Shakapacker to React on Rails config)"
259
+ template("#{base_path}/#{webpack_config_path}.tt", webpack_config_path, config)
260
+ elsif react_on_rails_config?(existing_content)
261
+ puts " #{set_color('identical', :blue)} #{webpack_config_path} " \
262
+ "(already React on Rails compatible)"
263
+ # Skip - don't need to do anything
264
+ else
265
+ handle_custom_webpack_config(base_path, config, webpack_config_path)
266
+ end
267
+ else
268
+ # File doesn't exist, create it
269
+ template("#{base_path}/#{webpack_config_path}.tt", webpack_config_path, config)
270
+ end
271
+ end
272
+
273
+ def handle_custom_webpack_config(base_path, config, webpack_config_path)
274
+ # Custom config - ask user
275
+ puts "\n#{set_color('NOTICE:', :yellow)} Your webpack.config.js appears to be customized."
276
+ puts "React on Rails needs to replace it with an environment-specific loader."
277
+ puts "Your current config will be backed up to webpack.config.js.backup"
278
+
279
+ if yes?("Replace webpack.config.js with React on Rails version? (Y/n)")
280
+ # Create backup
281
+ backup_path = "#{webpack_config_path}.backup"
282
+ if File.exist?(webpack_config_path)
283
+ FileUtils.cp(webpack_config_path, backup_path)
284
+ puts " #{set_color('create', :green)} #{backup_path} (backup of your custom config)"
285
+ end
286
+
287
+ template("#{base_path}/#{webpack_config_path}.tt", webpack_config_path, config)
288
+ else
289
+ puts " #{set_color('skip', :yellow)} #{webpack_config_path}"
290
+ puts " #{set_color('WARNING:', :red)} React on Rails may not work correctly " \
291
+ "without the environment-specific webpack config"
292
+ end
293
+ end
294
+
295
+ def standard_shakapacker_config?(content)
296
+ # Get the expected default config based on Shakapacker version
297
+ expected_configs = shakapacker_default_configs
298
+
299
+ # Check if the content matches any of the known default configurations
300
+ expected_configs.any? { |config| content_matches_template?(content, config) }
301
+ end
302
+
303
+ def content_matches_template?(content, template)
304
+ # Normalize whitespace and compare
305
+ normalize_config_content(content) == normalize_config_content(template)
306
+ end
307
+
308
+ def normalize_config_content(content)
309
+ # Remove comments, normalize whitespace, and clean up for comparison
310
+ content.gsub(%r{//.*$}, "") # Remove single-line comments
311
+ .gsub(%r{/\*.*?\*/}m, "") # Remove multi-line comments
312
+ .gsub(/\s+/, " ") # Normalize whitespace
313
+ .strip
314
+ end
315
+
316
+ def shakapacker_default_configs
317
+ configs = []
318
+
319
+ # Shakapacker v7+ (generateWebpackConfig function)
320
+ configs << <<~CONFIG
321
+ // See the shakacode/shakapacker README and docs directory for advice on customizing your webpackConfig.
322
+ const { generateWebpackConfig } = require('shakapacker')
323
+
324
+ const webpackConfig = generateWebpackConfig()
325
+
326
+ module.exports = webpackConfig
327
+ CONFIG
328
+
329
+ # Shakapacker v6 (webpackConfig object)
330
+ configs << <<~CONFIG
331
+ const { webpackConfig } = require('shakapacker')
332
+
333
+ // See the shakacode/shakapacker README and docs directory for advice on customizing your webpackConfig.
334
+
335
+ module.exports = webpackConfig
336
+ CONFIG
337
+
338
+ # Also check without comments for variations
339
+ configs << <<~CONFIG
340
+ const { generateWebpackConfig } = require('shakapacker')
341
+ const webpackConfig = generateWebpackConfig()
342
+ module.exports = webpackConfig
343
+ CONFIG
344
+
345
+ configs << <<~CONFIG
346
+ const { webpackConfig } = require('shakapacker')
347
+ module.exports = webpackConfig
348
+ CONFIG
349
+
350
+ configs
351
+ end
352
+
353
+ def react_on_rails_config?(content)
354
+ # Check if it already has React on Rails environment-specific loading
355
+ content.include?("envSpecificConfig") || content.include?("env.nodeEnv")
356
+ end
357
+
152
358
  # From https://github.com/rails/rails/blob/4c940b2dbfb457f67c6250b720f63501d74a45fd/railties/lib/rails/generators/rails/app/app_generator.rb
153
359
  def app_name
154
360
  @app_name ||= (defined_app_const_base? ? defined_app_name : File.basename(destination_root))
@@ -1,30 +1,46 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
3
 
4
- def installed?(process)
5
- IO.popen "#{process} -v"
6
- rescue Errno::ENOENT
7
- false
8
- end
4
+ # ReactOnRails Development Server
5
+ #
6
+ # This script provides a simple interface to the ReactOnRails development
7
+ # server management. The core logic is implemented in ReactOnRails::Dev
8
+ # classes for better maintainability and testing.
9
+ #
10
+ # Each command uses a specific Procfile for process management:
11
+ # - bin/dev (default/hmr): Uses Procfile.dev
12
+ # - bin/dev static: Uses Procfile.dev-static-assets
13
+ # - bin/dev prod: Uses Procfile.dev-prod-assets
14
+ #
15
+ # To customize development environment:
16
+ # 1. Edit the appropriate Procfile to modify which processes run
17
+ # 2. Modify this script for project-specific command-line behavior
18
+ # 3. Extend ReactOnRails::Dev classes in your Rails app for advanced customization
19
+ # 4. Use classes directly: ReactOnRails::Dev::ServerManager.start(:development, "Custom.procfile")
9
20
 
10
- def run(process)
11
- system "#{process} start -f Procfile.dev"
12
- rescue Errno::ENOENT
13
- warn <<~MSG
14
- ERROR:
15
- Please ensure `Procfile.dev` exists in your project!
16
- MSG
17
- exit!
21
+ begin
22
+ require "bundler/setup"
23
+ require "react_on_rails/dev"
24
+ rescue LoadError
25
+ # Fallback for when gem is not yet installed
26
+ puts "Loading ReactOnRails development tools..."
27
+ require_relative "../../lib/react_on_rails/dev"
18
28
  end
19
29
 
20
- if installed? "overmind"
21
- run "overmind"
22
- elsif installed? "foreman"
23
- run "foreman"
30
+ # Main execution
31
+ case ARGV[0]
32
+ when "production-assets", "prod"
33
+ ReactOnRails::Dev::ServerManager.start(:production_like)
34
+ when "static"
35
+ ReactOnRails::Dev::ServerManager.start(:static, "Procfile.dev-static-assets")
36
+ when "kill"
37
+ ReactOnRails::Dev::ServerManager.kill_processes
38
+ when "help", "--help", "-h"
39
+ ReactOnRails::Dev::ServerManager.show_help
40
+ when "hmr", nil
41
+ ReactOnRails::Dev::ServerManager.start(:development, "Procfile.dev")
24
42
  else
25
- warn <<~MSG
26
- NOTICE:
27
- For this script to run, you need either 'overmind' or 'foreman' installed on your machine. Please try this script after installing one of them.
28
- MSG
29
- exit!
43
+ puts "Unknown argument: #{ARGV[0]}"
44
+ puts "Run 'bin/dev help' for usage information"
45
+ exit 1
30
46
  end
@@ -7,6 +7,7 @@ module ReactOnRails
7
7
  module Generators
8
8
  class DevTestsGenerator < Rails::Generators::Base
9
9
  include GeneratorHelper
10
+
10
11
  Rails::Generators.hide_namespace(namespace)
11
12
  source_root(File.expand_path("templates/dev_tests", __dir__))
12
13