react_on_rails 16.1.2 → 16.2.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 (106) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +2 -0
  3. data/.rubocop.yml +85 -0
  4. data/Gemfile.development_dependencies +8 -7
  5. data/Gemfile.lock +158 -119
  6. data/Steepfile +56 -0
  7. data/lib/generators/react_on_rails/base_generator.rb +43 -120
  8. data/lib/generators/react_on_rails/dev_tests_generator.rb +2 -1
  9. data/lib/generators/react_on_rails/generator_helper.rb +102 -2
  10. data/lib/generators/react_on_rails/install_generator.rb +36 -156
  11. data/lib/generators/react_on_rails/js_dependency_manager.rb +383 -0
  12. data/lib/generators/react_on_rails/templates/base/base/.dev-services.yml.example +76 -0
  13. data/lib/generators/react_on_rails/templates/base/base/bin/shakapacker-precompile-hook +30 -0
  14. data/lib/generators/react_on_rails/templates/base/base/bin/switch-bundler +141 -0
  15. data/lib/generators/react_on_rails/templates/base/base/config/initializers/react_on_rails.rb.tt +44 -45
  16. data/lib/generators/react_on_rails/templates/base/base/config/{shakapacker.yml → shakapacker.yml.tt} +28 -3
  17. data/lib/generators/react_on_rails/templates/base/base/config/webpack/development.js.tt +15 -9
  18. data/lib/generators/react_on_rails/templates/base/base/config/webpack/serverWebpackConfig.js.tt +42 -6
  19. data/lib/react_on_rails/configuration.rb +149 -32
  20. data/lib/react_on_rails/controller.rb +3 -3
  21. data/lib/react_on_rails/dev/pack_generator.rb +168 -2
  22. data/lib/react_on_rails/dev/process_manager.rb +136 -14
  23. data/lib/react_on_rails/dev/server_manager.rb +194 -26
  24. data/lib/react_on_rails/dev/service_checker.rb +200 -0
  25. data/lib/react_on_rails/doctor.rb +341 -12
  26. data/lib/react_on_rails/engine.rb +75 -1
  27. data/lib/react_on_rails/git_utils.rb +3 -1
  28. data/lib/react_on_rails/helper.rb +70 -192
  29. data/lib/react_on_rails/locales/base.rb +17 -5
  30. data/lib/react_on_rails/packer_utils.rb +79 -2
  31. data/lib/react_on_rails/packs_generator.rb +57 -39
  32. data/lib/react_on_rails/prerender_error.rb +74 -17
  33. data/lib/react_on_rails/pro_helper.rb +64 -0
  34. data/lib/react_on_rails/react_component/render_options.rb +7 -7
  35. data/lib/react_on_rails/server_rendering_pool/ruby_embedded_java_script.rb +2 -5
  36. data/lib/react_on_rails/smart_error.rb +326 -0
  37. data/lib/react_on_rails/system_checker.rb +8 -9
  38. data/lib/react_on_rails/test_helper/webpack_assets_status_checker.rb +16 -7
  39. data/lib/react_on_rails/utils.rb +241 -55
  40. data/lib/react_on_rails/version.rb +1 -1
  41. data/lib/react_on_rails/version_checker.rb +383 -35
  42. data/lib/tasks/generate_packs.rake +12 -6
  43. data/lib/tasks/locale.rake +6 -1
  44. data/rakelib/docker.rake +26 -0
  45. data/rakelib/dummy_apps.rake +30 -0
  46. data/rakelib/example_type.rb +121 -0
  47. data/rakelib/examples_config.yml +52 -0
  48. data/rakelib/lint.rake +52 -0
  49. data/rakelib/node_package.rake +15 -0
  50. data/rakelib/rbs.rake +70 -0
  51. data/rakelib/run_rspec.rake +223 -0
  52. data/rakelib/shakapacker_examples.rake +171 -0
  53. data/rakelib/task_helpers.rb +134 -0
  54. data/rakelib/update_changelog.rake +73 -0
  55. data/react_on_rails.gemspec +4 -3
  56. data/sig/README.md +52 -0
  57. data/sig/react_on_rails/configuration.rbs +96 -0
  58. data/sig/react_on_rails/controller.rbs +15 -0
  59. data/sig/react_on_rails/dev/file_manager.rbs +15 -0
  60. data/sig/react_on_rails/dev/pack_generator.rbs +19 -0
  61. data/sig/react_on_rails/dev/process_manager.rbs +22 -0
  62. data/sig/react_on_rails/dev/server_manager.rbs +39 -0
  63. data/sig/react_on_rails/dev/service_checker.rbs +22 -0
  64. data/sig/react_on_rails/error.rbs +4 -0
  65. data/sig/react_on_rails/generators/js_dependency_manager.rbs +123 -0
  66. data/sig/react_on_rails/git_utils.rbs +8 -0
  67. data/sig/react_on_rails/helper.rbs +65 -0
  68. data/sig/react_on_rails/json_parse_error.rbs +10 -0
  69. data/sig/react_on_rails/locales.rbs +46 -0
  70. data/sig/react_on_rails/packer_utils.rbs +15 -0
  71. data/sig/react_on_rails/prerender_error.rbs +21 -0
  72. data/sig/react_on_rails/server_rendering_pool.rbs +12 -0
  73. data/sig/react_on_rails/smart_error.rbs +28 -0
  74. data/sig/react_on_rails/test_helper.rbs +11 -0
  75. data/sig/react_on_rails/utils.rbs +34 -0
  76. data/sig/react_on_rails/version_checker.rbs +12 -0
  77. data/sig/react_on_rails.rbs +17 -0
  78. metadata +49 -32
  79. data/AI_AGENT_INSTRUCTIONS.md +0 -63
  80. data/CHANGELOG.md +0 -1836
  81. data/CLAUDE.md +0 -135
  82. data/CODING_AGENTS.md +0 -313
  83. data/CONTRIBUTING.md +0 -668
  84. data/Dockerfile_tests +0 -12
  85. data/KUDOS.md +0 -114
  86. data/LICENSE.md +0 -47
  87. data/LICENSES/README.md +0 -14
  88. data/NEWS.md +0 -62
  89. data/PROJECTS.md +0 -63
  90. data/REACT-ON-RAILS-PRO-LICENSE.md +0 -129
  91. data/README.md +0 -217
  92. data/SUMMARY.md +0 -88
  93. data/TODO.md +0 -135
  94. data/bin/lefthook/check-trailing-newlines +0 -38
  95. data/bin/lefthook/get-changed-files +0 -26
  96. data/bin/lefthook/prettier-format +0 -26
  97. data/bin/lefthook/ruby-autofix +0 -26
  98. data/bin/lefthook/ruby-lint +0 -27
  99. data/docker-compose.yml +0 -11
  100. data/eslint.config.ts +0 -232
  101. data/knip.ts +0 -114
  102. data/lib/react_on_rails/pro/NOTICE +0 -21
  103. data/lib/react_on_rails/pro/helper.rb +0 -122
  104. data/lib/react_on_rails/pro/utils.rb +0 -53
  105. data/tsconfig.eslint.json +0 -6
  106. data/tsconfig.json +0 -19
data/Steepfile ADDED
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Steepfile - Configuration for Steep type checker
4
+ # See https://github.com/soutaro/steep for documentation
5
+ #
6
+ # IMPORTANT: This file lists only the files that are ready for type checking.
7
+ # We use a positive list (explicit check statements) rather than checking all files
8
+ # because not all files have RBS signatures yet.
9
+ #
10
+ # Files/directories intentionally excluded (no RBS signatures yet):
11
+ # - lib/generators/**/* - Rails generators (complex Rails integration)
12
+ # - lib/react_on_rails/engine.rb - Rails engine setup
13
+ # - lib/react_on_rails/doctor.rb - Diagnostic tool
14
+ # - lib/react_on_rails/locales/**/* - I18n files
15
+ # - lib/react_on_rails/props_js_builder.rb - TODO: Add signature
16
+ # - lib/react_on_rails/shakapacker/**/* - Shakapacker integration (complex)
17
+ #
18
+ # To add a new file to type checking:
19
+ # 1. Create corresponding RBS signature in sig/react_on_rails/filename.rbs
20
+ # 2. Add `check "lib/react_on_rails/filename.rb"` below
21
+ # 3. Run `bundle exec rake rbs:steep` to verify
22
+ # 4. Fix any type errors before committing
23
+
24
+ D = Steep::Diagnostic
25
+
26
+ target :lib do
27
+ # Core files with RBS signatures (alphabetically ordered for easy maintenance)
28
+ check "lib/react_on_rails.rb"
29
+ check "lib/react_on_rails/configuration.rb"
30
+ check "lib/react_on_rails/controller.rb"
31
+ check "lib/react_on_rails/dev/file_manager.rb"
32
+ check "lib/react_on_rails/dev/pack_generator.rb"
33
+ check "lib/react_on_rails/dev/process_manager.rb"
34
+ check "lib/react_on_rails/dev/server_manager.rb"
35
+ check "lib/react_on_rails/dev/service_checker.rb"
36
+ check "lib/react_on_rails/git_utils.rb"
37
+ check "lib/react_on_rails/helper.rb"
38
+ check "lib/react_on_rails/packer_utils.rb"
39
+ check "lib/react_on_rails/server_rendering_pool.rb"
40
+ check "lib/react_on_rails/test_helper.rb"
41
+ check "lib/react_on_rails/utils.rb"
42
+ check "lib/react_on_rails/version_checker.rb"
43
+
44
+ # Specify RBS signature directories
45
+ signature "sig"
46
+
47
+ # Configure libraries (gems) - Steep will load their RBS signatures
48
+ configure_code_diagnostics(D::Ruby.default)
49
+
50
+ # Library configuration - standard library gems used by checked files
51
+ library "pathname"
52
+ library "singleton"
53
+ library "logger"
54
+ library "monitor"
55
+ library "securerandom"
56
+ end
@@ -4,10 +4,12 @@ require "rails/generators"
4
4
  require "fileutils"
5
5
  require_relative "generator_messages"
6
6
  require_relative "generator_helper"
7
+ require_relative "js_dependency_manager"
7
8
  module ReactOnRails
8
9
  module Generators
9
10
  class BaseGenerator < Rails::Generators::Base
10
11
  include GeneratorHelper
12
+ include JsDependencyManager
11
13
 
12
14
  Rails::Generators.hide_namespace(namespace)
13
15
  source_root(File.expand_path("templates", __dir__))
@@ -19,6 +21,12 @@ module ReactOnRails
19
21
  desc: "Install Redux package and Redux version of Hello World Example",
20
22
  aliases: "-R"
21
23
 
24
+ # --rspack
25
+ class_option :rspack,
26
+ type: :boolean,
27
+ default: false,
28
+ desc: "Use Rspack instead of Webpack as the bundler"
29
+
22
30
  def add_hello_world_route
23
31
  route "get 'hello_world', to: 'hello_world#index'"
24
32
  end
@@ -37,12 +45,17 @@ module ReactOnRails
37
45
  app/views/layouts/hello_world.html.erb
38
46
  Procfile.dev
39
47
  Procfile.dev-static-assets
40
- Procfile.dev-prod-assets]
48
+ Procfile.dev-prod-assets
49
+ .dev-services.yml.example
50
+ bin/shakapacker-precompile-hook]
41
51
  base_templates = %w[config/initializers/react_on_rails.rb]
42
52
  base_files.each { |file| copy_file("#{base_path}#{file}", file) }
43
53
  base_templates.each do |file|
44
54
  template("#{base_path}/#{file}.tt", file)
45
55
  end
56
+
57
+ # Make the hook script executable (copy_file guarantees it exists)
58
+ File.chmod(0o755, File.join(destination_root, "bin/shakapacker-precompile-hook"))
46
59
  end
47
60
 
48
61
  def copy_js_bundle_files
@@ -82,20 +95,23 @@ module ReactOnRails
82
95
  if File.exist?(".shakapacker_just_installed")
83
96
  puts "Skipping Shakapacker config copy (already installed by Shakapacker installer)"
84
97
  File.delete(".shakapacker_just_installed") # Clean up marker
98
+ configure_rspack_in_shakapacker if options.rspack?
85
99
  return
86
100
  end
87
101
 
88
102
  puts "Adding Shakapacker #{ReactOnRails::PackerUtils.shakapacker_version} config"
89
103
  base_path = "base/base/"
90
104
  config = "config/shakapacker.yml"
91
- copy_file("#{base_path}#{config}", config)
105
+ # Use template to enable version-aware configuration
106
+ template("#{base_path}#{config}.tt", config)
107
+ configure_rspack_in_shakapacker if options.rspack?
92
108
  end
93
109
 
94
110
  def add_base_gems_to_gemfile
95
111
  run "bundle"
96
112
  end
97
113
 
98
- def update_gitignore_for_generated_bundles
114
+ def update_gitignore_for_auto_registration
99
115
  gitignore_path = File.join(destination_root, ".gitignore")
100
116
  return unless File.exist?(gitignore_path)
101
117
 
@@ -134,123 +150,6 @@ module ReactOnRails
134
150
 
135
151
  private
136
152
 
137
- def setup_js_dependencies
138
- add_js_dependencies
139
- install_js_dependencies
140
- end
141
-
142
- def add_js_dependencies
143
- add_react_on_rails_package
144
- add_react_dependencies
145
- add_css_dependencies
146
- add_dev_dependencies
147
- end
148
-
149
- def add_react_on_rails_package
150
- major_minor_patch_only = /\A\d+\.\d+\.\d+\z/
151
-
152
- # Try to use package_json gem first, fall back to direct npm commands
153
- react_on_rails_pkg = if ReactOnRails::VERSION.match?(major_minor_patch_only)
154
- ["react-on-rails@#{ReactOnRails::VERSION}"]
155
- else
156
- puts "Adding the latest react-on-rails NPM module. " \
157
- "Double check this is correct in package.json"
158
- ["react-on-rails"]
159
- end
160
-
161
- puts "Installing React on Rails package..."
162
- return if add_npm_dependencies(react_on_rails_pkg)
163
-
164
- puts "Using direct npm commands as fallback"
165
- success = system("npm", "install", *react_on_rails_pkg)
166
- handle_npm_failure("react-on-rails package", react_on_rails_pkg) unless success
167
- end
168
-
169
- def add_react_dependencies
170
- puts "Installing React dependencies..."
171
- react_deps = %w[
172
- react
173
- react-dom
174
- @babel/preset-react
175
- prop-types
176
- babel-plugin-transform-react-remove-prop-types
177
- babel-plugin-macros
178
- ]
179
- return if add_npm_dependencies(react_deps)
180
-
181
- success = system("npm", "install", *react_deps)
182
- handle_npm_failure("React dependencies", react_deps) unless success
183
- end
184
-
185
- def add_css_dependencies
186
- puts "Installing CSS handling dependencies..."
187
- css_deps = %w[
188
- css-loader
189
- css-minimizer-webpack-plugin
190
- mini-css-extract-plugin
191
- style-loader
192
- ]
193
- return if add_npm_dependencies(css_deps)
194
-
195
- success = system("npm", "install", *css_deps)
196
- handle_npm_failure("CSS dependencies", css_deps) unless success
197
- end
198
-
199
- def add_dev_dependencies
200
- puts "Installing development dependencies..."
201
- dev_deps = %w[
202
- @pmmmwh/react-refresh-webpack-plugin
203
- react-refresh
204
- ]
205
- return if add_npm_dependencies(dev_deps, dev: true)
206
-
207
- success = system("npm", "install", "--save-dev", *dev_deps)
208
- handle_npm_failure("development dependencies", dev_deps, dev: true) unless success
209
- end
210
-
211
- def install_js_dependencies
212
- # Detect which package manager to use
213
- success = if File.exist?(File.join(destination_root, "yarn.lock"))
214
- system("yarn", "install")
215
- elsif File.exist?(File.join(destination_root, "pnpm-lock.yaml"))
216
- system("pnpm", "install")
217
- elsif File.exist?(File.join(destination_root, "package-lock.json")) ||
218
- File.exist?(File.join(destination_root, "package.json"))
219
- # Use npm for package-lock.json or as default fallback
220
- system("npm", "install")
221
- else
222
- true # No package manager detected, skip
223
- end
224
-
225
- unless success
226
- GeneratorMessages.add_warning(<<~MSG.strip)
227
- ⚠️ JavaScript dependencies installation failed.
228
-
229
- This could be due to network issues or missing package manager.
230
- You can install dependencies manually later by running:
231
- • npm install (if using npm)
232
- • yarn install (if using yarn)
233
- • pnpm install (if using pnpm)
234
- MSG
235
- end
236
-
237
- success
238
- end
239
-
240
- def handle_npm_failure(dependency_type, packages, dev: false)
241
- install_command = dev ? "npm install --save-dev" : "npm install"
242
- GeneratorMessages.add_warning(<<~MSG.strip)
243
- ⚠️ Failed to install #{dependency_type}.
244
-
245
- The following packages could not be installed automatically:
246
- #{packages.map { |pkg| " • #{pkg}" }.join("\n")}
247
-
248
- This could be due to network issues or missing package manager.
249
- You can install them manually later by running:
250
- #{install_command} #{packages.join(' ')}
251
- MSG
252
- end
253
-
254
153
  def copy_webpack_main_config(base_path, config)
255
154
  webpack_config_path = "config/webpack/webpack.config.js"
256
155
 
@@ -392,6 +291,30 @@ module ReactOnRails
392
291
  search_str = "RSpec.configure do |config|"
393
292
  gsub_file(helper_file, search_str, CONFIGURE_RSPEC_TO_COMPILE_ASSETS)
394
293
  end
294
+
295
+ def configure_rspack_in_shakapacker
296
+ shakapacker_config_path = "config/shakapacker.yml"
297
+ return unless File.exist?(shakapacker_config_path)
298
+
299
+ puts Rainbow("🔧 Configuring Shakapacker for Rspack...").yellow
300
+
301
+ # Parse YAML config properly to avoid fragile regex manipulation
302
+ # Support both old and new Psych versions
303
+ config = begin
304
+ YAML.load_file(shakapacker_config_path, aliases: true)
305
+ rescue ArgumentError
306
+ # Older Psych versions don't support the aliases parameter
307
+ YAML.load_file(shakapacker_config_path)
308
+ end
309
+ # Update default section
310
+ config["default"] ||= {}
311
+ config["default"]["assets_bundler"] = "rspack"
312
+ config["default"]["webpack_loader"] = "swc"
313
+
314
+ # Write back as YAML
315
+ File.write(shakapacker_config_path, YAML.dump(config))
316
+ puts Rainbow("✅ Updated shakapacker.yml for Rspack").green
317
+ end
395
318
  end
396
319
  end
397
320
  end
@@ -31,7 +31,8 @@ module ReactOnRails
31
31
 
32
32
  def add_test_related_gems_to_gemfile
33
33
  gem("rspec-rails", group: :test)
34
- gem("chromedriver-helper", group: :test)
34
+ # NOTE: chromedriver-helper was deprecated in 2019. Modern selenium-webdriver (4.x)
35
+ # and GitHub Actions have built-in driver management, so no driver helper is needed.
35
36
  gem("coveralls", require: false)
36
37
  end
37
38
 
@@ -26,9 +26,9 @@ module GeneratorHelper
26
26
 
27
27
  begin
28
28
  if dev
29
- pj.manager.add(packages, type: :dev)
29
+ pj.manager.add(packages, type: :dev, exact: true)
30
30
  else
31
- pj.manager.add(packages)
31
+ pj.manager.add(packages, exact: true)
32
32
  end
33
33
  true
34
34
  rescue StandardError => e
@@ -95,4 +95,104 @@ module GeneratorHelper
95
95
  def component_extension(options)
96
96
  options.typescript? ? "tsx" : "jsx"
97
97
  end
98
+
99
+ # Check if Shakapacker 9.0 or higher is available
100
+ # Returns true if Shakapacker >= 9.0, false otherwise
101
+ #
102
+ # This method is used during code generation to determine which configuration
103
+ # patterns to use in generated files (e.g., config.privateOutputPath vs hardcoded paths).
104
+ #
105
+ # @return [Boolean] true if Shakapacker 9.0+ is available or likely to be installed
106
+ #
107
+ # @note Default behavior: Returns true when Shakapacker is not yet installed
108
+ # Rationale: During fresh installations, we optimistically assume users will install
109
+ # the latest Shakapacker version. This ensures new projects get best-practice configs.
110
+ # If users later install an older version, the generated webpack config includes
111
+ # fallback logic (e.g., `config.privateOutputPath || hardcodedPath`) that prevents
112
+ # breakage, and validation warnings guide them to fix any misconfigurations.
113
+ def shakapacker_version_9_or_higher?
114
+ return @shakapacker_version_9_or_higher if defined?(@shakapacker_version_9_or_higher)
115
+
116
+ @shakapacker_version_9_or_higher = begin
117
+ # If Shakapacker is not available yet (fresh install), default to true
118
+ # since we're likely installing the latest version
119
+ return true unless defined?(ReactOnRails::PackerUtils)
120
+
121
+ ReactOnRails::PackerUtils.shakapacker_version_requirement_met?("9.0.0")
122
+ rescue StandardError
123
+ # If we can't determine version, assume latest
124
+ true
125
+ end
126
+ end
127
+
128
+ # Check if SWC is configured as the JavaScript transpiler in shakapacker.yml
129
+ #
130
+ # @return [Boolean] true if SWC is configured or should be used by default
131
+ #
132
+ # Detection logic:
133
+ # 1. If shakapacker.yml exists and specifies javascript_transpiler: parse it
134
+ # 2. For Shakapacker 9.3.0+, SWC is the default if not specified
135
+ # 3. Returns true for fresh installations (SWC is recommended default)
136
+ #
137
+ # @note This method is used to determine whether to install SWC dependencies
138
+ # (@swc/core, swc-loader) instead of Babel dependencies during generation.
139
+ #
140
+ # @note Caching: The result is memoized for the lifetime of the generator instance.
141
+ # If shakapacker.yml changes during generator execution (unlikely), the cached
142
+ # value will not update. This is acceptable since generators run quickly.
143
+ def using_swc?
144
+ return @using_swc if defined?(@using_swc)
145
+
146
+ @using_swc = detect_swc_configuration
147
+ end
148
+
149
+ private
150
+
151
+ def detect_swc_configuration
152
+ shakapacker_yml_path = File.join(destination_root, "config/shakapacker.yml")
153
+
154
+ if File.exist?(shakapacker_yml_path)
155
+ config = parse_shakapacker_yml(shakapacker_yml_path)
156
+ transpiler = config.dig("default", "javascript_transpiler")
157
+
158
+ # Explicit configuration takes precedence
159
+ return transpiler == "swc" if transpiler
160
+
161
+ # For Shakapacker 9.3.0+, SWC is the default
162
+ return shakapacker_version_9_3_or_higher?
163
+ end
164
+
165
+ # Fresh install: SWC is recommended default for Shakapacker 9.3.0+
166
+ shakapacker_version_9_3_or_higher?
167
+ end
168
+
169
+ def parse_shakapacker_yml(path)
170
+ require "yaml"
171
+ # Use safe_load_file for security (defense-in-depth, even though this is user's own config)
172
+ # permitted_classes: [Symbol] allows symbol keys which shakapacker.yml may use
173
+ # aliases: true allows YAML anchors (&default, *default) commonly used in Rails configs
174
+ YAML.safe_load_file(path, permitted_classes: [Symbol], aliases: true)
175
+ rescue ArgumentError
176
+ # Older Psych versions don't support all parameters - try without aliases
177
+ begin
178
+ YAML.safe_load_file(path, permitted_classes: [Symbol])
179
+ rescue ArgumentError
180
+ # Very old Psych - fall back to safe_load with File.read
181
+ YAML.safe_load(File.read(path), permitted_classes: [Symbol]) # rubocop:disable Style/YAMLFileRead
182
+ end
183
+ rescue StandardError
184
+ # If we can't parse the file, return empty config
185
+ {}
186
+ end
187
+
188
+ # Check if Shakapacker 9.3.0 or higher is available
189
+ # This version made SWC the default JavaScript transpiler
190
+ def shakapacker_version_9_3_or_higher?
191
+ return true unless defined?(ReactOnRails::PackerUtils)
192
+
193
+ ReactOnRails::PackerUtils.shakapacker_version_requirement_met?("9.3.0")
194
+ rescue StandardError
195
+ # If we can't determine version, assume latest (which uses SWC)
196
+ true
197
+ end
98
198
  end
@@ -4,12 +4,14 @@ require "rails/generators"
4
4
  require "json"
5
5
  require_relative "generator_helper"
6
6
  require_relative "generator_messages"
7
+ require_relative "js_dependency_manager"
7
8
 
8
9
  module ReactOnRails
9
10
  module Generators
10
11
  # rubocop:disable Metrics/ClassLength
11
12
  class InstallGenerator < Rails::Generators::Base
12
13
  include GeneratorHelper
14
+ include JsDependencyManager
13
15
 
14
16
  # fetch USAGE file for details generator description
15
17
  source_root(File.expand_path(__dir__))
@@ -28,6 +30,12 @@ module ReactOnRails
28
30
  desc: "Generate TypeScript files and install TypeScript dependencies. Default: false",
29
31
  aliases: "-T"
30
32
 
33
+ # --rspack
34
+ class_option :rspack,
35
+ type: :boolean,
36
+ default: false,
37
+ desc: "Use Rspack instead of Webpack as the bundler. Default: false"
38
+
31
39
  # --ignore-warnings
32
40
  class_option :ignore_warnings,
33
41
  type: :boolean,
@@ -36,7 +44,25 @@ module ReactOnRails
36
44
 
37
45
  # Removed: --skip-shakapacker-install (Shakapacker is now a required dependency)
38
46
 
47
+ # Main generator entry point
48
+ #
49
+ # Sets up React on Rails in a Rails application by:
50
+ # 1. Validating prerequisites
51
+ # 2. Installing required packages
52
+ # 3. Generating configuration files
53
+ # 4. Setting up example components
54
+ #
55
+ # @note Validation Skipping: Sets ENV["REACT_ON_RAILS_SKIP_VALIDATION"] to prevent
56
+ # version validation from running during generator execution. The npm package
57
+ # isn't installed until midway through the generator, so validation would fail
58
+ # if run during Rails initialization. The ensure block guarantees cleanup even
59
+ # if the generator fails.
39
60
  def run_generators
61
+ # Set environment variable to skip validation during generator run
62
+ # This is inherited by all invoked generators and persists through Rails initialization
63
+ # See lib/react_on_rails/engine.rb for the validation skip logic
64
+ ENV["REACT_ON_RAILS_SKIP_VALIDATION"] = "true"
65
+
40
66
  if installation_prerequisites_met? || options.ignore_warnings?
41
67
  invoke_generators
42
68
  add_bin_scripts
@@ -55,6 +81,11 @@ module ReactOnRails
55
81
  GeneratorMessages.add_error(error)
56
82
  end
57
83
  ensure
84
+ # Always clean up ENV variable, even if generator fails
85
+ # CRITICAL: ENV cleanup must come first to ensure it executes even if
86
+ # print_generator_messages raises an exception. This prevents ENV pollution
87
+ # that could affect subsequent processes.
88
+ ENV.delete("REACT_ON_RAILS_SKIP_VALIDATION")
58
89
  print_generator_messages
59
90
  end
60
91
 
@@ -73,7 +104,8 @@ module ReactOnRails
73
104
  create_css_module_types
74
105
  create_typescript_config
75
106
  end
76
- invoke "react_on_rails:base", [], { typescript: options.typescript? }
107
+ invoke "react_on_rails:base", [],
108
+ { typescript: options.typescript?, redux: options.redux?, rspack: options.rspack? }
77
109
  if options.redux?
78
110
  invoke "react_on_rails:react_with_redux", [], { typescript: options.typescript? }
79
111
  else
@@ -83,10 +115,7 @@ module ReactOnRails
83
115
  end
84
116
 
85
117
  def setup_react_dependencies
86
- @added_dependencies_to_package_json ||= false
87
- @ran_direct_installs ||= false
88
- add_js_dependencies
89
- install_js_dependencies if @added_dependencies_to_package_json && !@ran_direct_installs
118
+ setup_js_dependencies
90
119
  end
91
120
 
92
121
  # NOTE: other requirements for existing files such as .gitignore or application.
@@ -336,29 +365,8 @@ module ReactOnRails
336
365
 
337
366
  def install_typescript_dependencies
338
367
  puts Rainbow("📝 Installing TypeScript dependencies...").yellow
339
-
340
- # Install TypeScript and React type definitions
341
- typescript_packages = %w[
342
- typescript
343
- @types/react
344
- @types/react-dom
345
- @babel/preset-typescript
346
- ]
347
-
348
- # Try using GeneratorHelper first (package manager agnostic)
349
- return if add_npm_dependencies(typescript_packages, dev: true)
350
-
351
- # Fallback to npm if GeneratorHelper fails
352
- success = system("npm", "install", "--save-dev", *typescript_packages)
353
- return if success
354
-
355
- warning = <<~MSG.strip
356
- ⚠️ Failed to install TypeScript dependencies automatically.
357
-
358
- Please run manually:
359
- npm install --save-dev #{typescript_packages.join(' ')}
360
- MSG
361
- GeneratorMessages.add_warning(warning)
368
+ # Delegate to shared module for consistent dependency management
369
+ add_typescript_dependencies
362
370
  end
363
371
 
364
372
  def create_css_module_types
@@ -420,134 +428,6 @@ module ReactOnRails
420
428
  puts Rainbow("✅ Created tsconfig.json").green
421
429
  end
422
430
 
423
- def add_js_dependencies
424
- add_react_on_rails_package
425
- add_react_dependencies
426
- add_css_dependencies
427
- add_dev_dependencies
428
- end
429
-
430
- def add_react_on_rails_package
431
- major_minor_patch_only = /\A\d+\.\d+\.\d+\z/
432
-
433
- # Try to use package_json gem first, fall back to direct npm commands
434
- react_on_rails_pkg = if ReactOnRails::VERSION.match?(major_minor_patch_only)
435
- ["react-on-rails@#{ReactOnRails::VERSION}"]
436
- else
437
- puts "Adding the latest react-on-rails NPM module. " \
438
- "Double check this is correct in package.json"
439
- ["react-on-rails"]
440
- end
441
-
442
- puts "Installing React on Rails package..."
443
- if add_npm_dependencies(react_on_rails_pkg)
444
- @added_dependencies_to_package_json = true
445
- return
446
- end
447
-
448
- puts "Using direct npm commands as fallback"
449
- success = system("npm", "install", *react_on_rails_pkg)
450
- @ran_direct_installs = true if success
451
- handle_npm_failure("react-on-rails package", react_on_rails_pkg) unless success
452
- end
453
-
454
- def add_react_dependencies
455
- puts "Installing React dependencies..."
456
- react_deps = %w[
457
- react
458
- react-dom
459
- @babel/preset-react
460
- prop-types
461
- babel-plugin-transform-react-remove-prop-types
462
- babel-plugin-macros
463
- ]
464
- if add_npm_dependencies(react_deps)
465
- @added_dependencies_to_package_json = true
466
- return
467
- end
468
-
469
- success = system("npm", "install", *react_deps)
470
- @ran_direct_installs = true if success
471
- handle_npm_failure("React dependencies", react_deps) unless success
472
- end
473
-
474
- def add_css_dependencies
475
- puts "Installing CSS handling dependencies..."
476
- css_deps = %w[
477
- css-loader
478
- css-minimizer-webpack-plugin
479
- mini-css-extract-plugin
480
- style-loader
481
- ]
482
- if add_npm_dependencies(css_deps)
483
- @added_dependencies_to_package_json = true
484
- return
485
- end
486
-
487
- success = system("npm", "install", *css_deps)
488
- @ran_direct_installs = true if success
489
- handle_npm_failure("CSS dependencies", css_deps) unless success
490
- end
491
-
492
- def add_dev_dependencies
493
- puts "Installing development dependencies..."
494
- dev_deps = %w[
495
- @pmmmwh/react-refresh-webpack-plugin
496
- react-refresh
497
- ]
498
- if add_npm_dependencies(dev_deps, dev: true)
499
- @added_dependencies_to_package_json = true
500
- return
501
- end
502
-
503
- success = system("npm", "install", "--save-dev", *dev_deps)
504
- @ran_direct_installs = true if success
505
- handle_npm_failure("development dependencies", dev_deps, dev: true) unless success
506
- end
507
-
508
- def install_js_dependencies
509
- # Detect which package manager to use
510
- success = if File.exist?(File.join(destination_root, "yarn.lock"))
511
- system("yarn", "install")
512
- elsif File.exist?(File.join(destination_root, "pnpm-lock.yaml"))
513
- system("pnpm", "install")
514
- elsif File.exist?(File.join(destination_root, "package-lock.json")) ||
515
- File.exist?(File.join(destination_root, "package.json"))
516
- # Use npm for package-lock.json or as default fallback
517
- system("npm", "install")
518
- else
519
- true # No package manager detected, skip
520
- end
521
-
522
- unless success
523
- GeneratorMessages.add_warning(<<~MSG.strip)
524
- ⚠️ JavaScript dependencies installation failed.
525
-
526
- This could be due to network issues or missing package manager.
527
- You can install dependencies manually later by running:
528
- • npm install (if using npm)
529
- • yarn install (if using yarn)
530
- • pnpm install (if using pnpm)
531
- MSG
532
- end
533
-
534
- success
535
- end
536
-
537
- def handle_npm_failure(dependency_type, packages, dev: false)
538
- install_command = dev ? "npm install --save-dev" : "npm install"
539
- GeneratorMessages.add_warning(<<~MSG.strip)
540
- ⚠️ Failed to install #{dependency_type}.
541
-
542
- The following packages could not be installed automatically:
543
- #{packages.map { |pkg| " • #{pkg}" }.join("\n")}
544
-
545
- This could be due to network issues or missing package manager.
546
- You can install them manually later by running:
547
- #{install_command} #{packages.join(' ')}
548
- MSG
549
- end
550
-
551
431
  # Removed: Shakapacker auto-installation logic (now explicit dependency)
552
432
 
553
433
  # Removed: Shakapacker 8+ is now required as explicit dependency