react_on_rails 14.2.1 → 16.1.1

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