react_on_rails 15.0.0.rc.2 → 16.0.1.rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +103 -34
  3. data/CLAUDE.md +102 -0
  4. data/CODING_AGENTS.md +312 -0
  5. data/CONTRIBUTING.md +378 -3
  6. data/Gemfile.lock +2 -1
  7. data/LICENSE.md +30 -4
  8. data/LICENSES/README.md +14 -0
  9. data/REACT-ON-RAILS-PRO-LICENSE.md +129 -0
  10. data/README.md +70 -20
  11. data/TODO.md +135 -0
  12. data/eslint.config.ts +5 -0
  13. data/knip.ts +20 -9
  14. data/lib/generators/USAGE +4 -5
  15. data/lib/generators/react_on_rails/USAGE +65 -0
  16. data/lib/generators/react_on_rails/base_generator.rb +263 -57
  17. data/lib/generators/react_on_rails/dev_tests_generator.rb +1 -0
  18. data/lib/generators/react_on_rails/generator_helper.rb +35 -1
  19. data/lib/generators/react_on_rails/generator_messages.rb +138 -17
  20. data/lib/generators/react_on_rails/install_generator.rb +336 -26
  21. data/lib/generators/react_on_rails/react_no_redux_generator.rb +19 -6
  22. data/lib/generators/react_on_rails/react_with_redux_generator.rb +111 -18
  23. data/lib/generators/react_on_rails/templates/base/base/Procfile.dev +5 -0
  24. data/lib/generators/react_on_rails/templates/base/base/Procfile.dev-prod-assets +8 -0
  25. data/lib/generators/react_on_rails/templates/base/base/Procfile.dev-static-assets +2 -0
  26. data/lib/generators/react_on_rails/templates/base/base/app/javascript/bundles/HelloWorld/components/HelloWorld.jsx +0 -5
  27. data/lib/generators/react_on_rails/templates/base/base/app/javascript/packs/server-bundle.js +1 -8
  28. data/lib/generators/react_on_rails/templates/base/base/app/javascript/src/HelloWorld/ror_components/HelloWorld.client.jsx +21 -0
  29. data/lib/generators/react_on_rails/templates/base/base/app/javascript/src/HelloWorld/ror_components/HelloWorld.client.tsx +25 -0
  30. data/lib/generators/react_on_rails/templates/base/base/app/javascript/src/HelloWorld/ror_components/HelloWorld.module.css +4 -0
  31. data/lib/generators/react_on_rails/templates/base/base/app/javascript/src/HelloWorld/ror_components/HelloWorld.server.jsx +5 -0
  32. data/lib/generators/react_on_rails/templates/base/base/app/javascript/src/HelloWorld/ror_components/HelloWorld.server.tsx +5 -0
  33. data/lib/generators/react_on_rails/templates/base/base/app/views/hello_world/index.html.erb.tt +1 -1
  34. data/lib/generators/react_on_rails/templates/base/base/app/views/layouts/hello_world.html.erb +4 -2
  35. data/lib/generators/react_on_rails/templates/base/base/babel.config.js.tt +5 -2
  36. data/lib/generators/react_on_rails/templates/base/base/bin/dev +34 -0
  37. data/lib/generators/react_on_rails/templates/base/base/config/initializers/react_on_rails.rb.tt +3 -3
  38. data/lib/generators/react_on_rails/templates/base/base/config/shakapacker.yml +76 -7
  39. data/lib/generators/react_on_rails/templates/base/base/config/webpack/commonWebpackConfig.js.tt +1 -1
  40. data/lib/generators/react_on_rails/templates/base/base/config/webpack/development.js.tt +8 -8
  41. data/lib/generators/react_on_rails/templates/base/base/config/webpack/production.js.tt +2 -2
  42. data/lib/generators/react_on_rails/templates/base/base/config/webpack/test.js.tt +2 -2
  43. data/lib/generators/react_on_rails/templates/dev_tests/spec/system/hello_world_spec.rb +0 -2
  44. data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/actions/helloWorldActionCreators.ts +18 -0
  45. data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/components/HelloWorld.jsx +0 -6
  46. data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/components/HelloWorld.module.css +4 -0
  47. data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/components/HelloWorld.tsx +24 -0
  48. data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/constants/helloWorldConstants.ts +6 -0
  49. data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/containers/HelloWorldContainer.ts +20 -0
  50. data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/reducers/helloWorldReducer.ts +22 -0
  51. data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/startup/HelloWorldApp.client.tsx +23 -0
  52. data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/startup/HelloWorldApp.server.jsx +5 -0
  53. data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/startup/HelloWorldApp.server.tsx +5 -0
  54. data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/store/helloWorldStore.ts +18 -0
  55. data/lib/react_on_rails/configuration.rb +15 -11
  56. data/lib/react_on_rails/controller.rb +5 -3
  57. data/lib/react_on_rails/dev/file_manager.rb +78 -0
  58. data/lib/react_on_rails/dev/pack_generator.rb +27 -0
  59. data/lib/react_on_rails/dev/process_manager.rb +61 -0
  60. data/lib/react_on_rails/dev/server_manager.rb +487 -0
  61. data/lib/react_on_rails/dev.rb +20 -0
  62. data/lib/react_on_rails/doctor.rb +1149 -0
  63. data/lib/react_on_rails/engine.rb +6 -0
  64. data/lib/react_on_rails/git_utils.rb +12 -2
  65. data/lib/react_on_rails/helper.rb +19 -44
  66. data/lib/react_on_rails/packer_utils.rb +4 -18
  67. data/lib/react_on_rails/packs_generator.rb +134 -8
  68. data/lib/react_on_rails/pro/NOTICE +21 -0
  69. data/lib/react_on_rails/pro/helper.rb +122 -0
  70. data/lib/react_on_rails/pro/utils.rb +53 -0
  71. data/lib/react_on_rails/react_component/render_options.rb +8 -4
  72. data/lib/react_on_rails/server_rendering_js_code.rb +0 -1
  73. data/lib/react_on_rails/server_rendering_pool/ruby_embedded_java_script.rb +1 -0
  74. data/lib/react_on_rails/system_checker.rb +659 -0
  75. data/lib/react_on_rails/test_helper/webpack_assets_status_checker.rb +1 -0
  76. data/lib/react_on_rails/utils.rb +16 -1
  77. data/lib/react_on_rails/version.rb +1 -1
  78. data/lib/react_on_rails/version_syntax_converter.rb +1 -1
  79. data/lib/react_on_rails.rb +1 -0
  80. data/lib/tasks/doctor.rake +51 -0
  81. data/lib/tasks/generate_packs.rake +144 -1
  82. data/package-lock.json +11984 -0
  83. data/react_on_rails.gemspec +1 -0
  84. metadata +55 -11
  85. data/REACT-ON-RAILS-PRO-LICENSE +0 -95
  86. data/lib/generators/react_on_rails/adapt_for_older_shakapacker_generator.rb +0 -41
  87. data/lib/generators/react_on_rails/bin/dev +0 -30
  88. data/lib/generators/react_on_rails/bin/dev-static +0 -30
  89. data/lib/generators/react_on_rails/templates/base/base/Procfile.dev-static.tt +0 -9
  90. data/lib/generators/react_on_rails/templates/base/base/Procfile.dev.tt +0 -5
  91. data/lib/generators/react_on_rails/templates/base/base/app/javascript/packs/registration.js.tt +0 -8
  92. /data/lib/generators/react_on_rails/templates/base/base/config/webpack/{webpackConfig.js.tt → generateWebpackConfigs.js.tt} +0 -0
  93. /data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/startup/{HelloWorldApp.jsx → HelloWorldApp.client.jsx} +0 -0
@@ -1,11 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "rails/generators"
4
+ require "json"
4
5
  require_relative "generator_helper"
5
6
  require_relative "generator_messages"
6
7
 
7
8
  module ReactOnRails
8
9
  module Generators
10
+ # rubocop:disable Metrics/ClassLength
9
11
  class InstallGenerator < Rails::Generators::Base
10
12
  include GeneratorHelper
11
13
 
@@ -16,22 +18,38 @@ module ReactOnRails
16
18
  class_option :redux,
17
19
  type: :boolean,
18
20
  default: false,
19
- desc: "Install Redux gems and Redux version of Hello World Example. Default: false",
21
+ desc: "Install Redux package and Redux version of Hello World Example. Default: false",
20
22
  aliases: "-R"
21
23
 
24
+ # --typescript
25
+ class_option :typescript,
26
+ type: :boolean,
27
+ default: false,
28
+ desc: "Generate TypeScript files and install TypeScript dependencies. Default: false",
29
+ aliases: "-T"
30
+
22
31
  # --ignore-warnings
23
32
  class_option :ignore_warnings,
24
33
  type: :boolean,
25
34
  default: false,
26
35
  desc: "Skip warnings. Default: false"
27
36
 
37
+ # Removed: --skip-shakapacker-install (Shakapacker is now a required dependency)
38
+
28
39
  def run_generators
29
40
  if installation_prerequisites_met? || options.ignore_warnings?
30
41
  invoke_generators
31
42
  add_bin_scripts
32
43
  add_post_install_message
33
44
  else
34
- error = "react_on_rails generator prerequisites not met!"
45
+ error = <<~MSG.strip
46
+ 🚫 React on Rails generator prerequisites not met!
47
+
48
+ Please resolve the issues listed above before continuing.
49
+ All prerequisites must be satisfied for a successful installation.
50
+
51
+ Use --ignore-warnings to bypass checks (not recommended).
52
+ MSG
35
53
  GeneratorMessages.add_error(error)
36
54
  end
37
55
  ensure
@@ -47,45 +65,97 @@ module ReactOnRails
47
65
  end
48
66
 
49
67
  def invoke_generators
50
- invoke "react_on_rails:base"
68
+ ensure_shakapacker_installed
69
+ if options.typescript?
70
+ install_typescript_dependencies
71
+ create_css_module_types
72
+ create_typescript_config
73
+ end
74
+ invoke "react_on_rails:base", [], { typescript: options.typescript? }
51
75
  if options.redux?
52
- invoke "react_on_rails:react_with_redux"
76
+ invoke "react_on_rails:react_with_redux", [], { typescript: options.typescript? }
53
77
  else
54
- invoke "react_on_rails:react_no_redux"
78
+ invoke "react_on_rails:react_no_redux", [], { typescript: options.typescript? }
55
79
  end
56
-
57
- invoke "react_on_rails:adapt_for_older_shakapacker" unless using_shakapacker_7_or_above?
58
80
  end
59
81
 
60
82
  # NOTE: other requirements for existing files such as .gitignore or application.
61
83
  # js(.coffee) are not checked by this method, but instead produce warning messages
62
84
  # and allow the build to continue
63
85
  def installation_prerequisites_met?
64
- !(missing_node? || missing_yarn? || ReactOnRails::GitUtils.uncommitted_changes?(GeneratorMessages))
86
+ !(missing_node? || missing_package_manager? || ReactOnRails::GitUtils.uncommitted_changes?(GeneratorMessages))
65
87
  end
66
88
 
67
- def missing_yarn?
68
- return false unless ReactOnRails::Utils.running_on_windows? ? `where yarn`.blank? : `which yarn`.blank?
89
+ def missing_node?
90
+ node_missing = ReactOnRails::Utils.running_on_windows? ? `where node`.blank? : `which node`.blank?
69
91
 
70
- error = "yarn is required. Please install it before continuing. https://yarnpkg.com/en/docs/install"
71
- GeneratorMessages.add_error(error)
72
- true
92
+ if node_missing
93
+ error = <<~MSG.strip
94
+ 🚫 Node.js is required but not found on your system.
95
+
96
+ Please install Node.js before continuing:
97
+ • Download from: https://nodejs.org/en/
98
+ • Recommended: Use a version manager like nvm, fnm, or volta
99
+ • Minimum required version: Node.js 18+
100
+
101
+ After installation, restart your terminal and try again.
102
+ MSG
103
+ GeneratorMessages.add_error(error)
104
+ return true
105
+ end
106
+
107
+ # Check Node.js version if available
108
+ check_node_version
109
+ false
73
110
  end
74
111
 
75
- def missing_node?
76
- return false unless ReactOnRails::Utils.running_on_windows? ? `where node`.blank? : `which node`.blank?
112
+ def check_node_version
113
+ node_version = `node --version 2>/dev/null`.strip
114
+ return if node_version.blank?
77
115
 
78
- error = "** nodejs is required. Please install it before continuing. https://nodejs.org/en/"
79
- GeneratorMessages.add_error(error)
80
- true
116
+ # Extract major version number (e.g., "v18.17.0" -> 18)
117
+ major_version = node_version[/v(\d+)/, 1]&.to_i
118
+ return unless major_version
119
+
120
+ return unless major_version < 18
121
+
122
+ warning = <<~MSG.strip
123
+ ⚠️ Node.js version #{node_version} detected.
124
+
125
+ React on Rails recommends Node.js 18+ for best compatibility.
126
+ You may experience issues with older versions.
127
+
128
+ Consider upgrading: https://nodejs.org/en/
129
+ MSG
130
+ GeneratorMessages.add_warning(warning)
131
+ end
132
+
133
+ def ensure_shakapacker_installed
134
+ return if shakapacker_configured?
135
+
136
+ print_shakapacker_setup_banner
137
+ ensure_shakapacker_in_gemfile
138
+ install_shakapacker
139
+ finalize_shakapacker_setup
140
+ end
141
+
142
+ # Checks whether "shakapacker" is explicitly declared in this project's Gemfile.
143
+ # We only check the Gemfile text, not lockfile or dependencies, because
144
+ # shakapacker might be present as a dependency of react_on_rails but not
145
+ # properly configured for this specific Rails application.
146
+ def shakapacker_in_gemfile?
147
+ gem_name = "shakapacker"
148
+ shakapacker_in_gemfile_text?(gem_name)
81
149
  end
82
150
 
83
151
  def add_bin_scripts
84
- directory "#{__dir__}/bin", "bin"
152
+ # Copy bin scripts from templates
153
+ template_bin_path = "#{__dir__}/templates/base/base/bin"
154
+ directory template_bin_path, "bin"
85
155
 
86
156
  # Make these and only these files executable
87
157
  files_to_copy = []
88
- Dir.chdir("#{__dir__}/bin") do
158
+ Dir.chdir(template_bin_path) do
89
159
  files_to_copy.concat(Dir.glob("*"))
90
160
  end
91
161
  files_to_become_executable = files_to_copy.map { |filename| "bin/#{filename}" }
@@ -94,16 +164,256 @@ module ReactOnRails
94
164
  end
95
165
 
96
166
  def add_post_install_message
97
- GeneratorMessages.add_info(GeneratorMessages.helpful_message_after_installation)
167
+ # Determine what route will be created by the generator
168
+ route = "hello_world" # This is the hardcoded route from base_generator.rb
169
+ component_name = options.redux? ? "HelloWorldApp" : "HelloWorld"
170
+
171
+ GeneratorMessages.add_info(GeneratorMessages.helpful_message_after_installation(
172
+ component_name: component_name,
173
+ route: route
174
+ ))
175
+ end
176
+
177
+ def shakapacker_loaded_in_process?(gem_name)
178
+ Gem.loaded_specs.key?(gem_name)
179
+ end
180
+
181
+ def shakapacker_in_lockfile?(gem_name)
182
+ gemfile = ENV["BUNDLE_GEMFILE"] || "Gemfile"
183
+ lockfile = File.join(File.dirname(gemfile), "Gemfile.lock")
184
+
185
+ File.file?(lockfile) && File.foreach(lockfile).any? { |l| l.match?(/^\s{4}#{Regexp.escape(gem_name)}\s\(/) }
186
+ end
187
+
188
+ def shakapacker_in_bundler_specs?(gem_name)
189
+ require "bundler"
190
+ Bundler.load.specs.any? { |s| s.name == gem_name }
191
+ rescue StandardError
192
+ false
193
+ end
194
+
195
+ def shakapacker_in_gemfile_text?(gem_name)
196
+ gemfile = ENV["BUNDLE_GEMFILE"] || "Gemfile"
197
+
198
+ File.file?(gemfile) &&
199
+ File.foreach(gemfile).any? { |l| l.match?(/^\s*gem\s+['"]#{Regexp.escape(gem_name)}['"]/) }
200
+ end
201
+
202
+ def cli_exists?(command)
203
+ system("which #{command} > /dev/null 2>&1")
204
+ end
205
+
206
+ def shakapacker_binaries_exist?
207
+ File.exist?("bin/shakapacker") && File.exist?("bin/shakapacker-dev-server")
208
+ end
209
+
210
+ def shakapacker_configured?
211
+ # Check for essential shakapacker configuration files and binaries
212
+ shakapacker_binaries_exist? &&
213
+ File.exist?("config/shakapacker.yml") &&
214
+ File.exist?("config/webpack/webpack.config.js")
215
+ end
216
+
217
+ def print_shakapacker_setup_banner
218
+ puts Rainbow("\n#{'=' * 80}").cyan
219
+ puts Rainbow("🔧 SHAKAPACKER SETUP").cyan.bold
220
+ puts Rainbow("=" * 80).cyan
221
+ end
222
+
223
+ def ensure_shakapacker_in_gemfile
224
+ return if shakapacker_in_gemfile?
225
+
226
+ puts Rainbow("📝 Adding Shakapacker to Gemfile...").yellow
227
+ success = system("bundle add shakapacker --strict")
228
+ return if success
229
+
230
+ handle_shakapacker_gemfile_error
231
+ end
232
+
233
+ def install_shakapacker
234
+ puts Rainbow("⚙️ Installing Shakapacker (required for webpack integration)...").yellow
235
+
236
+ # First run bundle install to make shakapacker available
237
+ puts Rainbow("📦 Running bundle install...").yellow
238
+ bundle_success = system("bundle install")
239
+ unless bundle_success
240
+ handle_shakapacker_install_error
241
+ return
242
+ end
243
+
244
+ # Then run the shakapacker installer
245
+ success = system("bundle exec rails shakapacker:install")
246
+ return if success
247
+
248
+ handle_shakapacker_install_error
98
249
  end
99
250
 
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
251
+ def finalize_shakapacker_setup
252
+ puts Rainbow("✅ Shakapacker installed successfully!").green
253
+ puts Rainbow("=" * 80).cyan
254
+ puts Rainbow("🚀 CONTINUING WITH REACT ON RAILS SETUP").cyan.bold
255
+ puts "#{Rainbow('=' * 80).cyan}\n"
256
+
257
+ # Create marker file so base generator can avoid copying shakapacker.yml
258
+ File.write(".shakapacker_just_installed", "")
259
+ end
260
+
261
+ def handle_shakapacker_gemfile_error
262
+ error = <<~MSG.strip
263
+ 🚫 Failed to add Shakapacker to your Gemfile.
264
+
265
+ This could be due to:
266
+ • Bundle installation issues
267
+ • Network connectivity problems
268
+ • Gemfile permissions
269
+
270
+ Please try manually:
271
+ bundle add shakapacker --strict
272
+
273
+ Then re-run: rails generate react_on_rails:install
274
+ MSG
275
+ GeneratorMessages.add_error(error)
276
+ raise Thor::Error, error unless options.ignore_warnings?
277
+ end
278
+
279
+ def handle_shakapacker_install_error
280
+ error = <<~MSG.strip
281
+ 🚫 Failed to install Shakapacker automatically.
282
+
283
+ This could be due to:
284
+ • Missing Node.js or npm/yarn
285
+ • Network connectivity issues
286
+ • Incomplete bundle installation
287
+ • Missing write permissions
288
+
289
+ Troubleshooting steps:
290
+ 1. Ensure Node.js is installed: node --version
291
+ 2. Run: bundle install
292
+ 3. Try manually: bundle exec rails shakapacker:install
293
+ 4. Check for error output above
294
+ 5. Re-run: rails generate react_on_rails:install
295
+
296
+ Need help? Visit: https://github.com/shakacode/shakapacker/blob/main/docs/installation.md
297
+ MSG
298
+ GeneratorMessages.add_error(error)
299
+ raise Thor::Error, error unless options.ignore_warnings?
300
+ end
301
+
302
+ def missing_package_manager?
303
+ package_managers = %w[npm pnpm yarn bun]
304
+ missing = package_managers.none? { |pm| cli_exists?(pm) }
305
+
306
+ if missing
307
+ error = <<~MSG.strip
308
+ 🚫 No JavaScript package manager found on your system.
309
+
310
+ React on Rails requires a JavaScript package manager to install dependencies.
311
+ Please install one of the following:
312
+
313
+ • npm: Usually comes with Node.js (https://nodejs.org/en/)
314
+ • yarn: npm install -g yarn (https://yarnpkg.com/)
315
+ • pnpm: npm install -g pnpm (https://pnpm.io/)
316
+ • bun: Install from https://bun.sh/
317
+
318
+ After installation, restart your terminal and try again.
319
+ MSG
320
+ GeneratorMessages.add_error(error)
321
+ return true
322
+ end
323
+
105
324
  false
106
325
  end
326
+
327
+ def install_typescript_dependencies
328
+ puts Rainbow("📝 Installing TypeScript dependencies...").yellow
329
+
330
+ # Install TypeScript and React type definitions
331
+ typescript_packages = %w[
332
+ typescript
333
+ @types/react
334
+ @types/react-dom
335
+ @babel/preset-typescript
336
+ ]
337
+
338
+ # Try using GeneratorHelper first (package manager agnostic)
339
+ return if add_npm_dependencies(typescript_packages, dev: true)
340
+
341
+ # Fallback to npm if GeneratorHelper fails
342
+ success = system("npm", "install", "--save-dev", *typescript_packages)
343
+ return if success
344
+
345
+ warning = <<~MSG.strip
346
+ ⚠️ Failed to install TypeScript dependencies automatically.
347
+
348
+ Please run manually:
349
+ npm install --save-dev #{typescript_packages.join(' ')}
350
+ MSG
351
+ GeneratorMessages.add_warning(warning)
352
+ end
353
+
354
+ def create_css_module_types
355
+ puts Rainbow("📝 Creating CSS module type definitions...").yellow
356
+
357
+ # Ensure the types directory exists
358
+ FileUtils.mkdir_p("app/javascript/types")
359
+
360
+ css_module_types_content = <<~TS.strip
361
+ // TypeScript definitions for CSS modules
362
+ declare module "*.module.css" {
363
+ const classes: { [key: string]: string };
364
+ export default classes;
365
+ }
366
+
367
+ declare module "*.module.scss" {
368
+ const classes: { [key: string]: string };
369
+ export default classes;
370
+ }
371
+
372
+ declare module "*.module.sass" {
373
+ const classes: { [key: string]: string };
374
+ export default classes;
375
+ }
376
+ TS
377
+
378
+ File.write("app/javascript/types/css-modules.d.ts", css_module_types_content)
379
+ puts Rainbow("✅ Created CSS module type definitions").green
380
+ end
381
+
382
+ def create_typescript_config
383
+ if File.exist?("tsconfig.json")
384
+ puts Rainbow("⚠️ tsconfig.json already exists, skipping creation").yellow
385
+ return
386
+ end
387
+
388
+ tsconfig_content = {
389
+ "compilerOptions" => {
390
+ "target" => "es2018",
391
+ "allowJs" => true,
392
+ "skipLibCheck" => true,
393
+ "strict" => true,
394
+ "noUncheckedIndexedAccess" => true,
395
+ "forceConsistentCasingInFileNames" => true,
396
+ "noFallthroughCasesInSwitch" => true,
397
+ "module" => "esnext",
398
+ "moduleResolution" => "bundler",
399
+ "resolveJsonModule" => true,
400
+ "isolatedModules" => true,
401
+ "noEmit" => true,
402
+ "jsx" => "react-jsx"
403
+ },
404
+ "include" => [
405
+ "app/javascript/**/*"
406
+ ]
407
+ }
408
+
409
+ File.write("tsconfig.json", JSON.pretty_generate(tsconfig_content))
410
+ puts Rainbow("✅ Created tsconfig.json").green
411
+ end
412
+
413
+ # Removed: Shakapacker auto-installation logic (now explicit dependency)
414
+
415
+ # Removed: Shakapacker 8+ is now required as explicit dependency
416
+ # rubocop:enable Metrics/ClassLength
107
417
  end
108
418
  end
109
419
  end
@@ -7,24 +7,37 @@ 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
 
14
+ class_option :typescript,
15
+ type: :boolean,
16
+ default: false,
17
+ desc: "Generate TypeScript files"
18
+
13
19
  def copy_base_files
14
20
  base_js_path = "base/base"
15
- base_files = %w[app/javascript/bundles/HelloWorld/components/HelloWorld.jsx]
16
- base_files.each { |file| copy_file("#{base_js_path}/#{file}", file) }
21
+
22
+ # Determine which component files to copy based on TypeScript option
23
+ component_files = [
24
+ "app/javascript/src/HelloWorld/ror_components/HelloWorld.client.#{component_extension(options)}",
25
+ "app/javascript/src/HelloWorld/ror_components/HelloWorld.server.#{component_extension(options)}",
26
+ "app/javascript/src/HelloWorld/ror_components/HelloWorld.module.css"
27
+ ]
28
+
29
+ component_files.each do |file|
30
+ copy_file("#{base_js_path}/#{file}", file)
31
+ end
17
32
  end
18
33
 
19
34
  def create_appropriate_templates
20
35
  base_path = "base/base"
21
36
  config = {
22
- component_name: "HelloWorld",
23
- app_relative_path: "../bundles/HelloWorld/components/HelloWorld"
37
+ component_name: "HelloWorld"
24
38
  }
25
39
 
26
- template("#{base_path}/app/javascript/packs/registration.js.tt",
27
- "app/javascript/packs/hello-world-bundle.js", config)
40
+ # Only create the view template - no manual bundle needed for auto registration
28
41
  template("#{base_path}/app/views/hello_world/index.html.erb.tt",
29
42
  "app/views/hello_world/index.html.erb", config)
30
43
  end
@@ -1,51 +1,144 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "rails/generators"
4
+ require_relative "generator_helper"
5
+ require_relative "generator_messages"
4
6
 
5
7
  module ReactOnRails
6
8
  module Generators
7
9
  class ReactWithReduxGenerator < Rails::Generators::Base
10
+ include GeneratorHelper
11
+
8
12
  Rails::Generators.hide_namespace(namespace)
9
13
  source_root(File.expand_path("templates", __dir__))
10
14
 
15
+ class_option :typescript,
16
+ type: :boolean,
17
+ default: false,
18
+ desc: "Generate TypeScript files",
19
+ aliases: "-T"
20
+
11
21
  def create_redux_directories
12
- dirs = %w[actions constants containers reducers store startup]
13
- dirs.each { |name| empty_directory("app/javascript/bundles/HelloWorld/#{name}") }
22
+ # Create auto-registration directory structure for Redux
23
+ empty_directory("app/javascript/src/HelloWorldApp/ror_components")
24
+
25
+ # Create Redux support directories within the component directory
26
+ dirs = %w[actions constants containers reducers store components]
27
+ dirs.each { |name| empty_directory("app/javascript/src/HelloWorldApp/#{name}") }
14
28
  end
15
29
 
16
30
  def copy_base_files
17
31
  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) }
32
+ ext = component_extension(options)
33
+
34
+ # Copy Redux-connected component to auto-registration structure
35
+ copy_file("#{base_js_path}/app/javascript/bundles/HelloWorld/startup/HelloWorldApp.client.#{ext}",
36
+ "app/javascript/src/HelloWorldApp/ror_components/HelloWorldApp.client.#{ext}")
37
+ copy_file("#{base_js_path}/app/javascript/bundles/HelloWorld/startup/HelloWorldApp.server.#{ext}",
38
+ "app/javascript/src/HelloWorldApp/ror_components/HelloWorldApp.server.#{ext}")
39
+ copy_file("#{base_js_path}/app/javascript/bundles/HelloWorld/components/HelloWorld.module.css",
40
+ "app/javascript/src/HelloWorldApp/components/HelloWorld.module.css")
41
+
42
+ # Update import paths in client component
43
+ ror_client_file = "app/javascript/src/HelloWorldApp/ror_components/HelloWorldApp.client.#{ext}"
44
+ gsub_file(ror_client_file, "../store/helloWorldStore", "../store/helloWorldStore")
45
+ gsub_file(ror_client_file, "../containers/HelloWorldContainer",
46
+ "../containers/HelloWorldContainer")
20
47
  end
21
48
 
22
49
  def copy_base_redux_files
23
50
  base_hello_world_path = "redux/base/app/javascript/bundles/HelloWorld"
24
- %w[actions/helloWorldActionCreators.js
25
- containers/HelloWorldContainer.js
26
- constants/helloWorldConstants.js
27
- reducers/helloWorldReducer.js
28
- store/helloWorldStore.js
29
- startup/HelloWorldApp.jsx].each do |file|
51
+ redux_extension = options.typescript? ? "ts" : "js"
52
+
53
+ # Copy Redux infrastructure files with appropriate extension
54
+ %W[actions/helloWorldActionCreators.#{redux_extension}
55
+ containers/HelloWorldContainer.#{redux_extension}
56
+ constants/helloWorldConstants.#{redux_extension}
57
+ reducers/helloWorldReducer.#{redux_extension}
58
+ store/helloWorldStore.#{redux_extension}
59
+ components/HelloWorld.#{component_extension(options)}].each do |file|
30
60
  copy_file("#{base_hello_world_path}/#{file}",
31
- "app/javascript/bundles/HelloWorld/#{file}")
61
+ "app/javascript/src/HelloWorldApp/#{file}")
32
62
  end
33
63
  end
34
64
 
35
65
  def create_appropriate_templates
36
66
  base_path = "base/base"
37
- base_js_path = "#{base_path}/app/javascript"
38
67
  config = {
39
- component_name: "HelloWorldApp",
40
- app_relative_path: "../bundles/HelloWorld/startup/HelloWorldApp"
68
+ component_name: "HelloWorldApp"
41
69
  }
42
70
 
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)
71
+ # Only create the view template - no manual bundle needed for auto registration
72
+ template("#{base_path}/app/views/hello_world/index.html.erb.tt",
73
+ "app/views/hello_world/index.html.erb", config)
74
+ end
75
+
76
+ def add_redux_npm_dependencies
77
+ # Add Redux dependencies as regular dependencies
78
+ regular_packages = %w[redux react-redux]
79
+
80
+ # Try using GeneratorHelper first (package manager agnostic)
81
+ success = add_npm_dependencies(regular_packages)
82
+
83
+ # Fallback to package manager detection if GeneratorHelper fails
84
+ return if success
85
+
86
+ package_manager = GeneratorMessages.detect_package_manager
87
+ return unless package_manager
88
+
89
+ install_packages_with_fallback(regular_packages, dev: false, package_manager: package_manager)
90
+ end
91
+
92
+ private
93
+
94
+ def install_packages_with_fallback(packages, dev:, package_manager:)
95
+ install_args = build_install_args(package_manager, dev, packages)
96
+
97
+ success = system(*install_args)
98
+ return if success
99
+
100
+ install_command = install_args.join(" ")
101
+ warning = <<~MSG.strip
102
+ ⚠️ Failed to install Redux dependencies automatically.
103
+
104
+ Please run manually:
105
+ #{install_command}
106
+ MSG
107
+ GeneratorMessages.add_warning(warning)
108
+ end
109
+
110
+ def build_install_args(package_manager, dev, packages)
111
+ # Security: Validate package manager to prevent command injection
112
+ allowed_package_managers = %w[npm yarn pnpm bun].freeze
113
+ unless allowed_package_managers.include?(package_manager)
114
+ raise ArgumentError, "Invalid package manager: #{package_manager}"
115
+ end
116
+
117
+ base_commands = {
118
+ "npm" => %w[npm install],
119
+ "yarn" => %w[yarn add],
120
+ "pnpm" => %w[pnpm add],
121
+ "bun" => %w[bun add]
122
+ }
123
+
124
+ base_args = base_commands[package_manager].dup
125
+ base_args << dev_flag_for(package_manager) if dev
126
+ base_args + packages
127
+ end
128
+
129
+ def dev_flag_for(package_manager)
130
+ case package_manager
131
+ when "npm", "pnpm" then "--save-dev"
132
+ when "yarn", "bun" then "--dev"
133
+ end
45
134
  end
46
135
 
47
- def add_redux_yarn_dependencies
48
- run "yarn add redux react-redux"
136
+ def add_redux_specific_messages
137
+ # Override the generic messages with Redux-specific instructions
138
+ GeneratorMessages.output.clear
139
+ GeneratorMessages.add_info(
140
+ GeneratorMessages.helpful_message_after_installation(component_name: "HelloWorldApp", route: "hello_world")
141
+ )
49
142
  end
50
143
  end
51
144
  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;