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
@@ -0,0 +1,383 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "generator_messages"
4
+
5
+ # rubocop:disable Metrics/ModuleLength
6
+ module ReactOnRails
7
+ module Generators
8
+ # Shared module for managing JavaScript dependencies across generators
9
+ # This module provides common functionality for adding and installing
10
+ # JS dependencies to avoid code duplication between generators.
11
+ #
12
+ # Since react_on_rails requires shakapacker, and shakapacker includes
13
+ # package_json as a dependency, the package_json gem is always available.
14
+ #
15
+ # == Required Methods
16
+ # Including classes must include GeneratorHelper module which provides:
17
+ # - add_npm_dependencies(packages, dev: false): Add packages via package_json gem
18
+ # - package_json: Access to PackageJson instance (always available via shakapacker)
19
+ # - destination_root: Generator destination directory
20
+ #
21
+ # == Optional Methods
22
+ # Including classes may define:
23
+ # - options.rspack?: Returns true if --rspack flag is set (for Rspack support)
24
+ # - options.typescript?: Returns true if --typescript flag is set (for TypeScript support)
25
+ #
26
+ # == Installation Behavior
27
+ # The module ALWAYS runs package manager install after adding dependencies.
28
+ # This is safe because package_json gem's install method is idempotent - it only
29
+ # installs what's actually needed from package.json. This prevents edge cases
30
+ # where package.json was modified but dependencies weren't installed.
31
+ #
32
+ # == Error Handling Philosophy
33
+ # All dependency addition methods use a graceful degradation approach:
34
+ # - Methods return false on failure instead of raising exceptions
35
+ # - StandardError is caught at the lowest level (add_package) and higher levels (add_*_dependencies)
36
+ # - Failures trigger user-facing warnings via GeneratorMessages
37
+ # - Warnings provide clear manual installation instructions
38
+ #
39
+ # This ensures the generator ALWAYS completes successfully, even when:
40
+ # - Network connectivity issues prevent package downloads
41
+ # - Package manager (npm/yarn/pnpm) has permission errors
42
+ # - package_json gem encounters unexpected states
43
+ #
44
+ # Users can manually run package installation commands after generator completion.
45
+ # This is preferable to generator crashes that leave Rails apps in incomplete states.
46
+ #
47
+ # == Usage
48
+ # Include this module in generator classes and call setup_js_dependencies
49
+ # to handle all JS dependency installation via package_json gem.
50
+ module JsDependencyManager
51
+ # Core React dependencies required for React on Rails
52
+ # Note: @babel/preset-react and babel plugins are NOT included here because:
53
+ # - Shakapacker handles JavaScript transpiler configuration (babel, swc, or esbuild)
54
+ # - Users configure their preferred transpiler via shakapacker.yml javascript_transpiler setting
55
+ # - SWC is now the default and doesn't need Babel presets
56
+ # - For Babel users, shakapacker will install babel-loader and its dependencies
57
+ REACT_DEPENDENCIES = %w[
58
+ react
59
+ react-dom
60
+ prop-types
61
+ ].freeze
62
+
63
+ # CSS processing dependencies for webpack
64
+ CSS_DEPENDENCIES = %w[
65
+ css-loader
66
+ css-minimizer-webpack-plugin
67
+ mini-css-extract-plugin
68
+ style-loader
69
+ ].freeze
70
+
71
+ # Development-only dependencies for hot reloading (Webpack)
72
+ DEV_DEPENDENCIES = %w[
73
+ @pmmmwh/react-refresh-webpack-plugin
74
+ react-refresh
75
+ ].freeze
76
+
77
+ # Rspack core dependencies (only installed when --rspack flag is used)
78
+ RSPACK_DEPENDENCIES = %w[
79
+ @rspack/core
80
+ rspack-manifest-plugin
81
+ ].freeze
82
+
83
+ # Rspack development dependencies for hot reloading
84
+ RSPACK_DEV_DEPENDENCIES = %w[
85
+ @rspack/cli
86
+ @rspack/plugin-react-refresh
87
+ react-refresh
88
+ ].freeze
89
+
90
+ # TypeScript dependencies (only installed when --typescript flag is used)
91
+ # Note: @babel/preset-typescript is NOT included because:
92
+ # - SWC is now the default javascript_transpiler (has built-in TypeScript support)
93
+ # - Shakapacker handles the transpiler configuration via shakapacker.yml
94
+ # - If users choose javascript_transpiler: 'babel', they should manually add @babel/preset-typescript
95
+ # and configure it in their babel.config.js
96
+ TYPESCRIPT_DEPENDENCIES = %w[
97
+ typescript
98
+ @types/react
99
+ @types/react-dom
100
+ ].freeze
101
+
102
+ # SWC transpiler dependencies (for Shakapacker 9.3.0+ default transpiler)
103
+ # SWC is ~20x faster than Babel and is the default for new Shakapacker installations
104
+ SWC_DEPENDENCIES = %w[
105
+ @swc/core
106
+ swc-loader
107
+ ].freeze
108
+
109
+ private
110
+
111
+ def setup_js_dependencies
112
+ add_js_dependencies
113
+
114
+ # Always run install to ensure all dependencies are properly installed.
115
+ # The package_json gem's install method is idempotent and safe to call
116
+ # even if packages were already added - it will only install what's needed.
117
+ # This ensures edge cases where package.json was modified but install wasn't
118
+ # run are handled correctly.
119
+ install_js_dependencies
120
+ end
121
+
122
+ def add_js_dependencies
123
+ add_react_on_rails_package
124
+ add_react_dependencies
125
+ add_css_dependencies
126
+ # Rspack dependencies are only added when --rspack flag is used
127
+ add_rspack_dependencies if respond_to?(:options) && options&.rspack?
128
+ # SWC dependencies are only added when SWC is the configured transpiler
129
+ add_swc_dependencies if using_swc?
130
+ # Dev dependencies vary based on bundler choice
131
+ add_dev_dependencies
132
+ end
133
+
134
+ def add_react_on_rails_package
135
+ # Use exact version match between gem and npm package for all versions including pre-releases
136
+ # Ruby gem versions use dots (16.2.0.beta.10) but npm requires hyphens (16.2.0-beta.10)
137
+ # This method converts between the two formats.
138
+ #
139
+ # The regex matches:
140
+ # - Stable: 16.2.0
141
+ # - Beta (Ruby): 16.2.0.beta.10 or (npm): 16.2.0-beta.10
142
+ # - RC (Ruby): 16.1.0.rc.1 or (npm): 16.1.0-rc.1
143
+ # - Alpha (Ruby): 16.0.0.alpha.5 or (npm): 16.0.0-alpha.5
144
+ # This ensures beta/rc versions use the exact version instead of "latest" which would
145
+ # install the latest stable release and cause version mismatches.
146
+
147
+ # Accept both dot and hyphen separators for pre-release versions
148
+ version_with_optional_prerelease = /\A(\d+\.\d+\.\d+)([-.]([a-zA-Z0-9.]+))?\z/
149
+
150
+ react_on_rails_pkg = if (match = ReactOnRails::VERSION.match(version_with_optional_prerelease))
151
+ base_version = match[1]
152
+ prerelease = match[3]
153
+
154
+ # Convert Ruby gem format (dot) to npm semver format (hyphen)
155
+ npm_version = if prerelease
156
+ "#{base_version}-#{prerelease}"
157
+ else
158
+ base_version
159
+ end
160
+
161
+ "react-on-rails@#{npm_version}"
162
+ else
163
+ puts "WARNING: Unrecognized version format #{ReactOnRails::VERSION}. " \
164
+ "Adding the latest react-on-rails NPM module. " \
165
+ "Double check this is correct in package.json"
166
+ "react-on-rails"
167
+ end
168
+
169
+ puts "Installing React on Rails package..."
170
+ return if add_package(react_on_rails_pkg)
171
+
172
+ GeneratorMessages.add_warning(<<~MSG.strip)
173
+ ⚠️ Failed to add react-on-rails package.
174
+
175
+ You can install it manually by running:
176
+ npm install #{react_on_rails_pkg}
177
+ MSG
178
+ rescue StandardError => e
179
+ GeneratorMessages.add_warning(<<~MSG.strip)
180
+ ⚠️ Error adding react-on-rails package: #{e.message}
181
+
182
+ You can install it manually by running:
183
+ npm install #{react_on_rails_pkg}
184
+ MSG
185
+ end
186
+
187
+ def add_react_dependencies
188
+ puts "Installing React dependencies..."
189
+ return if add_packages(REACT_DEPENDENCIES)
190
+
191
+ GeneratorMessages.add_warning(<<~MSG.strip)
192
+ ⚠️ Failed to add React dependencies.
193
+
194
+ You can install them manually by running:
195
+ npm install #{REACT_DEPENDENCIES.join(' ')}
196
+ MSG
197
+ rescue StandardError => e
198
+ GeneratorMessages.add_warning(<<~MSG.strip)
199
+ ⚠️ Error adding React dependencies: #{e.message}
200
+
201
+ You can install them manually by running:
202
+ npm install #{REACT_DEPENDENCIES.join(' ')}
203
+ MSG
204
+ end
205
+
206
+ def add_css_dependencies
207
+ puts "Installing CSS handling dependencies..."
208
+ return if add_packages(CSS_DEPENDENCIES)
209
+
210
+ GeneratorMessages.add_warning(<<~MSG.strip)
211
+ ⚠️ Failed to add CSS dependencies.
212
+
213
+ You can install them manually by running:
214
+ npm install #{CSS_DEPENDENCIES.join(' ')}
215
+ MSG
216
+ rescue StandardError => e
217
+ GeneratorMessages.add_warning(<<~MSG.strip)
218
+ ⚠️ Error adding CSS dependencies: #{e.message}
219
+
220
+ You can install them manually by running:
221
+ npm install #{CSS_DEPENDENCIES.join(' ')}
222
+ MSG
223
+ end
224
+
225
+ def add_rspack_dependencies
226
+ puts "Installing Rspack core dependencies..."
227
+ return if add_packages(RSPACK_DEPENDENCIES)
228
+
229
+ GeneratorMessages.add_warning(<<~MSG.strip)
230
+ ⚠️ Failed to add Rspack dependencies.
231
+
232
+ You can install them manually by running:
233
+ npm install #{RSPACK_DEPENDENCIES.join(' ')}
234
+ MSG
235
+ rescue StandardError => e
236
+ GeneratorMessages.add_warning(<<~MSG.strip)
237
+ ⚠️ Error adding Rspack dependencies: #{e.message}
238
+
239
+ You can install them manually by running:
240
+ npm install #{RSPACK_DEPENDENCIES.join(' ')}
241
+ MSG
242
+ end
243
+
244
+ def add_swc_dependencies
245
+ puts "Installing SWC transpiler dependencies (20x faster than Babel)..."
246
+ return if add_packages(SWC_DEPENDENCIES, dev: true)
247
+
248
+ GeneratorMessages.add_warning(<<~MSG.strip)
249
+ ⚠️ Failed to add SWC dependencies.
250
+
251
+ SWC is the default JavaScript transpiler for Shakapacker 9.3.0+.
252
+ You can install them manually by running:
253
+ npm install --save-dev #{SWC_DEPENDENCIES.join(' ')}
254
+ MSG
255
+ rescue StandardError => e
256
+ GeneratorMessages.add_warning(<<~MSG.strip)
257
+ ⚠️ Error adding SWC dependencies: #{e.message}
258
+
259
+ You can install them manually by running:
260
+ npm install --save-dev #{SWC_DEPENDENCIES.join(' ')}
261
+ MSG
262
+ end
263
+
264
+ def add_typescript_dependencies
265
+ puts "Installing TypeScript dependencies..."
266
+ return if add_packages(TYPESCRIPT_DEPENDENCIES, dev: true)
267
+
268
+ GeneratorMessages.add_warning(<<~MSG.strip)
269
+ ⚠️ Failed to add TypeScript dependencies.
270
+
271
+ You can install them manually by running:
272
+ npm install --save-dev #{TYPESCRIPT_DEPENDENCIES.join(' ')}
273
+ MSG
274
+ rescue StandardError => e
275
+ GeneratorMessages.add_warning(<<~MSG.strip)
276
+ ⚠️ Error adding TypeScript dependencies: #{e.message}
277
+
278
+ You can install them manually by running:
279
+ npm install --save-dev #{TYPESCRIPT_DEPENDENCIES.join(' ')}
280
+ MSG
281
+ end
282
+
283
+ def add_dev_dependencies
284
+ puts "Installing development dependencies..."
285
+
286
+ # Use Rspack-specific dev dependencies if --rspack flag is set
287
+ dev_deps = if respond_to?(:options) && options&.rspack?
288
+ RSPACK_DEV_DEPENDENCIES
289
+ else
290
+ DEV_DEPENDENCIES
291
+ end
292
+
293
+ return if add_packages(dev_deps, dev: true)
294
+
295
+ GeneratorMessages.add_warning(<<~MSG.strip)
296
+ ⚠️ Failed to add development dependencies.
297
+
298
+ You can install them manually by running:
299
+ npm install --save-dev #{dev_deps.join(' ')}
300
+ MSG
301
+ rescue StandardError => e
302
+ GeneratorMessages.add_warning(<<~MSG.strip)
303
+ ⚠️ Error adding development dependencies: #{e.message}
304
+
305
+ You can install them manually by running:
306
+ npm install --save-dev #{dev_deps.join(' ')}
307
+ MSG
308
+ end
309
+
310
+ # Add a single dependency using package_json gem
311
+ #
312
+ # This method is used internally for adding the react-on-rails package
313
+ # with version-specific handling (react-on-rails@VERSION).
314
+ # For batch operations, use add_packages instead.
315
+ #
316
+ # The exact: true flag ensures version pinning aligns with the gem version,
317
+ # preventing version mismatches between the Ruby gem and NPM package.
318
+ #
319
+ # @param package [String] Package specifier (e.g., "react-on-rails@16.0.0")
320
+ # @param dev [Boolean] Whether to add as dev dependency
321
+ # @return [Boolean] true if successful, false otherwise
322
+ def add_package(package, dev: false)
323
+ pj = package_json
324
+ return false unless pj
325
+
326
+ begin
327
+ # Ensure package is in array format for package_json gem
328
+ packages_array = [package]
329
+ if dev
330
+ pj.manager.add(packages_array, type: :dev, exact: true)
331
+ else
332
+ pj.manager.add(packages_array, exact: true)
333
+ end
334
+ true
335
+ rescue StandardError
336
+ # Return false to trigger warning in calling method
337
+ false
338
+ end
339
+ end
340
+
341
+ # Add multiple dependencies at once using package_json gem
342
+ #
343
+ # This method delegates to GeneratorHelper's add_npm_dependencies for
344
+ # better package manager abstraction and batch processing efficiency.
345
+ #
346
+ # @param packages [Array<String>] Package names to add
347
+ # @param dev [Boolean] Whether to add as dev dependencies
348
+ # @return [Boolean] true if successful, false otherwise
349
+ def add_packages(packages, dev: false)
350
+ # Use the add_npm_dependencies helper from GeneratorHelper
351
+ add_npm_dependencies(packages, dev: dev)
352
+ end
353
+
354
+ def install_js_dependencies
355
+ # Use package_json gem's install method (always available via shakapacker)
356
+ # package_json is guaranteed to be available because:
357
+ # 1. react_on_rails gemspec requires shakapacker
358
+ # 2. shakapacker gemspec requires package_json
359
+ # 3. GeneratorHelper provides package_json method
360
+ pj = package_json
361
+ unless pj
362
+ GeneratorMessages.add_warning("package_json not available, skipping dependency installation")
363
+ return false
364
+ end
365
+
366
+ pj.manager.install
367
+ true
368
+ rescue StandardError => e
369
+ GeneratorMessages.add_warning(<<~MSG.strip)
370
+ ⚠️ JavaScript dependencies installation failed: #{e.message}
371
+
372
+ This could be due to network issues or package manager problems.
373
+ You can install dependencies manually later by running:
374
+ • npm install (if using npm)
375
+ • yarn install (if using yarn)
376
+ • pnpm install (if using pnpm)
377
+ MSG
378
+ false
379
+ end
380
+ end
381
+ end
382
+ end
383
+ # rubocop:enable Metrics/ModuleLength
@@ -0,0 +1,76 @@
1
+ # Service Dependencies Configuration
2
+ #
3
+ # This file defines external services that must be running before bin/dev starts.
4
+ # Copy this file to .dev-services.yml and customize for your application.
5
+ #
6
+ # bin/dev will check each service before starting the development server.
7
+ # If any service is not running, it will display helpful error messages with
8
+ # instructions on how to start the service.
9
+ #
10
+ # ⚠️ SECURITY WARNING:
11
+ # Commands in this file are executed during bin/dev startup. Only add commands
12
+ # from trusted sources. This file should not be committed if it contains
13
+ # sensitive information or custom paths specific to your machine. Consider
14
+ # adding .dev-services.yml to .gitignore if it contains machine-specific config.
15
+ #
16
+ # Security best practices:
17
+ # - Commands are executed without shell expansion (shell metacharacters won't work)
18
+ # - Use simple, single commands (e.g., "redis-cli ping", "pg_isready")
19
+ # - Do NOT use shell features: &&, ||, |, $, ;, backticks, etc. will fail
20
+ # - Only include services you trust
21
+ # - Keep commands simple and focused on service health checks
22
+ # - Consider adding .dev-services.yml to .gitignore (commit .example instead)
23
+ #
24
+ # Example configuration:
25
+ #
26
+ # services:
27
+ # redis:
28
+ # check_command: "redis-cli ping"
29
+ # expected_output: "PONG"
30
+ # start_command: "redis-server"
31
+ # install_hint: "brew install redis (macOS) or apt-get install redis-server (Linux)"
32
+ # description: "Redis (for caching and background jobs)"
33
+ #
34
+ # postgresql:
35
+ # check_command: "pg_isready"
36
+ # expected_output: "accepting connections"
37
+ # start_command: "pg_ctl -D /usr/local/var/postgres start"
38
+ # install_hint: "brew install postgresql (macOS) or apt-get install postgresql (Linux)"
39
+ # description: "PostgreSQL database"
40
+ #
41
+ # elasticsearch:
42
+ # check_command: "curl -s http://localhost:9200"
43
+ # expected_output: "cluster_name"
44
+ # start_command: "brew services start elasticsearch-full"
45
+ # install_hint: "brew install elasticsearch-full"
46
+ # description: "Elasticsearch (for search)"
47
+ #
48
+ # Field descriptions:
49
+ # check_command: Shell command to check if service is running (required)
50
+ # expected_output: String that must appear in command output (optional)
51
+ # start_command: Command to start the service (shown in error messages)
52
+ # install_hint: How to install the service if not found
53
+ # description: Human-readable description of the service
54
+ #
55
+ # To use this file:
56
+ # 1. Copy to .dev-services.yml: cp .dev-services.yml.example .dev-services.yml
57
+ # 2. Uncomment and configure the services your app needs
58
+ # 3. Add .dev-services.yml to .gitignore if it contains sensitive info
59
+ # 4. Run bin/dev - it will check services before starting
60
+
61
+ services:
62
+ # Uncomment and configure the services your application requires:
63
+
64
+ # redis:
65
+ # check_command: "redis-cli ping"
66
+ # expected_output: "PONG"
67
+ # start_command: "redis-server"
68
+ # install_hint: "brew install redis (macOS) or apt-get install redis-server (Linux)"
69
+ # description: "Redis (for caching and background jobs)"
70
+
71
+ # postgresql:
72
+ # check_command: "pg_isready"
73
+ # expected_output: "accepting connections"
74
+ # start_command: "pg_ctl -D /usr/local/var/postgres start" # macOS; Linux: sudo service postgresql start
75
+ # install_hint: "brew install postgresql (macOS) or apt-get install postgresql (Linux)"
76
+ # description: "PostgreSQL database"
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Shakapacker precompile hook for React on Rails
5
+ #
6
+ # This script runs before webpack compilation to generate pack files
7
+ # for auto-bundled components. It's called automatically by Shakapacker
8
+ # when configured in config/shakapacker.yml:
9
+ # precompile_hook: 'bin/shakapacker-precompile-hook'
10
+ #
11
+ # Emoji Scheme:
12
+ # 🔄 = Running/in-progress
13
+ # ✅ = Success
14
+ # ❌ = Error
15
+
16
+ # Skip validation during precompile hook execution
17
+ # The hook runs early in the build process, potentially before full Rails initialization,
18
+ # and doesn't need package version validation since it's part of the build itself
19
+ ENV["REACT_ON_RAILS_SKIP_VALIDATION"] = "true"
20
+
21
+ require_relative "../config/environment"
22
+
23
+ begin
24
+ puts Rainbow("🔄 Running React on Rails precompile hook...").cyan
25
+ ReactOnRails::PacksGenerator.instance.generate_packs_if_stale
26
+ rescue StandardError => e
27
+ warn Rainbow("❌ Error in precompile hook: #{e.message}").red
28
+ warn e.backtrace.first(5).join("\n")
29
+ exit 1
30
+ end
@@ -0,0 +1,141 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "fileutils"
5
+ require "yaml"
6
+ require "json"
7
+
8
+ # Script to switch between webpack and rspack bundlers
9
+ class BundlerSwitcher
10
+ WEBPACK_DEPS = {
11
+ dependencies: %w[webpack webpack-assets-manifest webpack-merge],
12
+ dev_dependencies: %w[webpack-cli webpack-dev-server @pmmmwh/react-refresh-webpack-plugin]
13
+ }.freeze
14
+
15
+ RSPACK_DEPS = {
16
+ dependencies: %w[@rspack/core rspack-manifest-plugin],
17
+ dev_dependencies: %w[@rspack/cli @rspack/plugin-react-refresh]
18
+ }.freeze
19
+
20
+ def initialize(target_bundler)
21
+ @target_bundler = target_bundler.to_s.downcase
22
+ @shakapacker_config = "config/shakapacker.yml"
23
+ validate_bundler!
24
+ end
25
+
26
+ def switch!
27
+ puts "🔄 Switching to #{@target_bundler}..."
28
+
29
+ update_shakapacker_config
30
+ update_dependencies
31
+ install_dependencies
32
+
33
+ puts "✅ Successfully switched to #{@target_bundler}!"
34
+ puts "\nNext steps:"
35
+ puts " 1. Review your webpack configuration files in config/webpack/"
36
+ puts " 2. Restart your development server"
37
+ end
38
+
39
+ private
40
+
41
+ def validate_bundler!
42
+ return if %w[webpack rspack].include?(@target_bundler)
43
+
44
+ abort "❌ Invalid bundler: #{@target_bundler}. Use 'webpack' or 'rspack'"
45
+ end
46
+
47
+ def update_shakapacker_config
48
+ abort "❌ #{@shakapacker_config} not found" unless File.exist?(@shakapacker_config)
49
+
50
+ puts "📝 Updating #{@shakapacker_config}..."
51
+ config = YAML.load_file(@shakapacker_config)
52
+
53
+ config["default"] ||= {}
54
+ config["default"]["assets_bundler"] = @target_bundler
55
+
56
+ # Update webpack_loader based on bundler
57
+ # Rspack works best with SWC, webpack typically uses babel
58
+ config["default"]["webpack_loader"] = @target_bundler == "rspack" ? "swc" : "babel"
59
+
60
+ File.write(@shakapacker_config, YAML.dump(config))
61
+ puts "✅ Updated assets_bundler to '#{@target_bundler}'"
62
+ end
63
+
64
+ def update_dependencies
65
+ puts "📦 Updating package.json dependencies..."
66
+
67
+ package_json_path = "package.json"
68
+ unless File.exist?(package_json_path)
69
+ puts "⚠️ package.json not found, skipping dependency updates"
70
+ return
71
+ end
72
+
73
+ package_json = JSON.parse(File.read(package_json_path))
74
+
75
+ remove_deps = @target_bundler == "rspack" ? WEBPACK_DEPS : RSPACK_DEPS
76
+
77
+ # Remove old bundler dependencies
78
+ remove_deps[:dependencies].each do |dep|
79
+ package_json["dependencies"]&.delete(dep)
80
+ end
81
+ remove_deps[:dev_dependencies].each do |dep|
82
+ package_json["devDependencies"]&.delete(dep)
83
+ end
84
+
85
+ puts "✅ Removed #{@target_bundler == 'rspack' ? 'webpack' : 'rspack'} dependencies"
86
+ File.write(package_json_path, JSON.pretty_generate(package_json))
87
+ end
88
+
89
+ def install_dependencies
90
+ puts "📥 Installing #{@target_bundler} dependencies..."
91
+
92
+ deps = @target_bundler == "rspack" ? RSPACK_DEPS : WEBPACK_DEPS
93
+
94
+ # Detect package manager
95
+ package_manager = detect_package_manager
96
+
97
+ # Install dependencies using array form to prevent command injection
98
+ success = case package_manager
99
+ when "yarn"
100
+ system("yarn", "add", *deps[:dependencies])
101
+ when "pnpm"
102
+ system("pnpm", "add", *deps[:dependencies])
103
+ else
104
+ system("npm", "install", *deps[:dependencies])
105
+ end
106
+
107
+ abort("❌ Failed to install dependencies") unless success
108
+
109
+ # Install dev dependencies using array form to prevent command injection
110
+ success = case package_manager
111
+ when "yarn"
112
+ system("yarn", "add", "-D", *deps[:dev_dependencies])
113
+ when "pnpm"
114
+ system("pnpm", "add", "-D", *deps[:dev_dependencies])
115
+ else
116
+ system("npm", "install", "--save-dev", *deps[:dev_dependencies])
117
+ end
118
+
119
+ abort("❌ Failed to install dev dependencies") unless success
120
+
121
+ puts "✅ Installed #{@target_bundler} dependencies"
122
+ end
123
+
124
+ def detect_package_manager
125
+ return "yarn" if File.exist?("yarn.lock")
126
+ return "pnpm" if File.exist?("pnpm-lock.yaml")
127
+
128
+ "npm"
129
+ end
130
+ end
131
+
132
+ # Main execution
133
+ if ARGV.empty?
134
+ puts "Usage: bin/switch-bundler [webpack|rspack]"
135
+ puts "\nExamples:"
136
+ puts " bin/switch-bundler rspack # Switch to Rspack"
137
+ puts " bin/switch-bundler webpack # Switch to Webpack"
138
+ exit 1
139
+ end
140
+
141
+ BundlerSwitcher.new(ARGV[0]).switch!